-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Render vector tiles from graph storage /mvt #1572
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
Merged
Merged
Changes from all commits
Commits
Show all changes
34 commits
Select commit
Hold shift + click to select a range
e5cff90
use 4 or 16 cells only for LocationIndexTree
karussell f0e40ef
make it possible to visualize boundaries of Quadtree and add few note…
karussell d2458ef
implement query(BBox), #1324
karussell 2c031d2
test and show how to properly user query(BBox)
karussell d8d6b9f
make it clear what we can expect from the Visitor functionality; adde…
karussell f68cd97
Merge branch 'master' into loc_index
karussell 4ccc7d5
use query(Shape) instead of query(BBox)
karussell 81de4f2
example log: avoid immediateFlush config as it is confusing
karussell de24f4a
make current maximum precision clear in javadoc
karussell 1621c48
show tiles via graph exploration
karussell 95e6555
merged branch loc_index
karussell eb48989
made vector tiles much faster via loc_index branch
karussell 1025393
show less details for small zoom numbers and full geometry details fo…
karussell 924b0bf
make it possible to use speed in JavaScript and use Leaflet > 1.0; se…
karussell 6cdd50d
store name and filter based on speed not edge length
karussell ccbcd99
use separate MVTResource
karussell a9db7d3
try to fix openjdk12 download URL
karussell deb8fe8
merged master
karussell e23fd66
merged master
karussell 62dba6f
Adds alternative z/x/y endpoint.
easbar c4df0e2
fix response application type
karussell 39d1393
remove unnecessary isochrone stuff
karussell ba9a703
integrate local mvt into GH maps demo
karussell 19d07da
switch localhost to 127.0.0.1
karussell 2b638b5
do remove roads from omniscale layer
karussell 4e800d7
Merge branch 'master' into experimental-mvt
karussell eedda41
updated main.js
karussell 829a4dc
fixed merge conflict for LocationIndexTree
karussell 76964db
include mvt test that reads tile from /mvt endpoint
karussell 4086e0b
revert changes in travis config
karussell 82fd4c2
Merge branch 'master' into experimental-mvt
karussell bf3ece8
include various EncodedValues in response
karussell ce9bcd9
advertise /mvt endpoint
karussell 2ec2309
Merge branch 'master' into experimental-mvt
karussell File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
167 changes: 167 additions & 0 deletions
167
web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| package com.graphhopper.resources; | ||
|
|
||
| import com.graphhopper.GraphHopper; | ||
| import com.graphhopper.routing.profiles.*; | ||
| import com.graphhopper.routing.util.DefaultEdgeFilter; | ||
| import com.graphhopper.routing.util.EncodingManager; | ||
| import com.graphhopper.storage.NodeAccess; | ||
| import com.graphhopper.storage.index.LocationIndexTree; | ||
| import com.graphhopper.util.*; | ||
| import com.graphhopper.util.shapes.BBox; | ||
| import com.wdtinc.mapbox_vector_tile.VectorTile; | ||
| import com.wdtinc.mapbox_vector_tile.adapt.jts.IGeometryFilter; | ||
| import com.wdtinc.mapbox_vector_tile.adapt.jts.JtsAdapter; | ||
| import com.wdtinc.mapbox_vector_tile.adapt.jts.TileGeomResult; | ||
| import com.wdtinc.mapbox_vector_tile.adapt.jts.UserDataKeyValueMapConverter; | ||
| import com.wdtinc.mapbox_vector_tile.build.MvtLayerBuild; | ||
| import com.wdtinc.mapbox_vector_tile.build.MvtLayerParams; | ||
| import com.wdtinc.mapbox_vector_tile.build.MvtLayerProps; | ||
| import org.locationtech.jts.geom.Coordinate; | ||
| import org.locationtech.jts.geom.Envelope; | ||
| import org.locationtech.jts.geom.GeometryFactory; | ||
| import org.locationtech.jts.geom.LineString; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import javax.inject.Inject; | ||
| import javax.servlet.http.HttpServletRequest; | ||
| import javax.ws.rs.*; | ||
| import javax.ws.rs.core.Context; | ||
| import javax.ws.rs.core.MediaType; | ||
| import javax.ws.rs.core.Response; | ||
| import javax.ws.rs.core.UriInfo; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
|
|
||
| @Path("mvt") | ||
| public class MVTResource { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(MVTResource.class); | ||
| private static final MediaType PBF = new MediaType("application", "x-protobuf"); | ||
| private final GraphHopper graphHopper; | ||
| private final EncodingManager encodingManager; | ||
|
|
||
| @Inject | ||
| public MVTResource(GraphHopper graphHopper, EncodingManager encodingManager) { | ||
| this.graphHopper = graphHopper; | ||
| this.encodingManager = encodingManager; | ||
| } | ||
|
|
||
| @GET | ||
| @Path("{z}/{x}/{y}.mvt") | ||
| @Produces("application/x-protobuf") | ||
| public Response doGetXyz( | ||
| @Context HttpServletRequest httpReq, | ||
| @Context UriInfo uriInfo, | ||
| @PathParam("z") int zInfo, | ||
| @PathParam("x") int xInfo, | ||
| @PathParam("y") int yInfo, | ||
| @QueryParam(Parameters.DETAILS.PATH_DETAILS) List<String> pathDetails) { | ||
|
|
||
| if (zInfo <= 9) { | ||
| VectorTile.Tile.Builder mvtBuilder = VectorTile.Tile.newBuilder(); | ||
| return Response.fromResponse(Response.ok(mvtBuilder.build().toByteArray(), PBF).build()) | ||
| .header("X-GH-Took", "0") | ||
| .build(); | ||
| } | ||
|
|
||
| StopWatch totalSW = new StopWatch().start(); | ||
| Coordinate nw = num2deg(xInfo, yInfo, zInfo); | ||
| Coordinate se = num2deg(xInfo + 1, yInfo + 1, zInfo); | ||
| LocationIndexTree locationIndex = (LocationIndexTree) graphHopper.getLocationIndex(); | ||
| final NodeAccess na = graphHopper.getGraphHopperStorage().getNodeAccess(); | ||
| EdgeExplorer edgeExplorer = graphHopper.getGraphHopperStorage().createEdgeExplorer(DefaultEdgeFilter.ALL_EDGES); | ||
| BBox bbox = new BBox(nw.x, se.x, se.y, nw.y); | ||
| if (!bbox.isValid()) | ||
| throw new IllegalStateException("Invalid bbox " + bbox); | ||
|
|
||
| final GeometryFactory geometryFactory = new GeometryFactory(); | ||
| VectorTile.Tile.Builder mvtBuilder = VectorTile.Tile.newBuilder(); | ||
| final IGeometryFilter acceptAllGeomFilter = geometry -> true; | ||
| final Envelope tileEnvelope = new Envelope(se, nw); | ||
| final MvtLayerParams layerParams = new MvtLayerParams(256, 4096); | ||
| final UserDataKeyValueMapConverter converter = new UserDataKeyValueMapConverter(); | ||
| if (!encodingManager.hasEncodedValue(RoadClass.KEY)) | ||
| throw new IllegalStateException("You need to configure GraphHopper to store road_class, e.g. graph.encoded_values: road_class,max_speed,... "); | ||
|
|
||
| final EnumEncodedValue<RoadClass> roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); | ||
| final AtomicInteger edgeCounter = new AtomicInteger(0); | ||
| // in toFeatures addTags of the converter is called and layerProps is filled with keys&values => those need to be stored in the layerBuilder | ||
| // otherwise the decoding won't be successful and "undefined":"undefined" instead of "speed": 30 is the result | ||
| final MvtLayerProps layerProps = new MvtLayerProps(); | ||
| final VectorTile.Tile.Layer.Builder layerBuilder = MvtLayerBuild.newLayerBuilder("roads", layerParams); | ||
|
|
||
| locationIndex.query(bbox, new LocationIndexTree.EdgeVisitor(edgeExplorer) { | ||
| @Override | ||
| public void onEdge(EdgeIteratorState edge, int nodeA, int nodeB) { | ||
| LineString lineString; | ||
| RoadClass rc = edge.get(roadClassEnc); | ||
| if (zInfo >= 14) { | ||
| PointList pl = edge.fetchWayGeometry(3); | ||
| lineString = pl.toLineString(false); | ||
| } else if (rc == RoadClass.MOTORWAY | ||
| || zInfo > 10 && (rc == RoadClass.PRIMARY || rc == RoadClass.TRUNK) | ||
| || zInfo > 11 && (rc == RoadClass.SECONDARY) | ||
| || zInfo > 12) { | ||
| double lat = na.getLatitude(nodeA); | ||
| double lon = na.getLongitude(nodeA); | ||
| double toLat = na.getLatitude(nodeB); | ||
| double toLon = na.getLongitude(nodeB); | ||
| lineString = geometryFactory.createLineString(new Coordinate[]{new Coordinate(lon, lat), new Coordinate(toLon, toLat)}); | ||
| } else { | ||
| // skip edge for certain zoom | ||
| return; | ||
| } | ||
|
|
||
| edgeCounter.incrementAndGet(); | ||
| Map<String, Object> map = new HashMap<>(2); | ||
| map.put("name", edge.getName()); | ||
| for (String str : pathDetails) { | ||
| // how to indicate an erroneous parameter? | ||
| if (str.contains(",") || !encodingManager.hasEncodedValue(str)) | ||
| continue; | ||
|
|
||
| EncodedValue ev = encodingManager.getEncodedValue(str, EncodedValue.class); | ||
| if (ev instanceof EnumEncodedValue) | ||
| map.put(ev.getName(), edge.get((EnumEncodedValue) ev).toString()); | ||
| else if (ev instanceof DecimalEncodedValue) | ||
| map.put(ev.getName(), edge.get((DecimalEncodedValue) ev)); | ||
| else if (ev instanceof BooleanEncodedValue) | ||
| map.put(ev.getName(), edge.get((BooleanEncodedValue) ev)); | ||
| else if (ev instanceof IntEncodedValue) | ||
| map.put(ev.getName(), edge.get((IntEncodedValue) ev)); | ||
| } | ||
|
|
||
| lineString.setUserData(map); | ||
|
|
||
| // doing some AffineTransformation | ||
| TileGeomResult tileGeom = JtsAdapter.createTileGeom(lineString, tileEnvelope, geometryFactory, layerParams, acceptAllGeomFilter); | ||
| List<VectorTile.Tile.Feature> features = JtsAdapter.toFeatures(tileGeom.mvtGeoms, layerProps, converter); | ||
| layerBuilder.addAllFeatures(features); | ||
| } | ||
|
|
||
| @Override | ||
| public void onTile(BBox bbox, int depth) { | ||
| } | ||
| }); | ||
|
|
||
| MvtLayerBuild.writeProps(layerBuilder, layerProps); | ||
| mvtBuilder.addLayers(layerBuilder.build()); | ||
| byte[] bytes = mvtBuilder.build().toByteArray(); | ||
| totalSW.stop(); | ||
| logger.debug("took: " + totalSW.getSeconds() + ", edges:" + edgeCounter.get()); | ||
| return Response.ok(bytes, PBF).header("X-GH-Took", "" + totalSW.getSeconds() * 1000) | ||
| .build(); | ||
| } | ||
|
|
||
| Coordinate num2deg(int xInfo, int yInfo, int zoom) { | ||
| double n = Math.pow(2, zoom); | ||
| double lonDeg = xInfo / n * 360.0 - 180.0; | ||
| // unfortunately latitude numbers goes from north to south | ||
| double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * yInfo / n))); | ||
| double latDeg = Math.toDegrees(latRad); | ||
| return new Coordinate(lonDeg, latDeg); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With maputnik this gives me
Error: http status 200 returned without content. Can we do better at returning 'an empty tile' ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should be possible to convert an empty geometry to an MVT tile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I cannot reproduce this problem (and also do not know how to create an empty geometry), can we delay and fix this in a later issue?