Hi Gerd

Patch attached:

- inherits your is_in-function_v14.patch

- adds Math.round to Coord.makeBetweenPoint(); Is this how you
indented?

- removes IsInUtil.insidePolygon() and changes callers to use
isPointInShape()

- adds ON tolerance to isPointInShape() - I couldn't work out how to do
this with the winding algo, so changed it to crossing-point, which is
fine for mkgmap polygons.

- add some more tolerances to isLineInShape

- isLineInShape had comment:

// it is unlikely but not impossible that pTest is on boundary :(

and it returned the wrong result if it was. This contributed to the
difficulties with b14 (and b19). I've fixed it and, I think, improved
and simplified the running status setting

- fix spelling of rhumLine to rhumbLine

There are still a couple of places that have complicated distance
calculation and point insertion using, bearings, rhumbline, meter
conversion but I don't think it is worth re-implementing them for this.

I'm slightly surprised how much use there is of
bearing/rhumbLine/Mercator projection calculations. I think real
distance/metre calcs should be "great circle" and internal poly/line
chopping, testing etc should be high-precision polar coords.

Ticker

On Thu, 2020-02-27 at 19:36 +0000, Gerd Petermann wrote:
> Hi Ticker,
> 
> if you see a way to change the algo just do it.
> I've just noticed that I forgot to commit a change  in
> Coord.makeBetweenPoint().
> This routine should use Math.round. Will reduce the error by 50%,
> right?
> 
> Gerd
> 
> ________________________________________
> Von: mkgmap-dev <mkgmap-dev-boun...@lists.mkgmap.org.uk> im Auftrag
> von Ticker Berkin <rwb-mkg...@jagit.co.uk>
> Gesendet: Donnerstag, 27. Februar 2020 20:24
> An: Development list for mkgmap
> Betreff: Re: [mkgmap-dev] Work on is_in branch
> 
> Hi Gerd
> 
> Looking at the distance calculations to compare with EPS (ie very
> small), wouldn't it be much simpler and clearer just to calculate it
> as
> highPrecisionSquared units, not bothering with degrees/radians, rhumb
> -line, metre conversion etc. Then EPS would be 4.
> 
> Then, considering IsInUtil.insidePolgon and can it be changed to have
> some tolerance and maybe changing the interface to return IN/ON/OUT,
> it
> looks like it will stop, returning onBoundary, if there is a polygon
> segment that 'aims at' the point.
> 
> Ticker
Index: src/uk/me/parabola/imgfmt/app/Coord.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/Coord.java	(revision 4460)
+++ src/uk/me/parabola/imgfmt/app/Coord.java	(working copy)
@@ -56,6 +56,7 @@
 	public static final int DELTA_SHIFT = HIGH_PREC_BITS - 24; 
 	private static final int MAX_DELTA = 1 << (DELTA_SHIFT - 2); // max delta abs value that is considered okay
 	private static final long FACTOR_HP = 1L << HIGH_PREC_BITS;
+    	private static final int HIGH_PREC_UNUSED_BITS = Integer.SIZE - HIGH_PREC_BITS;
 	
 	public static final double R = 6378137.0; // Radius of earth at equator as defined by WGS84
 	public static final double U = R * 2 * Math.PI; // circumference of earth at equator (WGS84)
@@ -516,6 +517,16 @@
 	}
 
 	/**
+	 * Distance to other point in high precision squared units
+	 */
+	public long distanceInHighPrecSquared(Coord other) {
+		int dLatHp = other.getHighPrecLat() - getHighPrecLat();
+		int dLonHp = other.getHighPrecLon() - getHighPrecLon();
+		dLonHp = (dLonHp << HIGH_PREC_UNUSED_BITS) >> HIGH_PREC_UNUSED_BITS; // fix wrap-around earth
+		return (long)dLatHp * dLatHp + (long)dLonHp * dLonHp;
+	}
+
+	/**
 	 * Calculate point on the line this->other. If d is the distance between this and other,
 	 * the point is {@code fraction * d} metres from this.
 	 * For small distances between this and other we use a flat earth approximation,
@@ -525,15 +536,15 @@
 	public Coord makeBetweenPoint(Coord other, double fraction) {
 		int dlatHp = other.getHighPrecLat() - getHighPrecLat();
 		int dlonHp = other.getHighPrecLon() - getHighPrecLon();
-		if (dlonHp == 0 || Math.abs(dlatHp) < 1000000 && Math.abs(dlonHp) < 1000000 ){
+		if (dlonHp == 0 || Math.abs(dlatHp) < 1000000 && Math.abs(dlonHp) < 1000000) {
 			// distances are rather small, we can use flat earth approximation
-			int latHighPrec = (int) (getHighPrecLat() + dlatHp * fraction);
-			int lonHighPrec = (int) (getHighPrecLon() + dlonHp * fraction);
+			int latHighPrec = (int)Math.round(getHighPrecLat() + dlatHp * fraction);
+			int lonHighPrec = (int)Math.round(getHighPrecLon() + dlonHp * fraction);
 			return makeHighPrecCoord(latHighPrec, lonHighPrec);
 		}
 		double brng = this.bearingToOnRhumbLine(other, true);
 		double dist = this.distance(other) * fraction;
-		return this.destOnRhumLine(dist, brng);
+		return this.destOnRhumbLine(dist, brng);
 	}
 
 	
@@ -763,7 +774,7 @@
 	 * @param brng bearing in degrees
 	 * @return a new Coord instance
 	 */
-	public Coord destOnRhumLine(double dist, double brng){
+	public Coord destOnRhumbLine(double dist, double brng){
 		double distRad = dist / R; // angular distance in radians
 		double lat1 = hpToRadians(this.getHighPrecLat());
 		double lon1 = hpToRadians(this.getHighPrecLon());
@@ -813,7 +824,7 @@
 			// simple calculation using Herons formula will fail
 			// calculate x, the point on line a-b which is as far away from a as this point
 			double b_ab = a.bearingToOnRhumbLine(b, true);
-			Coord x = a.destOnRhumLine(ap, b_ab);
+			Coord x = a.destOnRhumbLine(ap, b_ab);
 			// this dist between these two points is not exactly 
 			// the perpendicul distance, but close enough
 			dist = x.distance(this);
Index: src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java	(revision 4460)
+++ src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java	(working copy)
@@ -67,9 +67,7 @@
 		
 		POLYGON_ALL("all",                FeatureKind.POLYGON,  false, false, true,  true)
 			{ @Override public boolean mapFlags(boolean hasIn, boolean hasOn, boolean hasOut) {return !hasOut;} },
-//		POLYGON_ANY("any",                FeatureKind.POLYGON,  true,  false, false, false)
-// problem with test b14 on the cut polygons and isLineInShape that goes away when merged. TODO: investigate sometime
-		POLYGON_ANY("any",                FeatureKind.POLYGON,  true,  false, false, true)
+		POLYGON_ANY("any",                FeatureKind.POLYGON,  true,  false, false, false)
 			{ @Override public boolean mapFlags(boolean hasIn, boolean hasOn, boolean hasOut) {return hasIn || !hasOut;} };
 
 /* thoughts for ON methods for polyons and the hasOn flag
@@ -246,9 +244,12 @@
 	private static boolean notInHole(Coord c, List<List<Coord>> holes) {
 		if (holes == null)
 			return true;
-		for (List<Coord> hole : holes)
-			if (IsInUtil.insidePolygon(c, true, hole))
+		for (List<Coord> hole : holes) {
+			int flags = IsInUtil.isPointInShape(c, hole);
+			log.debug("notInHole", flags);
+			if (flags != IsInUtil.OUT)
 				return false;
+		}
 		return true;
 	}
 
@@ -258,9 +259,11 @@
 		checked all the polygons and haven't satisfied IN/ON, so no point is calling setOut()
 		and it wouldn't stop the processing or effect the answer anyway
 		*/
-		switch (method) { // Use the method to control the onBoundary condition of insidePolygon.
+		int flags = IsInUtil.isPointInShape(c, shape);
+		log.debug("checkPoint", flags);
+		switch (method) {
 		case POINT_IN:
-			if (IsInUtil.insidePolygon(c, false, shape))
+			if (flags == IsInUtil.IN)
 				if (notInHole(c, holes))
 					setIn();
 				else // in hole in this shape, no point in looking at more shapes
@@ -267,18 +270,17 @@
 					throw new CanStopProcessing();
 			break;
 		case POINT_IN_OR_ON:
-			if (IsInUtil.insidePolygon(c, true, shape))
+			if (flags != IsInUtil.OUT)
 				// no need to check holes for this as didn't need to merge polygons
 				setIn(); // don't care about setOn()
 			break;
 		case POINT_ON:
-			if (IsInUtil.insidePolygon(c, true, shape) &&
-			    !IsInUtil.insidePolygon(c, false, shape))
+			if (flags == IsInUtil.ON)
 				// hole checking is a separate pass
 				setOn(); // don't care about setIn()
 			break;
 		default:
-			throw new ExitException("Bbad point method: " + method);
+			throw new ExitException("Bad point method: " + method);
 		}
 	}
 
Index: src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java	(revision 4460)
+++ src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryUtil.java	(working copy)
@@ -837,8 +837,11 @@
     	List<Coord> polygonNodes = Java2DConverter.singularAreaToPoints(area);
     	if (polygonNodes == null)
     		return false;
-
-    	return IsInUtil.insidePolygon(point, onBoundary, polygonNodes);
+	int status = IsInUtil.isPointInShape(point, polygonNodes);
+	if (status == IsInUtil.ON)
+		return onBoundary;
+	else
+		return status == IsInUtil.IN;
     }
     
 }
Index: src/uk/me/parabola/util/IsInUtil.java
===================================================================
--- src/uk/me/parabola/util/IsInUtil.java	(revision 4460)
+++ src/uk/me/parabola/util/IsInUtil.java	(working copy)
@@ -20,6 +20,8 @@
 import java.util.Set;
 import java.util.TreeMap;
 
+import uk.me.parabola.util.GpxCreator;
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
@@ -31,7 +33,7 @@
  * 1: the point is outside the polygon <br>
  * 2: the point is on the boundary of the polygon (or very close to it) <br>
  * 3: the point in inside the polygon
- * 
+ *
  * We distinguish 6 cases for lines: <br>
  * 1: all of the line is outside the polygon <br>
  * 2: some of the line is outside and the rest touches or runs along the polygon
@@ -41,11 +43,12 @@
  * 5: all of the line is inside the polygon <br>
  * 6: some is inside and some outside the polygon. Obviously some point is on
  * the polygon edge but we don't care if runs along the edge.
- * 
+ *
  * @author Gerd Petermann
  *
  */
 public class IsInUtil {
+	private static final Logger log = Logger.getLogger(IsInUtil.class);
 	public static final int IN = 0x01;
 	public static final int ON = 0x02;
 	public static final int OUT = 0x04;
@@ -57,7 +60,7 @@
 	}
 
 	public static void mergePolygons(Set<Way> polygons, List<List<Coord>> outers, List<List<Coord>> holes) {
-	// combine all polygons which intersect the bbox of the element if possible
+		// combine all polygons which intersect the bbox of the element if possible
 		Path2D.Double path = new Path2D.Double();
 		for (Way polygon : polygons) {
 			path.append(Java2DConverter.createPath2D(polygon.getPoints()), false);
@@ -75,18 +78,19 @@
 		TOUCHING, CROSSING, SPLITTING, JOINING,SIMILAR, DOUBLE_SPIKE
 	}
 	
-	
+	private static final int EPS_HP = 4; // ~0.15 meters at equator
+	private static final int EPS_HP_SQRD = EPS_HP * EPS_HP;
+	private static final double EPS = 0.15; // meters. needed for distToLineSegment()
+
 	public static int isLineInShape(List<Coord> lineToTest, List<Coord> shape, Area elementBbox) {
 		final int n = lineToTest.size();
 		int status = isPointInShape(lineToTest.get(0), shape);
 		BitSet onBoundary = new BitSet();
-		boolean statusFromFirst = true;
 		
 		for (int i = 0; i < shape.size() - 1; i++) {
 			Coord p11 = shape.get(i);
 			Coord p12 = shape.get(i + 1);
-			if (p11.highPrecEquals(p12)) {
-				// maybe we should even skip very short segments (< 0.01 m)?
+			if (p11.distanceInHighPrecSquared(p12) < EPS_HP_SQRD) { // skip very short segments
 				continue;
 			}
 			// check if shape segment is clearly below, above, right or left of bbox
@@ -98,23 +102,19 @@
 			for (int k = 0; k < n - 1; k++) {
 				Coord p21 = lineToTest.get(k);
 				Coord p22 = lineToTest.get(k + 1);
-				if (p21.highPrecEquals(p22)) {
-					// maybe we should even skip very short segments (< 0.01 m)?
+				if (p21.distanceInHighPrecSquared(p22) < EPS_HP_SQRD) { // skip very short segments
 					continue;
 				}
 				Coord inter = Utils.getSegmentSegmentIntersection(p11, p12, p21, p22);
 				if (inter != null) {
-					// segments have at least one common point 
+					// segments have at least one common point
 					boolean isCrossing = false;
-					if (inter.distance(p21) < 0.01) {
+					if (inter.distanceInHighPrecSquared(p21) < EPS_HP_SQRD) {
 						onBoundary.set(k);
 						if (k == 0) {
-							// first segment of line and first point on boundary
-							if (status != ON && statusFromFirst) {
-								status = ON;
-							}
+							status |= ON;
 						} else {
-							if (p21.highPrecEquals(p11)) {
+							if (p21.distanceInHighPrecSquared(p11) < EPS_HP_SQRD) {
 								Coord p20 = lineToTest.get(k - 1);
 								Coord p10 = shape.get(i - 1 >= 0 ? i - 1 : shape.size() - 2);
 								IntersectionStatus x = analyseCrossingInPoint(p11, p20, p22, p10, p12);
@@ -131,17 +131,13 @@
 									}
 								}
 								if (pTest != null) {
-									// it is unlikely but not impossible that pTest is on boundary :(
 									int testStat = isPointInShape(pTest, shape);
-									if (status == ON) {
-										status = testStat;
-										statusFromFirst = false;
-									} else if (status != testStat) {
-										return IN_ON_OUT; 
-									}
+									status |= testStat;
+									if ((status|ON) == IN_ON_OUT)
+									    return IN_ON_OUT;
 								}
-							} else if (p21.highPrecEquals(p12)) {
-								// handled in next iteration (k+1) or (i+1) 
+							} else if (p21.distanceInHighPrecSquared(p12) < EPS_HP_SQRD) {
+								// handled in next iteration (k+1) or (i+1)b
 							} else {
 								// way segment starts on a shape segment
 								// somewhere between p11 and p12
@@ -153,13 +149,17 @@
 								if (isLeftPrev< 0 && isLeftNext > 0 || isLeftPrev > 0 && isLeftNext < 0) {
 									// both way segments are not on the shape
 									// segment and they are on different sides
-									isCrossing = true;  
+									isCrossing = true;
 								}
 							}
 						}
-					} else if (inter.distance(p22) < 0.01) {
+					} else if (inter.distanceInHighPrecSquared(p22) < EPS_HP_SQRD) {
 						onBoundary.set(k + 1);
 						// handle intersection on next iteration
+					} else if (inter.distanceInHighPrecSquared(p11) < EPS_HP_SQRD || inter.distanceInHighPrecSquared(p12) < EPS_HP_SQRD) {
+						// intersection is very close to end of shape segment
+						if (inter.distToLineSegment(p21, p22) > EPS)
+							isCrossing = true;
 					} else {
 						isCrossing = true;
 					}
@@ -178,7 +178,7 @@
 			if (onBoundary.cardinality() != n) {
 				// return result for first point which is not on boundary
 				Coord pTest = lineToTest.get(onBoundary.nextClearBit(0));
-				status |= insidePolygon(pTest, false, shape) ? IN : OUT;
+				status |= isPointInShape(pTest, shape);
 				return status;
 			}
 			status |= checkAllOn(lineToTest, shape);
@@ -187,7 +187,7 @@
 	}
 
 
-	/** 
+	/**
 	 * Handle special case that all points of {@code lineToTest} are on the edge of shape
 	 * @param lineToTest
 	 * @param shape
@@ -199,7 +199,6 @@
 		for (int i = 0; i < n-1; i++) {
 			Coord p1 = lineToTest.get(i);
 			Coord p2 = lineToTest.get(i + 1);
-			// TODO: may not work with b14 (element is inner ring in mp)
 			if (!isOnOrCloseToEdgeOfShape(shape, p1, p2)) {
 				Coord pTest = p1.makeBetweenPoint(p2, 0.01);
 				int resMidPoint = isPointInShape(pTest, shape);
@@ -212,16 +211,16 @@
 
 	/**
 	 * two line-strings a-s-c and x-s-y the same mid point. Check if they are crossing. This is the case
-	 * if a-s-c is between x-s-y or if x-s-y is between a-s-c. 
+	 * if a-s-c is between x-s-y or if x-s-y is between a-s-c.
 	 * @param s the share point
 	 * @param a 1st point 1st line-string
-	 * @param b 2nd point 1st line-string 
+	 * @param b 2nd point 1st line-string
 	 * @param x 1st point 2nd line-string
 	 * @param y 2nd point 2nd line-string
 	 * @return kind of crossing or touching
 	 */
 	private static IntersectionStatus analyseCrossingInPoint(Coord s, Coord a, Coord b, Coord x, Coord y) {
-		//TODO: Can we do this sorting without the costly bearing calculations? 
+		//TODO: Can we do this sorting without the costly bearing calculations?
 		TreeMap<Long, Character> map = new TreeMap<>();
 		long ba = Math.round(s.bearingTo(a) * 1000);
 		long bb = Math.round(s.bearingTo(b) * 1000);
@@ -242,7 +241,7 @@
 				// pair xy is either on 0 and 2 or 1 and 3, so only one of a and b is between them
 				// shape segments x-s-y is nether between nor outside of way segments a-s-b
 				return IntersectionStatus.CROSSING;
-			} 
+			}
 			return IntersectionStatus.TOUCHING;
 		}
 
@@ -249,7 +248,7 @@
 		if (map.size() == 3) {
 			if (xpos < 0) {
 				// x-s-y is a spike that touches a-s-b
-				return IntersectionStatus.TOUCHING; 
+				return IntersectionStatus.TOUCHING;
 			}
 			if (bpos < 0) {
 				// either s-x or s-y is overlaps s-b
@@ -265,17 +264,17 @@
 				// two spikes meeting
 				return IntersectionStatus.TOUCHING;
 			}
-			// a-s-b and x-s-y are overlapping (maybe have different directions) 
+			// a-s-b and x-s-y are overlapping (maybe have different directions)
 			return IntersectionStatus.SIMILAR;
 		}
-		// both a-s-b and x-s-y come from and go to the same direction 
-		return IntersectionStatus.DOUBLE_SPIKE; 
+		// both a-s-b and x-s-y come from and go to the same direction
+		return IntersectionStatus.DOUBLE_SPIKE;
 	}
 
 	/**
 	 * Check if the sequence p1-p2 or p2-p1 appears in the shape or if there is only one point c between and the sequence p1-c-p2
-	 * is nearly straight.       
-	 * @param shape list of points describing the shape 
+	 * is nearly straight.
+	 * @param shape list of points describing the shape
 	 * @param p1 first point
 	 * @param p2 second point
 	 * @return true if the sequence p1-p2 or p2-p1 appears in the shape or if there is only one point c between and the sequence p1-c-p2
@@ -284,21 +283,21 @@
 	private static boolean isOnOrCloseToEdgeOfShape(List<Coord> shape, Coord p1, Coord p2) {
 		for (int i  = 0; i < shape.size(); i++) {
 			Coord p = shape.get(i);
-			if (!p.highPrecEquals(p1)) 
+			if (p.distanceInHighPrecSquared(p1) >= EPS_HP_SQRD)
 				continue;
 			
 			int posPrev = i > 0 ? i - 1 : shape.size() - 2;
 			int posNext = i < shape.size() - 1 ? i + 1 : 1;
-			if (shape.get(posPrev).highPrecEquals(p2) || shape.get(posNext).highPrecEquals(p2))
+			if (shape.get(posPrev).distanceInHighPrecSquared(p2) < EPS_HP_SQRD || shape.get(posNext).distanceInHighPrecSquared(p2) < EPS_HP_SQRD)
 				return true;
 
 			int posPrev2 = posPrev > 0 ? posPrev - 1 : shape.size() - 2;
 			int posNext2 = posNext < shape.size() - 1 ? posNext + 1 : 1;
-			if (shape.get(posPrev2).highPrecEquals(p2) && Math.abs(Utils.getAngle(p1, shape.get(posPrev), p2)) < 0.1) {
+			if (shape.get(posPrev2).distanceInHighPrecSquared(p2) < EPS_HP_SQRD && Math.abs(Utils.getAngle(p1, shape.get(posPrev), p2)) < 0.1) {
 				// shape segments between p1 and p2 are almost straight
 				return true;
 			}
-			if (shape.get(posNext2).highPrecEquals(p2) && Math.abs(Utils.getAngle(p1, shape.get(posNext), p2)) < 0.1) {
+			if (shape.get(posNext2).distanceInHighPrecSquared(p2) < EPS_HP_SQRD && Math.abs(Utils.getAngle(p1, shape.get(posNext), p2)) < 0.1) {
 				// shape segments between p1 and p2 are almost straight
 				return true;
 			}
@@ -307,67 +306,98 @@
 		return false;
 	}
 
-	public static int isPointInShape(Coord p, List<Coord> shape) {
-		boolean inOrOn = insidePolygon(p, true, shape);
-		if (inOrOn) {
-			// point is in or on
-			boolean in = insidePolygon(p, false, shape);
-			return in ? IN : ON;
+	/**
+	 * Check if node is in polygon using crossing number counter, with some some tolerance
+	 * @param node the point to test
+	 * @param shape list of points describing the polygon
+	 * @return IN/ON/OUT
+	 */
+	public static int isPointInShape(Coord node, List<Coord> shape) {
+			final int nodeLat = node.getHighPrecLat();
+		final int nodeLon = node.getHighPrecLon();
+		if (log.isDebugEnabled()) {
+			log.debug("node ", node, nodeLon, nodeLat, shape.size(), shape);
+			/*
+			String baseName = GpxCreator.getGpxBaseName();
+			//GpxCreator.createAreaGpx(baseName + "bbox", getTileBounds());
+			List<Coord> nodeAsList = new ArrayList<>();
+			nodeAsList.add(node);
+			GpxCreator.createGpx(baseName + "_node_", nodeAsList);
+			GpxCreator.createGpx(baseName + "_shape_", shape);
+			*/
 		}
-		return OUT;
-	}
-	
-    /**
-     * Check if node is in polygon using winding counter. 
-     * Based on code from Dan Sunday, but allows to define how to handle nodes on boundary.   
-     * @param p the point to test
-     * @param onBoundary the value that should be returned if the point is on the boundary
-     * @param shape list of points describing the polygon
-     * @return true if p is inside the polygon, false if outside, the value of onBoundary if its on the boundary
-     * <p>
-     * Copyright 2000 softSurfer, 2012 Dan Sunday
-     * This code may be freely used and modified for any purpose
-     * providing that this copyright notice is included with it.
-     * SoftSurfer makes no warranty for this code, and cannot be held
-     * liable for any real or imagined damage resulting from its use.
-     * Users of this code must verify correctness for their application.     
-     * See http://geomalgorithms.com/a03-_inclusion.html
-     */
-    public static final boolean insidePolygon (final Coord p, boolean onBoundary, List<Coord> shape) {
-        final int y = p.getHighPrecLat();
-        final int len = shape.size();
-        // the winding number counter
-        int wn = 0;
-        Coord c = shape.get(0), n; // current & next points from vertex array
-       
-		for (int i = 0; ++i < len; c = n) {
-			n = shape.get(i);
-			if (p.highPrecEquals(n)) {
-				return onBoundary;
-			}
-			if (c.getHighPrecLat() <= y) { // starting y <= p.y
-				// an upward crossing and p left of edge:
-				if (n.getHighPrecLat() > y) {
-					long l = p.isLeft(c, n);
-					if (l > 0) {
-						++wn;
-					} else if (l == 0) {
-						return onBoundary; 
+		int trailLat = 0, trailLon = 0;
+		int lhsCount = 0, rhsCount = 0; // count both, to be sure
+		int minLat, maxLat, minLon, maxLon;
+		double lonDif, latDif, distSqrd;
+		boolean subsequent = false;
+		for (Coord leadCoord : shape) {
+			final int leadLat = leadCoord.getHighPrecLat();
+			final int leadLon = leadCoord.getHighPrecLon();
+			if (subsequent) { // use first point as trailing (poly is closed)
+				if (leadCoord.distanceInHighPrecSquared(node) < EPS_HP_SQRD)
+					return ON;
+				if (leadLat < trailLat) {
+					minLat = leadLat;
+					maxLat = trailLat;
+				} else {
+					minLat = trailLat;
+					maxLat = leadLat;
+				}
+				if (leadLon < trailLon) {
+					minLon = leadLon;
+					maxLon = trailLon;
+				} else {
+					minLon = trailLon;
+					maxLon = leadLon;
+				}
+				if (minLat - EPS_HP > nodeLat)
+					; // line segment is all slightly above, ignore
+				else if (maxLat + EPS_HP < nodeLat)
+					; // line segment is all slightly below, ignore
+				else if (minLon - EPS_HP > nodeLon && minLat < nodeLat && maxLat > nodeLat)
+					++rhsCount; // definite line segment all slightly to the right
+				else if (maxLon + EPS_HP < nodeLon && minLat < nodeLat && maxLat > nodeLat)
+					++lhsCount; // definite line segment all slightly to the left
+				else { // need to consider this segment more carefully.
+					if (leadLat == trailLat)
+						lonDif = 0; // dif meaningless; will be ignored in crossing calc, 0 handled for distToLine calc
+					else
+						lonDif = nodeLon - trailLon - (double)(nodeLat - trailLat) / (leadLat - trailLat) * (leadLon - trailLon);
+					if (leadLon == trailLon)
+						latDif = 0; // ditto
+					else
+						latDif = nodeLat - trailLat - (double)(nodeLon - trailLon) / (leadLon - trailLon) * (leadLat - trailLat);
+					// calculate distance to segment using right-angle attitude theorem
+					final double lonDifSqrd = lonDif*lonDif;
+					final double latDifSqrd = latDif*latDif;
+					log.debug("inBox", leadLon-nodeLon, leadLat-nodeLat, trailLon-nodeLon, trailLat-nodeLat, lonDif, latDif, lhsCount, rhsCount);
+					// there a small area between the square EPS_HP*2 and the circle within, where, if polygon vertix and
+					// segments are the other side, it might still be calculated as ON.
+					if (lonDif == 0)
+						distSqrd = latDifSqrd;
+					else if (latDif == 0)
+						distSqrd = lonDifSqrd;
+					else
+						distSqrd = lonDifSqrd * latDifSqrd / (lonDifSqrd + latDifSqrd);
+					if (distSqrd < EPS_HP_SQRD)
+						return ON;
+					if ((trailLat <= nodeLat && leadLat >  nodeLat) || //  an upward crossing
+					    (trailLat >  nodeLat && leadLat <= nodeLat)) { // a downward crossing
+						if (lonDif < 0)
+							++rhsCount; // a valid crossing right of nodeLon
+						else
+							++lhsCount;
 					}
 				}
+			} // if not first Coord
+			subsequent = true;
+			trailLat = leadLat;
+			trailLon = leadLon;
+		} // for leadCoord
+		log.debug("lhs | rhs", lhsCount, rhsCount);
+		assert (lhsCount & 1) == (rhsCount & 1) : "LHS: " + lhsCount + " RHS: " + rhsCount;
+		return (rhsCount & 1) == 1 ? IN : OUT;
+	}
 
-			} else if (n.getHighPrecLat() <= y) {
-				// a downward crossing and p right of edge.
-				long l = p.isLeft(c, n);
-				if (l < 0) {
-					--wn;
-				} else if (l == 0) {
-					return onBoundary; 
-				}
-			}
-		}
-       
-        return wn != 0; // if 0 point is outside
-    }
-	
 }
Index: test/uk/me/parabola/imgfmt/app/CoordTest.java
===================================================================
--- test/uk/me/parabola/imgfmt/app/CoordTest.java	(revision 4460)
+++ test/uk/me/parabola/imgfmt/app/CoordTest.java	(working copy)
@@ -87,10 +87,10 @@
 	}
 	
 	@Test 
-	public void destOnRhumLineAt180(){
-		Coord russia3 = russia1.destOnRhumLine(1, 0.0);
+	public void destOnRhumbLineAt180(){
+		Coord russia3 = russia1.destOnRhumbLine(1, 0.0);
 		assertEquals(russia3.getLongitude(), russia1.getLongitude());
-		Coord russia4 = russia1.destOnRhumLine(10000, 0.0);
+		Coord russia4 = russia1.destOnRhumbLine(10000, 0.0);
 		assertEquals(russia4.getLongitude(), russia1.getLongitude());
 	}
 	
_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to