/* * Copyright (C) 2012 Dr. John Lindsay <jlindsay@uoguelph.ca> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package whitebox.geospatialfiles.shapefile; import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; import java.util.ArrayList; import java.util.Collections; import whitebox.structures.BoundingBox; /** * * @author Dr. John Lindsay email: jlindsay@uoguelph.ca */ public class PointsList { private ArrayList<ShapefilePoint> myList = new ArrayList<>(); boolean isClosedForAdding = false; BoundingBox box = new BoundingBox(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); public PointsList() { } public PointsList(ArrayList<ShapefilePoint> list) { for (ShapefilePoint sfp : list) { myList.add(sfp); } } private void updateBox(double x, double y) { if (box.getMinX() > x) box.setMinX(x); if (box.getMaxX() < x) box.setMaxX(x); if (box.getMinY() > y) box.setMinY(y); if (box.getMaxY() < y) box.setMaxY(y); } public void addPoint(double x, double y) { if (isClosedForAdding) { return; } ShapefilePoint sfp = new ShapefilePoint(x, y); myList.add(sfp); updateBox(x, y); } public void addMPoint(double x, double y, double m) { if (isClosedForAdding) { return; } ShapefilePoint sfp = new ShapefilePoint(x, y); sfp.m = m; myList.add(sfp); updateBox(x, y); } public void addMPoint(double x, double y) { if (isClosedForAdding) { return; } ShapefilePoint sfp = new ShapefilePoint(x, y); myList.add(sfp); updateBox(x, y); } public void addZPoint(double x, double y, double z, double m) { if (isClosedForAdding) { return; } ShapefilePoint sfp = new ShapefilePoint(x, y); sfp.z = z; sfp.m = m; myList.add(sfp); updateBox(x, y); } public void addZPoint(double x, double y, double z) { if (isClosedForAdding) { return; } ShapefilePoint sfp = new ShapefilePoint(x, y); sfp.z = z; myList.add(sfp); updateBox(x, y); } public void clear() { myList.clear(); } public void removePoint(int i) { ShapefilePoint sfp = myList.get(i); myList.remove(i); if (!box.entirelyContains(sfp.x, sfp.y)) { // update the box box = new BoundingBox(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY); for (ShapefilePoint sfp2 : myList) { if (box.getMinX() > sfp2.x) box.setMinX(sfp2.x); if (box.getMaxX() < sfp2.x) box.setMaxX(sfp2.x); if (box.getMinY() > sfp2.y) box.setMinY(sfp2.y); if (box.getMaxY() < sfp2.y) box.setMaxY(sfp2.y); } } } public BoundingBox getBox() { return box; } public ShapefilePoint getPoint(int i) { return myList.get(i); } public double[][] getPointsArray() { double[][] ret = new double[myList.size()][2]; int i = 0; for (ShapefilePoint sfp : myList) { ret[i][0] = sfp.x; ret[i][1] = sfp.y; i++; } return ret; } public CoordinateArraySequence getCoordinateArraySequence() { CoordinateArraySequence ret = new CoordinateArraySequence(myList.size()); int i = 0; for (ShapefilePoint sfp : myList) { ret.setOrdinate(i, 0, sfp.x); ret.setOrdinate(i, 1, sfp.y); i++; } return ret; } public double[] getZArray() { double[] ret = new double[myList.size()]; int i = 0; for (ShapefilePoint sfp : myList) { ret[i] = sfp.z; i++; } return ret; } public double[] getMArray() { double[] ret = new double[myList.size()]; int i = 0; for (ShapefilePoint sfp : myList) { ret[i] = sfp.m; i++; } return ret; } public void closePolygon() { ShapefilePoint firstPoint = myList.get(0); ShapefilePoint sfp = new ShapefilePoint(firstPoint.x, firstPoint.y); myList.add(sfp); isClosedForAdding = true; } public boolean isClockwiseOrder() { // Note: holes are polygons that have verticies in counter-clockwise order // This approach is based on the method described by Paul Bourke, March 1998 // http://paulbourke.net/geometry/clockwise/index.html //int stPoint, endPoint; double x0, y0, x1, y1, x2, y2; int n1 = 0, n2 = 0, n3 = 0; int numPointsInList = myList.size(); if (numPointsInList < 2) { return false; } // something's wrong! // first see if it is a convex or concave polygon // calculate the cross product for each adjacent edge. double[] crossproducts = new double[numPointsInList]; for (int j = 0; j < numPointsInList; j++) { n2 = j; if (j == 0) { n1 = numPointsInList - 1; n3 = j + 1; } else if (j == numPointsInList - 1) { n1 = j - 1; n3 = 0; } else { n1 = j - 1; n3 = j + 1; } x0 = myList.get(n1).x; y0 = myList.get(n1).y; //points[n1][1]; x1 = myList.get(n2).x; //points[n2][0]; y1 = myList.get(n2).y; //points[n2][1]; x2 = myList.get(n3).x; //points[n3][0]; y2 = myList.get(n3).y; //points[n3][1]; crossproducts[j] = (x1 - x0) * (y2 - y1) - (y1 - y0) * (x2 - x1); } boolean testSign; testSign = crossproducts[0] >= 0; boolean isConvex = true; for (int j = 1; j < numPointsInList; j++) { if (crossproducts[j] >= 0 && !testSign) { isConvex = false; break; } else if (crossproducts[j] < 0 && testSign) { isConvex = false; break; } } // now see if it is clockwise or counter-clockwise boolean isHole; if (isConvex) { if (testSign) { // positive means counter-clockwise isHole = true; } else { isHole = false; } } else { // calculate the polygon area. If it is positive is is in clockwise order, else counter-clockwise. double area2 = 0; for (int j = 0; j < numPointsInList; j++) { n1 = j; if (j < numPointsInList - 1) { n2 = j + 1; } else { n2 = 0; } x1 = myList.get(n1).x; //points[n1][0]; y1 = myList.get(n1).y; //points[n1][1]; x2 = myList.get(n2).x; //points[n2][0]; y2 = myList.get(n2).y; //points[n2][1]; area2 += (x1 * y2) - (x2 * y1); } area2 = area2 / 2.0; if (area2 < 0) { // a positive area indicates counter-clockwise order isHole = false; } else { isHole = true; } } return isHole; } public int size() { return myList.size(); } public void reverseOrder() { Collections.reverse(myList); } public void removeDuplicates() { int numElements = myList.size(); for (int i = numElements - 1; i > 0; i--) { ShapefilePoint p1 = myList.get(i); ShapefilePoint p2 = myList.get(i - 1); if (p1.equals(p2)) { myList.remove(i); } } } @Override public PointsList clone() throws CloneNotSupportedException { // super.clone(); ArrayList<ShapefilePoint> newList = new ArrayList<>(); for (ShapefilePoint p : myList) { newList.add(p); } return new PointsList(newList); } public void appendList(PointsList other) { if (other == null) { return; } for (int i = 0; i < other.size(); i++) { ShapefilePoint p = other.getPoint(i); myList.add(p); } } }