/*
* The JTS Topology Suite is a collection of Java classes that
* implement the fundamental operations required to validate a given
* geo-spatial data set to a known topological specification.
*
* Copyright (C) 2001 Vivid Solutions
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.revolsys.geometry.algorithm;
import java.util.Iterator;
import java.util.function.Consumer;
import com.revolsys.geometry.model.BoundingBox;
import com.revolsys.geometry.model.LineString;
import com.revolsys.geometry.model.Location;
import com.revolsys.geometry.model.Point;
import com.revolsys.geometry.model.Polygonal;
import com.revolsys.geometry.model.segment.LineSegment;
/**
* Counts the number of segments crossed by a horizontal ray extending to the right
* from a given point, in an incremental fashion.
* This can be used to determine whether a point lies in a {@link Polygonal} geometry.
* The class determines the situation where the point lies exactly on a segment.
* When being used for Point-In-Polygon determination, this case allows short-circuiting
* the evaluation.
* <p>
* This class handles polygonal geometries with any number of shells and holes.
* The orientation of the shell and hole rings is unimportant.
* In order to compute a correct location for a given polygonal geometry,
* it is essential that <b>all</b> segments are counted which
* <ul>
* <li>touch the ray
* <li>lie in in any ring which may contain the point
* </ul>
* The only exception is when the point-on-segment situation is detected, in which
* case no further processing is required.
* The implication of the above rule is that segments
* which can be a priori determined to <i>not</i> touch the ray
* (i.e. by a test of their bounding box or Y-extent)
* do not need to be counted. This allows for optimization by indexing.
*
* @author Martin Davis
*
*/
public class RayCrossingCounter implements Consumer<LineSegment> {
public static Location locatePointInRing(final Point p, final Iterable<Point> ring) {
final RayCrossingCounter counter = new RayCrossingCounter(p);
final Iterator<Point> iterator = ring.iterator();
if (iterator.hasNext()) {
final Point previousPoint = iterator.next();
while (iterator.hasNext()) {
final Point currentPoint = iterator.next();
counter.countSegment(currentPoint, previousPoint);
if (counter.isOnSegment()) {
return counter.getLocation();
}
}
}
return counter.getLocation();
}
/**
* Determines the {@link Location} of a point in a ring.
*
* @param p
* the point to test
* @param ring
* a coordinate sequence forming a ring
* @return the location of the point in the ring
*/
public static Location locatePointInRing(Point point, final LineString ring) {
if (point == null) {
return Location.EXTERIOR;
} else {
point = point.convertGeometry(ring.getGeometryFactory());
final BoundingBox boundingBox = ring.getBoundingBox();
if (point.intersects(boundingBox)) {
final RayCrossingCounter counter = new RayCrossingCounter(point);
double x1 = ring.getX(0);
double y1 = ring.getY(0);
final int vertexCount = ring.getVertexCount();
for (int i = 1; i < vertexCount; i++) {
final double x2 = ring.getX(i);
final double y2 = ring.getY(i);
counter.countSegment(x2, y2, x1, y1);
if (counter.isOnSegment()) {
return counter.getLocation();
}
x1 = x2;
y1 = y2;
}
return counter.getLocation();
} else {
return Location.EXTERIOR;
}
}
}
/**
* Determines the {@link Location} of a point in a ring.
* This method is an exemplar of how to use this class.
*
* @param p the point to test
* @param ring an array of Point forming a ring
* @return the location of the point in the ring
*/
public static Location locatePointInRing(final Point p, final Point[] ring) {
final RayCrossingCounter counter = new RayCrossingCounter(p);
for (int i = 1; i < ring.length; i++) {
final Point p1 = ring[i];
final Point p2 = ring[i - 1];
counter.countSegment(p1, p2);
if (counter.isOnSegment()) {
return counter.getLocation();
}
}
return counter.getLocation();
}
private int crossingCount = 0;
// true if the test point lies on an input segment
private boolean pointOnSegment = false;
private final double x;
private final double y;
public RayCrossingCounter(final double x, final double y) {
this.x = x;
this.y = y;
}
public RayCrossingCounter(final Point point) {
this(point.getX(), point.getY());
}
@Override
public void accept(final LineSegment segment) {
final double x1 = segment.getX(0);
final double y1 = segment.getY(0);
final double x2 = segment.getX(1);
final double y2 = segment.getY(1);
countSegment(x1, y1, x2, y2);
}
public void countSegment(final double x1, final double y1, final double x2, final double y2) {
if (x1 < this.x && x2 < this.x) {
// check if the segment is strictly to the left of the test point
} else if (this.x == x2 && this.y == y2) {
// check if the point is equal to the current ring vertex
this.pointOnSegment = true;
} else if (y1 == this.y && y2 == this.y) {
/**
* For horizontal segments, check if the point is on the segment. Otherwise,
* horizontal segments are not counted.
*/
double minX = x1;
double maxX = x2;
if (minX > maxX) {
minX = x2;
maxX = x1;
}
if (this.x >= minX && this.x <= maxX) {
this.pointOnSegment = true;
}
} else if (y1 > this.y && y2 <= this.y || y2 > this.y && y1 <= this.y) {
/**
* Evaluate all non-horizontal segments which cross a horizontal ray to the
* right of the test pt. To avoid double-counting shared vertices, we use
* the convention that
* <ul>
* <li>an upward edge includes its starting endpoint, and excludes its final
* endpoint
* <li>a downward edge excludes its starting endpoint, and includes its
* final endpoint
* </ul>
*/
// translate the segment so that the test point lies on the origin
final double deltaX1 = x1 - this.x;
final double deltaY1 = y1 - this.y;
final double deltaX2 = x2 - this.x;
final double deltaY2 = y2 - this.y;
/**
* The translated segment straddles the x-axis. Compute the sign of the
* ordinate of intersection with the x-axis. (y2 != y1, so denominator
* will never be 0.0)
*/
// double xIntSign = RobustDeterminant.signOfDet2x2(x1, y1, x2, y2) / (y2
// - y1);
// MD - faster & more robust computation?
double xIntSign = RobustDeterminant.signOfDet2x2(deltaX1, deltaY1, deltaX2, deltaY2);
if (xIntSign == 0.0) {
this.pointOnSegment = true;
} else {
if (deltaY2 < deltaY1) {
xIntSign = -xIntSign;
// xsave = xInt;
}
// System.out.println("xIntSign(" + x1 + ", " + y1 + ", " + x2 + ", " +
// y2
// + " = " + xIntSign);
// The segment crosses the ray if the sign is strictly positive.
if (xIntSign > 0.0) {
this.crossingCount++;
}
}
}
}
public void countSegment(final LineSegment segment) {
final double x1 = segment.getX(0);
final double y1 = segment.getY(0);
final double x2 = segment.getX(1);
final double y2 = segment.getY(1);
countSegment(x1, y1, x2, y2);
}
/**
* For each segment, check if it crosses a horizontal ray running from the
* test point in the positive x direction.
*
* @param p1 an endpoint of the segment
* @param p2 another endpoint of the segment
*/
public void countSegment(final Point p1, final Point p2) {
final double x1 = p1.getX();
final double y1 = p1.getY();
final double x2 = p2.getX();
final double y2 = p2.getY();
countSegment(x1, y1, x2, y2);
}
/**
* Gets the {@link Location} of the point relative to
* the ring, polygon
* or multipolygon from which the processed segments were provided.
* <p>
* This method only determines the correct location
* if <b>all</b> relevant segments must have been processed.
*
* @return the Location of the point
*/
public Location getLocation() {
if (this.pointOnSegment) {
return Location.BOUNDARY;
}
// The point is in the interior of the ring if the number of X-crossings is
// odd.
if (this.crossingCount % 2 == 1) {
return Location.INTERIOR;
}
return Location.EXTERIOR;
}
public double getX() {
return this.x;
}
public double getY() {
return this.y;
}
/**
* Reports whether the point lies exactly on one of the supplied segments.
* This method may be called at any time as segments are processed.
* If the result of this method is <tt>true</tt>,
* no further segments need be supplied, since the result
* will never change again.
*
* @return true if the point lies exactly on a segment
*/
public boolean isOnSegment() {
return this.pointOnSegment;
}
/**
* Tests whether the point lies in or on
* the ring, polygon
* or multipolygon from which the processed segments were provided.
* <p>
* This method only determines the correct location
* if <b>all</b> relevant segments must have been processed.
*
* @return true if the point lies in or on the supplied polygon
*/
public boolean isPointInPolygon() {
return getLocation() != Location.EXTERIOR;
}
public void setPointOnSegment(final boolean pointOnSegment) {
this.pointOnSegment = pointOnSegment;
}
}