com.graphhopper.resources.MVTResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphhopper-web-bundle Show documentation
Show all versions of graphhopper-web-bundle Show documentation
Use the GraphHopper routing engine as a web-service
package com.graphhopper.resources;
import com.graphhopper.GraphHopper;
import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.storage.NodeAccess;
import com.graphhopper.storage.index.LocationIndexTree;
import com.graphhopper.util.EdgeIteratorState;
import com.graphhopper.util.FetchMode;
import com.graphhopper.util.PointList;
import com.graphhopper.util.StopWatch;
import com.graphhopper.util.shapes.BBox;
import no.ecc.vectortile.VectorTileEncoder;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.util.AffineTransformation;
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.LinkedHashMap;
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("render_all") @DefaultValue("false") Boolean renderAll) {
if (zInfo <= 9) {
byte[] bytes = new VectorTileEncoder().encode();
return Response.fromResponse(Response.ok(bytes, 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.getBaseGraph().getNodeAccess();
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();
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 roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class);
final AtomicInteger edgeCounter = new AtomicInteger(0);
// 256x256 pixels per MVT. here we transform from the global coordinate system to the local one of the tile.
AffineTransformation affineTransformation = new AffineTransformation();
affineTransformation.translate(-nw.x, -se.y);
affineTransformation.scale(
256.0 / (se.x - nw.x),
-256.0 / (nw.y - se.y)
);
affineTransformation.translate(0, 256);
// if performance of the vector tile encoding becomes an issue it might be worth to get rid of the simplification
// and clipping in the no.ecc code? https://github.com/graphhopper/graphhopper/commit/0f96c2deddb24efa97109e35e0c05f1c91221f59#r90830001
VectorTileEncoder vectorTileEncoder = new VectorTileEncoder();
locationIndex.query(bbox, edgeId -> {
EdgeIteratorState edge = graphHopper.getBaseGraph().getEdgeIteratorStateForKey(edgeId * 2);
LineString lineString;
if (renderAll) {
PointList pl = edge.fetchWayGeometry(FetchMode.ALL);
lineString = pl.toLineString(false);
} else {
RoadClass rc = edge.get(roadClassEnc);
if (zInfo >= 14) {
PointList pl = edge.fetchWayGeometry(FetchMode.ALL);
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.getLat(edge.getBaseNode());
double lon = na.getLon(edge.getBaseNode());
double toLat = na.getLat(edge.getAdjNode());
double toLon = na.getLon(edge.getAdjNode());
lineString = geometryFactory.createLineString(new Coordinate[]{new Coordinate(lon, lat), new Coordinate(toLon, toLat)});
} else {
// skip edge for certain zoom
return;
}
}
edgeCounter.incrementAndGet();
Map map = new LinkedHashMap<>();
edge.getKeyValues().forEach(
entry -> map.put(entry.key, entry.value)
);
map.put("edge_id", edge.getEdge());
map.put("edge_key", edge.getEdgeKey());
map.put("base_node", edge.getBaseNode());
map.put("adj_node", edge.getAdjNode());
map.put("distance", edge.getDistance());
encodingManager.getEncodedValues().forEach(ev -> {
if (ev instanceof EnumEncodedValue)
map.put(ev.getName(), edge.get((EnumEncodedValue) ev).toString() + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((EnumEncodedValue) ev).toString() : ""));
else if (ev instanceof DecimalEncodedValue)
map.put(ev.getName(), edge.get((DecimalEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((DecimalEncodedValue) ev) : ""));
else if (ev instanceof BooleanEncodedValue)
map.put(ev.getName(), edge.get((BooleanEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((BooleanEncodedValue) ev) : ""));
else if (ev instanceof IntEncodedValue)
map.put(ev.getName(), edge.get((IntEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((IntEncodedValue) ev) : ""));
});
lineString.setUserData(map);
Geometry g = affineTransformation.transform(lineString);
vectorTileEncoder.addFeature("roads", map, g, edge.getEdge());
});
byte[] bytes = vectorTileEncoder.encode();
totalSW.stop();
logger.debug("took: " + totalSW.getMillis() + "ms, edges:" + edgeCounter.get());
return Response.ok(bytes, PBF).header("X-GH-Took", "" + totalSW.getSeconds() * 1000)
.build();
}
Coordinate num2deg(int xInfo, int yInfo, int zoom) {
// inverse web mercator projection
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);
}
}