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