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