Java tutorial
// This code is part of the CPCC-NG project. // // Copyright (c) 2013 Clemens Krainer <clemens.krainer@gmail.com> // // 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 2 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, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. package cpcc.core.base; import java.math.BigDecimal; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.stream.Collectors; import org.apache.commons.lang3.ArrayUtils; import org.geojson.LngLatAlt; import cpcc.core.entities.PolarCoordinate; /** * PolygonZone */ public class PolygonZone { private static final double EPSILON = 1E-9; private TwoTuple[] vertices; private double maxLat = Double.NEGATIVE_INFINITY; private double minLat = Double.POSITIVE_INFINITY; private double maxLon = Double.NEGATIVE_INFINITY; private double minLon = Double.POSITIVE_INFINITY; /** * @param v the vertices of the polygon as polar coordinates. */ public PolygonZone(PolarCoordinate[] v) { vertices = new TwoTuple[v.length]; for (int k = 0, l = v.length; k < l; ++k) { double x = v[k].getLatitude(); double y = v[k].getLongitude(); vertices[k] = new TwoTuple(x, y); } findBoundingBox(); } /** * @param v the vertices of the polygon as polar coordinates. */ public PolygonZone(List<LngLatAlt> v) { vertices = new TwoTuple[v.size()]; for (int k = 0, l = v.size(); k < l; ++k) { double x = v.get(k).getLatitude(); double y = v.get(k).getLongitude(); vertices[k] = new TwoTuple(x, y); } findBoundingBox(); } /** * @param vertices the vertices of the polygon as two-tuples. */ public PolygonZone(TwoTuple[] vertices) { this.vertices = ArrayUtils.clone(vertices); findBoundingBox(); } /** * @return get the vertices of this polygon. */ public List<LngLatAlt> getVertices() { return Arrays.asList(vertices).stream().map(tuple -> new LngLatAlt(tuple.getY(), tuple.getX())) .collect(Collectors.toList()); } /** * Find the bounding box of the polygon. */ private void findBoundingBox() { for (int k = 0, l = vertices.length; k < l; ++k) { if (vertices[k].x < minLat) { minLat = vertices[k].x; } if (vertices[k].x > maxLat) { maxLat = vertices[k].x; } if (vertices[k].y < minLon) { minLon = vertices[k].y; } if (vertices[k].y > maxLon) { maxLon = vertices[k].y; } } // TODO setDepotPosition(getCenterOfGravity()); } /** * @param p the coordinate to be checked. * @return true if the coordinate is inside the polygon, false otherwise. */ public boolean isInside(PolarCoordinate p) { if (p == null) { return false; } return isInside(p.getLatitude(), p.getLongitude()); } /** * @param cx the X-coordinate of a position. * @param cy the Y-coordinate of a position. * @return true if the position is inside the polygon, false otherwise. */ public boolean isInside(double cx, double cy) { if (isOutsideBoundingBox(cx, cy)) { return false; } int right = 0; int left = 0; for (int i = 0, l = vertices.length; i < l; ++i) { double ax = vertices[i].x; double ay = vertices[i].y; double bx = i + 1 == l ? vertices[0].x : vertices[i + 1].x; double by = i + 1 == l ? vertices[0].y : vertices[i + 1].y; if (pointIsAVertice(ax, ay, cx, cy)) { return true; } ay = adjustVerticalVerticeCoordinate(cy, ay); by = adjustVerticalVerticeCoordinate(cy, by); if (thereIsNoPointOfIntersection(ay, by, cy)) { continue; } if (segmentIsRightOfPoint(ax, bx, cx)) { ++right; continue; } if (segmentIsLeftOfPoint(ax, bx, cx)) { ++left; continue; } double k = (by - ay) / (bx - ax); double x = ax + (cy - ay) / k; if (cx <= x) { ++right; } else { ++left; } } return testForUneven(right, left); } /** * @param right the right counter. * @param left the left counter. * @return true if right or left are uneven. */ private boolean testForUneven(int right, int left) { if (right != 0) { return right % 2 != 0; } return left % 2 != 0; } /** * @param ax the X-coordinate of the polygon segment begin * @param bx the X-coordinate of the polygon segment end * @param cx the X-coordinate of the test point * @return true if the segment is on the left side of the point */ private boolean segmentIsLeftOfPoint(double ax, double bx, double cx) { return cx >= ax && cx >= bx; } /** * @param ax the X-coordinate of the polygon segment begin * @param bx the X-coordinate of the polygon segment end * @param cx the X-coordinate of the test point * @return true if the segment is on the right side of the point */ private boolean segmentIsRightOfPoint(double ax, double bx, double cx) { return cx <= ax && cx <= bx; } /** * @param ax the X-coordinate of the polygon vertice * @param cx the Y-coordinate of the test point * @param ay the X-coordinate of the polygon vertice * @param cy the Y-coordinate of the test point * @return */ private boolean pointIsAVertice(double ax, double ay, double cx, double cy) { return Math.abs(cx - ax) <= 1E-9 && Math.abs(cy - ay) <= 1E-9; } /** * @param ay the Y-coordinate of the polygon segment begin * @param by the Y-coordinate of the polygon segment end * @param cy the Y-coordinate of the test point * @return true if there is no point of intersection, false otherwise. */ private boolean thereIsNoPointOfIntersection(double ay, double by, double cy) { return (cy < ay && cy < by) || (cy > ay && cy > by); } /** * @param cy the test point Y-coordinate. * @param ny the node Y-coordinate. * @return the adjusted node Y-coordinate. */ private double adjustVerticalVerticeCoordinate(double cy, double ny) { return Math.abs(cy - ny) <= 1E-9 ? ny + EPSILON : ny; } /** * @param cx the X-coordinate of a position. * @param cy the Y-coordinate of a position. * @return true if the position is outside the bounding box, false otherwise. */ private boolean isOutsideBoundingBox(double cx, double cy) { return cx < minLat || cx > maxLat || cy < minLon || cy > maxLon; } /** * @return the coordinates of the center of gravity. */ public PolarCoordinate getCenterOfGravity() { BigDecimal x = new BigDecimal(0), y = new BigDecimal(0); BigDecimal doubleArea = new BigDecimal(0); for (int k = 0, l = vertices.length - 1; k < l; ++k) { BigDecimal ax = new BigDecimal(vertices[k].x); BigDecimal ay = new BigDecimal(vertices[k].y); BigDecimal bx = new BigDecimal(vertices[k + 1].x); BigDecimal by = new BigDecimal(vertices[k + 1].y); BigDecimal t = ax.multiply(by).subtract(bx.multiply(ay)); x = x.add(ax.add(bx).multiply(t)); y = y.add(ay.add(by).multiply(t)); doubleArea = doubleArea.add(ax.multiply(by).subtract(bx.multiply(ay))); } double sixTimesArea = 3.0 * doubleArea.doubleValue(); return new PolarCoordinate(x.doubleValue() / sixTimesArea, y.doubleValue() / sixTimesArea, 0); } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder b = new StringBuilder(); boolean first = true; for (TwoTuple t : vertices) { if (first) { first = false; } else { b.append(", "); } b.append(t); } return String.format(Locale.US, "vertices: %s", b.toString()); } /** * TwoTuple */ public static class TwoTuple { private double x; private double y; /** * @param x the X-coordinate. * @param y the y-coordinate. */ public TwoTuple(double x, double y) { this.x = x; this.y = y; } /** * @return the X-value. */ public double getX() { return x; } /** * @return the Y-value */ public double getY() { return y; } /** * {@inheritDoc} */ @Override public String toString() { return String.format(Locale.US, "(%.8f, %.8f)", x, y); } } }