Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace our polygon code with the JTS implementation #1853

Merged
merged 13 commits into from
Jan 28, 2020
Merged

Conversation

karussell
Copy link
Member

@karussell karussell commented Jan 17, 2020

I would like to have this before #1838.

This PR does the following:

  • avoids our custom Polygon code and uses the JTS implementation which should make the implementation more robust and make it working for more generic polygons like with inner rings
  • it additionally improves the precision of the block_area feature through a new Shape.intersects(PointList) method. Now not just a single point of an edge is checked for intersection but the whole point list. Still there is more work (later) Reimplement block_area with LocationIndexTree instead of graph traversal #1324.
  • I discovered PackedCoordinateSequence that allows us to be memory efficient with JTS too
  • I've introduced QuerySettings. Not sure if this should be done here, but the parameters for the printTimeOfRouteQuery method got more and more confusing. E.g. recent changes like f69f127#diff-cd749cc545f8b84137c2b75b2d174b72 are much easier to understand.

@@ -399,7 +404,10 @@ public static PointList fromLineString(LineString lineString) {
}

public LineString toLineString(boolean includeElevation) {
GeometryFactory gf = new GeometryFactory();
return toLineString(new GeometryFactory(), includeElevation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to create a factory for 2D and 3D each and keep them?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have done this (one static factory). Or why would we want 2?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GeometryFactory FAC_2D = new GeometryFactory(new PackedCoordinateSequenceFactory(DOUBLE, 2));
GeometryFactory FAC_3D = new GeometryFactory(new PackedCoordinateSequenceFactory(DOUBLE, 3));

includeElevation would just toggle which one of the two is being passed to the function. No need to create new objects here.

Copy link
Member Author

@karussell karussell Jan 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have adapted the current code (via a single factory). Or will there be something different with your proposed change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop all new PackedCoordinateSequence(...) calls:

2D_FAC.createPolygon(coordinates);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmh, maybe you mean something else? There is just one call and it is:

return factory.createLineString(new PackedCoordinateSequence.Double(coordinates, includeElevation ? 3 : 2));

api/src/main/java/com/graphhopper/util/shapes/Polygon.java Outdated Show resolved Hide resolved
api/src/main/java/com/graphhopper/util/shapes/Polygon.java Outdated Show resolved Hide resolved
Copy link
Member

@michaz michaz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, nice that we're taking this on.

Ultimately (long term, new code), I would recommend we stop wrapping the JTS classes, and migrate to them. The appeal is that it's not only a library, but a language -- intersects, contains etc. have precise definitions that can be googled. If it's possible to not hide them away behind wrappers, that clarity would propagate to the code where these data types are used. (One wouldn't have to ask oneself if our wrapper uses the word slightly differently.)

api/src/main/java/com/graphhopper/util/shapes/BBox.java Outdated Show resolved Hide resolved
// If they are not collinear, they must intersect in exactly one point.
return true;
// for estimation use bounding box as reference:
return getBounds().calculateArea() * envelope.getArea() / polygon.getGeometry().getArea();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could just remove this method, and its definition from Shape? It isn't used generically anywhere in the code, so we could easily avoid giving an "approximate" or fudged implementation here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it is used in GraphEdgeIdFinder.parseBlockArea

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, wasn't used before (seems like a bug), but is now :)
I.e. for smaller polygons edgeIds should be used and for bigger polygons we expect that the edgeId lookup is slower than calling the polygon.intersect for every edge of the traversed graph.

@karussell
Copy link
Member Author

Ultimately (long term, new code), I would recommend we stop wrapping the JTS classes, and migrate to them.

👍
Yes, I tried that here, but this is a major undertaking as we would not only remove Circle (not available in JTS) but migrate BBox and GHPoint etc. The big problems were for PointList, as it is highly tuned for our needs especially regarding memory usage when simplifying.

@karussell
Copy link
Member Author

@easbar can you have a short look into my change in Measurement (regarding QuerySetting). Do you agree roughly :) ? Should I do this in a separate PR?

@easbar
Copy link
Member

easbar commented Jan 28, 2020

@easbar can you have a short look into my change in Measurement (regarding QuerySetting). Do you agree roughly :) ? Should I do this in a separate PR?

Yes putting all these booleans in a config object is a good idea imo. Right now the biggest advantage is that we only have to specify the non-default values (all the booleans that should be true instead of false)?

@karussell
Copy link
Member Author

Right now the biggest advantage is that we only have to specify the non-default values

Yes (and you can better see what changes to the "default")

@karussell karussell merged commit 7f0a357 into master Jan 28, 2020
@karussell karussell added this to the 1.0 milestone Jan 28, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants