Hi Gerd

Here is patch with:
- IsInUtilTest driver using the is_in function with methods.
- a slight re-formulation to the 'any_in_or_in' method.
- updates to style manual for above.
- some fixes and improvements relating to holes, ON etc.

There is one more bit of logic that is needed for polygons: after the
polygon has been found to be fully within a shape, and it isn't in a
hole, check that a hole isn't within it. I'll so this in the next day
or so.

I'm having trouble with b14 (building is inner to a multi-polygon):
Without merging the cut outer, isLineInShape for the b14 polygon
returns IN/ON/OUT for the first component - I was expecting ON/OUT.
With merging, it returns IN for the merged shape (good), but IN/ON for
the hole - I was expecting ON.

Ticker

On Wed, 2020-02-12 at 15:55 +0000, Gerd Petermann wrote:
> Hi Ticker,
> 
> I found it difficult to test the real style function, that's why I
> moved all the logic out of it.
> I am looking forward to your solution.
> 
> Gerd
> 
> ________________________________________
> Von: mkgmap-dev <[email protected]> im Auftrag
> von Ticker Berkin <[email protected]>
> Gesendet: Mittwoch, 12. Februar 2020 16:47
> An: Development list for mkgmap
> Betreff: Re: [mkgmap-dev] Work on is_in branch
> 
> Hi Gerd
> 
> The re-structuring makes this difficult.
> 
> I propose a function in IsInUtilTest with the same interface as
> calcInsideness from IsInUtil that somehow drives the real function
> IsIn
> Function to collect and build the IN/ON/OUT intflag.
> 
> Ticker
> 
> On Wed, 2020-02-12 at 15:28 +0000, Gerd Petermann wrote:
> > Hi Ticker,
> > 
> > did you run the unit tests? This should download a newer version of
> > the samples.
> > 
> > Gerd
> > 
> > ________________________________________
> > Von: mkgmap-dev <[email protected]> im Auftrag
> > von Ticker Berkin <[email protected]>
> > Gesendet: Mittwoch, 12. Februar 2020 16:23
> > An: Development list for mkgmap
> > Betreff: Re: [mkgmap-dev] Work on is_in branch
> > 
> > Hi Gerd
> > 
> > Here it is - changes are:
> > 
> > - Some restructuring with early stopping where possible.
> > 
> > - Merging polygons for POINT IN/ON test so a point on shared
> > boundary
> > becomes IN rather than ON.
> > 
> > - Not merging polygons when no need.
> > 
> > - Make the function cacheable, so that there is negligible cost to
> > the
> > second call:
> > highway=path & is_in(landuse, residential, all)=true [0xAA]
> > highway=path & is_in(landuse, residential, all)=false [0xBB]
> > 
> > - Improved the layout of documentation in the Style Manual.
> > 
> > - Fixed quite a few problems.
> > 
> > I've left quite a lot of debug in for the moment, I think there
> > will
> > still be work to do.
> > 
> > It gives correct answers to 'point-on.osm'. I haven't worked
> > through
> > is
> > -in-hook-sample-v3.osm yet because I wanted to get this revision
> > out
> > to
> > replace faults in the previous versions.
> > 
> > Ticker
> > 
> > On Tue, 2020-02-11 at 15:49 +0000, Gerd Petermann wrote:
> > > Hi Ticker,
> > > 
> > > whatever you plan to do. I moved the code to the lib because it
> > > is
> > > easier to write a unit test.
> > > I would not invest much time to avoid a few tests which only
> > > happen
> > > in very rare cases. Makes testing more complicated and code less
> > > readable.
> > > 
> > > Gerd
> _______________________________________________
> mkgmap-dev mailing list
> [email protected]
> http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
Index: doc/styles/rules.txt
===================================================================
--- doc/styles/rules.txt	(revision 4453)
+++ doc/styles/rules.txt	(working copy)
@@ -286,19 +286,19 @@
 +true+ if the element is in polygon(s) having the specified +tag+=+value+ according to the +method+, +false+ otherwise.
 The methods available depend on the Style section:
 
-* points:
+. points:
  +in+ - the Node is within a polygon.
  +in_or_on+ - it is within or on the edge.
  +on+ - it is on the edge.
 
-* lines:
+. lines:
  +all+ - part of the Way is within the polygon(s), none is outside; it might touch an edge.
- +all_in_or_on+ - none is outside. This is useful for the negative - is_in(...,all_in_or_on)=false - for processing a line that is outside the polgon(s).
+ +all_in_or_on+ - none is outside.
  +on+ - it runs along the edge.
  +any+ - part is within.
- +any_in_or_on+ - part is within or in the edge.
+ +any_in_or_on+ - part is within or all is on the edge. This is useful for the negative - is_in(...,any_in_or_on)=false - for processing a line that is outside the polgon(s) but might touch an edge.
 
-* polygons:
+. polygons:
  +all+ - all of the closed Way is within the polygon(s).
  +any+ - some is within.
 
Index: src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java	(revision 4453)
+++ src/uk/me/parabola/mkgmap/osmstyle/function/IsInFunction.java	(working copy)
@@ -52,7 +52,7 @@
 		LINE_ALL_IN_OR_ON("all_in_or_on", FeatureKind.POLYLINE, false, false, true),
 		LINE_ALL_ON("on",                 FeatureKind.POLYLINE, true,  false, true),
 		LINE_ANY_IN("any",                FeatureKind.POLYLINE, true,  false, false),
-		LINE_ANY_IN_OR_ON("any_in_or_on", FeatureKind.POLYLINE, true,  true,  false),
+		LINE_ANY_IN_OR_ON("any_in_or_on", FeatureKind.POLYLINE, true,  false, false),
 
 		POLYGON_ALL("all",                FeatureKind.POLYGON,  false, false, true),
 		POLYGON_ANY("any",                FeatureKind.POLYGON,  true,  false, false);
@@ -169,7 +169,7 @@
 	private void setIn() {
 		log.info("setIn", hasIn, hasOn, hasOut);
 		hasIn = true;
-		if (method.canStopIn() || (hasOn && hasOut))
+		if (method.canStopIn() || hasOut)
 			throw new CanStopProcessing();
 	}
 
@@ -182,20 +182,23 @@
 	private void setOut() {
 		log.info("setOut", hasIn, hasOn, hasOut);
 		hasOut = true;
-		if (method.canStopOut() || (hasIn && hasOn))
+		if (method.canStopOut() || hasIn)
 			throw new CanStopProcessing();
 	}
 
 	private void setHasFromFlags(int flags) {
+		log.info("setFlags", flags);
+		if ((flags & IsInUtil.ON) != 0)
+			setOn();
 		if ((flags & IsInUtil.IN) != 0)
 			setIn();
-		if ((flags & IsInUtil.ON) != 0)
-			setOn();
 		if ((flags & IsInUtil.OUT) != 0)
 			setOut();
 	}
 
 	private boolean mapHasFlagsAnswer() {
+		if (!hasIn && !hasOn)
+			hasOut = true;
 		switch (method) {
 		case POINT_IN:
 			return hasIn;
@@ -212,7 +215,7 @@
 		case LINE_ANY_IN:
 			return hasIn;
 		case LINE_ANY_IN_OR_ON:
-			return hasIn || hasOn;
+			return hasIn || !(hasIn || hasOut);
 		case POLYGON_ALL:
 			return !hasOut;
 		case POLYGON_ANY:
@@ -290,9 +293,11 @@
 		doCommonTest(el);
 	}
 
-	private void checkHoles(List<Coord> polyLine, List<List<Coord>> holes, Area elementBbox) {
+	private boolean checkHoles(List<Coord> polyLine, List<List<Coord>> holes, Area elementBbox) {
+		boolean foundSomething = false;
 		for (List<Coord> hole : holes) {
 			int flags = IsInUtil.isLineInShape(kind, polyLine, hole, elementBbox);
+			log.info("checkhole", flags);
 			if ((flags & IsInUtil.IN) != 0) {
 				setOut();
 				if ((flags & IsInUtil.ON) != 0)
@@ -299,21 +304,21 @@
 					setOn();
 				if ((flags & IsInUtil.OUT) != 0)
 					setIn();
-				return;
+				return true;
+			} else if ((flags & IsInUtil.ON) != 0) {
+				setOn();
+				if ((flags & IsInUtil.OUT) != 0)
+					setIn();
+				foundSomething = true;
 			}
 		}
+		return foundSomething;
 	}
     /*
-make above function return boolean depending on it finding something.
-maybe should also respond to ON
-
-in below, delay setting the IN flag until have done the above, because the line could all be ON the inner.
-and use return from above to set IN, assume that when it finds something, it will set IN of OUT etc
-
 for POLY, still need a final check that, even if ON/IN, none of the holes is in this one, so
 we need another check (can we use above not-in-hole...) that takes a point from the hole and
 checks if it is the polyLine
-     */
+    */
 	private void doCommonTest(Element el) {
 		List<Coord> polyLine = ((Way)el).getPoints();
 		Area elementBbox = Area.getBBox(polyLine);
@@ -330,12 +335,19 @@
 			log.info("polyMerge", polygons.size(), outers.size(), holes.size());
 			for (List<Coord> shape : outers) {
 				int flags = IsInUtil.isLineInShape(kind, polyLine, shape, elementBbox);
-				if ((flags & (IsInUtil.IN | IsInUtil.ON)) != 0) {
-				    	// this shape is the one to consider
-					setHasFromFlags(flags); // might set OUT and stop
-					if ((flags & IsInUtil.IN) != 0)
-						checkHoles(polyLine, holes, elementBbox);
+				log.info("checkShape", flags);
+				if ((flags & IsInUtil.IN) != 0) { // this shape is the one to consider
+					if ((flags & IsInUtil.ON) != 0)
+						setOn();
+					if ((flags & IsInUtil.OUT) != 0)
+						setOut();
+					if (!checkHoles(polyLine, holes, elementBbox))
+						setIn();
 					break;
+				} else if ((flags & IsInUtil.ON) != 0) { // might still be IN later one
+					setOn();
+					if ((flags & IsInUtil.OUT) != 0)
+						setOut();
 				}
 			}
 		} else { // an ANY-like method
Index: test/uk/me/parabola/util/IsInUtilTest.java
===================================================================
--- test/uk/me/parabola/util/IsInUtilTest.java	(revision 4453)
+++ test/uk/me/parabola/util/IsInUtilTest.java	(working copy)
@@ -41,103 +41,74 @@
 import uk.me.parabola.mkgmap.reader.osm.OsmMapDataSource;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 
-import uk.me.parabola.mkgmap.osmstyle.function.IsInFunction;
+/*
+Source: test/resources/in/osm/is-in-samples.osm
+errors: test-reports/uk/me/parabola/util/62_IsInUtilTest-err.html
 
+b14 failed, expected: 4 got 5
+*/
+
 public class IsInUtilTest {
 
-	private static int calcInsideness(FeatureKind kind, Element el, Set<Way> polygons) {
-		int result = 0;  
-		if (polygons == null || polygons.isEmpty()) { 
-			return IsInUtil.OUT;
-		}
-		
-		Area elementBbox;
-		if (el instanceof Node) {
-			Coord c = ((Node) el).getLocation();
-			for (Way polygon : polygons) {
-				switch (IsInUtil.isPointInShape(c, polygon.getPoints())) {
-				case IsInUtil.IN:
-					return IsInUtil.IN;
-				case IsInUtil.ON:
-					result |= IsInUtil.ON;
-				default:
-				}
-			}
-			return result == 0 ? IsInUtil.OUT : IsInUtil.ON;
-		} else if (el instanceof Way) {
-			Way w = (Way) el;
-			if (w.isComplete()) {
-				elementBbox = Area.getBBox(w.getPoints());
-				if (polygons.size() > 1) {
-					// 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);
-					}
-					java.awt.geom.Area polygonsArea = new java.awt.geom.Area(path);
-					List<List<Coord>> mergedShapes = Java2DConverter.areaToShapes(polygonsArea);
+	Area testSourceBbox = null;
 
-					// combination of polygons may contain holes. They are counter clockwise.
-					List<List<Coord>> holes = new ArrayList<>();
-					List<List<Coord>> outers = new ArrayList<>();
-					for (List<Coord> shape : mergedShapes) {
-						(Way.clockwise(shape) ? outers : holes).add(shape);
-					}
-					
-					// check if any outer intersects with the way
-					for (List<Coord> shape : outers) {
-						int tmpRes = IsInUtil.isLineInShape(kind, w.getPoints(), shape, elementBbox);
-						if (tmpRes != IsInUtil.OUT) {
-							result |= tmpRes;
-							if ((tmpRes & IsInUtil.IN) != 0) {
-								result = tmpRes;
-								break;
-							}
-						}
-					}
-					if ((result & IsInUtil.IN) != 0 && !holes.isEmpty()) {
-						// an outer ring matched
-						// check if any hole intersects with the way
-						for (List<Coord> hole : holes) {
-							int tmpRes = IsInUtil.isLineInShape(kind, w.getPoints(), hole, elementBbox);
-							if (tmpRes == IsInUtil.IN_ON_OUT)
-								return tmpRes;
-							if ((tmpRes & IsInUtil.IN) != 0) 
-								result = IsInUtil.OUT;
-							result |= tmpRes & IsInUtil.ON;
-							
-						}
-					}
-					
-				} else if (polygons.size() == 1) {
-					result = IsInUtil.isLineInShape(kind, w.getPoints(), (polygons.iterator().next()).getPoints(), elementBbox);
-				}
-			}
-		}
-		if (result == 0)
-			result = IsInUtil.OUT;
-		return result;
+	private static boolean invokeMethod(IsInFunction anInst, String method, FeatureKind kind, Element el) {
+		anInst.setParams(Arrays.asList("landuse", "residential", method), kind); // tag key/value don't matter
+		String rslt = anInst.calcImpl(el);
+		return "true".equals(rslt);
 	}
 
-	private static int dev_calcInsideness(FeatureKind kind, Element el, Set<Way> polygons) {
-		int result = 0;  
-		if (polygons == null || polygons.isEmpty()) { 
+	private int calcInsideness(FeatureKind kind, Element el, Set<Way> polygons) {
+		int result = 0;
+		if (polygons == null || polygons.isEmpty()) {
 			return IsInUtil.OUT;
 		}
-		Area tileBbox = null; // ??? Area.getBBox(el); maybe
 		IsInFunction anInst = new IsInFunction();
-		// [javac] /norbert/svn/branches/is-in/test/uk/me/parabola/util/IsInUtilTest.java:125: error: incompatible types: Set<Way> cannot be converted to Collection<Element>
-		//fix anInst.unitTestAugment(new ElementQuadTree(tileBbox, polygons));
-		anInst.setParams(Arrays.asList("landuse", "residential", "all"), kind);
-		String rslt = anInst.calcImpl(el);
-		/* TODO: %%%
-choose 3 methods depending on Kind whose combination will give us IN/ON/OUT 
-test the rslt string for "True" and set the bits in result as appropriate
-		*/
+		List<Element> matchingPolygons = new ArrayList<>();
+		for (Way polygon : polygons)
+			matchingPolygons.add(polygon);
+		anInst.unitTestAugment(new ElementQuadTree(testSourceBbox, matchingPolygons));
+		switch (kind) {
+		case POINT:
+			if (invokeMethod(anInst, "in_or_on", kind, el))
+				if (invokeMethod(anInst, "in", kind, el))
+					result = IsInUtil.IN;
+				else
+					result = IsInUtil.ON;
+			else
+				result = IsInUtil.OUT;
+			break;
+		case POLYLINE:
+			/*			
+a IN        someInNoneOut allInOrOn       anyIn anyInOrOn (someInNoneOut==all, anyIn=any)
+b IN ON     someInNoneOut allInOrOn       anyIn anyInOrOn
+c IN ON OUT                               anyIn anyInOrOn
+d    ON                   allInOrOn allOn       anyInOrOn
+e    ON OUT
+f       OUT
+			*/
+			if (invokeMethod(anInst, "all", kind, el)) /*a,b*/
+				result = IsInUtil.IN | IsInUtil.ON; // methods won't say if also ON
+			else /*c,d,e,f*/ if (invokeMethod(anInst, "on", kind, el)) /*d*/
+				result = IsInUtil.ON;
+			else /*c,e,f*/ if (invokeMethod(anInst, "any", kind, el)) /*c*/
+				result = IsInUtil.IN | IsInUtil.ON | IsInUtil.OUT;
+			else /*e,f*/
+				result = IsInUtil.OUT | IsInUtil.ON; // methods won't say if also ON
+			break;
+		case POLYGON: // ON is meaningless for polygons
+			if (invokeMethod(anInst, "all", kind, el))
+				result = IsInUtil.IN;
+			else if (invokeMethod(anInst, "any", kind, el))
+				result = IsInUtil.IN | IsInUtil.OUT;
+			else
+				result = IsInUtil.OUT;
+			break;
+		}
 		return result;
 	}
- 
-	public static List<String> testWithVariants(FeatureKind kind, Element el, String name, Set<Way> polygons) {
+
+	public List<String> testWithVariants(FeatureKind kind, Element el, String name, Set<Way> polygons) {
 		List<String> errors = new ArrayList<>();
 		int res = calcInsideness(kind, el, polygons);
 		
@@ -144,6 +115,21 @@
 		String expectedVal = el.getTag("expected");
 		if (expectedVal != null && !"?".equals(expectedVal)) {
 			int expected = Integer.parseInt(expectedVal);
+
+/*
+Using the "method" interface to emulate the old version of IsInUtil.calcInsideness and try and deduce
+the IN/ON/OUT flags to compare with the 'expected' tag isn't quite possible:
+ For POLYGONs the ON flag is meaningless - it can be wholly or partially within
+ For LINEs, the methods don't distinguish between a line that is totally ON and one that is IN but touches the edge,
+  Similarly a line that is OUT and one that touches the edge.
+So here we adjust the expected value to match what can be tested.
+*/
+			if (kind == FeatureKind.POLYGON)
+				expected &= ~IsInUtil.ON;
+			else if (kind == FeatureKind.POLYLINE)
+				if (expected == IsInUtil.IN || expected == IsInUtil.OUT)
+					expected |= IsInUtil.ON;
+
 			if (expected != res) {
 				errors.add(name + " failed, expected: " + expected + " got "+ res);
 				return errors;
@@ -205,6 +191,7 @@
 		TestSource src = new TestSource();
 		src.config(new EnhancedProperties());
 		src.load(Args.TEST_RESOURCE_OSM + "is-in-samples.osm", false);
+		testSourceBbox = src.getElementSaver().getBoundingBox();
 		
 		ElementQuadTree qt = IsInFunction.buildTree(src.getElementSaver(), "landuse", "residential");
 		ArrayList<String> allErrors = new ArrayList<>();
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to