/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.api.ws.internals; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.xml.bind.annotation.XmlRootElement; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.IdentityBean; import org.opentripplanner.api.model.internals.Annotation; import org.opentripplanner.api.model.internals.AnnotationObject; import org.opentripplanner.api.model.internals.Annotations; import org.opentripplanner.api.model.internals.EdgeSet; import org.opentripplanner.api.model.internals.EdgesForVertex; import org.opentripplanner.api.model.internals.FeatureCount; import org.opentripplanner.api.model.internals.SimpleVertex; import org.opentripplanner.api.model.internals.SimpleVertexSet; import org.opentripplanner.api.model.internals.VertexSet; import org.opentripplanner.api.model.internals.WrappedEdge; import org.opentripplanner.gbannotation.GraphBuilderAnnotation; import org.opentripplanner.routing.edgetype.StreetEdge; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Graph.LoadLevel; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.services.GraphService; import org.opentripplanner.routing.vertextype.StreetVertex; import org.opentripplanner.routing.vertextype.TransitVertex; import org.springframework.beans.factory.annotation.Required; import org.springframework.security.access.annotation.Secured; import com.sun.jersey.api.spring.Autowire; import com.sun.jersey.spi.resource.Singleton; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.index.strtree.STRtree; /** * Data about the edges and vertices of a graph. This data is tied to a particular internal graph * representation. OTP internals are subject to change at any time, so please do not use this for * anything other than debugging. * * @author novalis * */ @Path("/internals") @XmlRootElement @Autowire @Singleton public class GraphInternals { private GraphService graphService; private HashMap<String, STRtree> vertexIndices = new HashMap<String, STRtree>(); private HashMap<String, STRtree> edgeIndices = new HashMap<String, STRtree>(); @Required public void setGraphService(GraphService graphService) { this.graphService = graphService; } public synchronized void initIndexes(String routerId) { STRtree vertexIndex = vertexIndices.get(routerId); if (vertexIndex != null) { return; } vertexIndex = new STRtree(); STRtree edgeIndex = new STRtree(); graphService.setLoadLevel(LoadLevel.DEBUG); Graph graph = graphService.getGraph(routerId); vertexIndex = new STRtree(); edgeIndex = new STRtree(); for (Vertex v : graph.getVertices()) { Envelope vertexEnvelope = new Envelope(v.getCoordinate()); vertexIndex.insert(vertexEnvelope, v); for (Edge e : v.getOutgoing()) { Envelope envelope; Geometry geometry = e.getGeometry(); if (geometry == null) { envelope = vertexEnvelope; } else { envelope = geometry.getEnvelopeInternal(); } edgeIndex.insert(envelope, e); } } vertexIndex.build(); edgeIndex.build(); vertexIndices.put(routerId, vertexIndex); edgeIndices.put(routerId, edgeIndex); } /** * Get vertices inside a bbox. * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/vertex") @Produces({ MediaType.APPLICATION_JSON }) public Object getVertex( @QueryParam("label") String label, @QueryParam("routerId") String routerId) { Graph graph = graphService.getGraph(routerId); Vertex vertex = graph.getVertex(label); if (vertex == null) { return null; } return new WrappedVertex(vertex).withGraph(graph); } /** * Get vertices inside a bbox. * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/vertices") @Produces({ MediaType.APPLICATION_JSON }) public Object getVertices( @QueryParam("lowerLeft") String lowerLeft, @QueryParam("upperRight") String upperRight, @QueryParam("pointsOnly") boolean pointsOnly, @QueryParam("exactClass") String className, @QueryParam("skipTransit") boolean skipTransit, @QueryParam("skipStreets") boolean skipStreets, @QueryParam("routerId") String routerId ) { initIndexes(routerId); if (className != null && className.equals("")) className = null; Envelope envelope = getEnvelope(lowerLeft, upperRight); STRtree vertexIndex = vertexIndices.get(routerId); @SuppressWarnings("unchecked") List<Vertex> query = vertexIndex .query(envelope); List<Vertex> filtered = new ArrayList<Vertex>(); for (Vertex v : query) { if (skipTransit && v instanceof TransitVertex) continue; if (skipStreets && v instanceof StreetVertex) continue; if (className != null && !v.getClass().getName().endsWith("." + className)) continue; filtered.add(v); } if (pointsOnly) { SimpleVertexSet out = new SimpleVertexSet(); out.vertices = new ArrayList<SimpleVertex>(filtered.size()); for (Vertex v : filtered) { out.vertices.add(new SimpleVertex(v)); } return out; } else { VertexSet out = new VertexSet(); out.vertices = filtered; Graph graph = graphService.getGraph(routerId); return out.withGraph(graph); } } /** * Get vertices connected to an edge * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/verticesForEdge") @Produces({ MediaType.APPLICATION_JSON }) public Object getVerticesForEdge(@QueryParam("edge") int edgeId, @QueryParam("routerId") String routerId) { Graph graph = graphService.getGraph(routerId); Edge edge = graph.getEdgeById(edgeId); VertexSet out = new VertexSet(); out.vertices = new ArrayList<Vertex>(2); out.vertices.add(edge.getFromVertex()); out.vertices.add(edge.getToVertex()); return out.withGraph(graph); } /** * Get edges connected to an vertex * @param routerId * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/edgesForVertex") @Produces({ MediaType.APPLICATION_JSON }) public EdgesForVertex getEdgesForVertex(@QueryParam("vertex") String label, @QueryParam("routerId") String routerId) { Graph graph = graphService.getGraph(routerId); Vertex vertex = graph.getVertex(label); if (vertex == null) { return null; } EdgeSet incoming = new EdgeSet(); incoming.addEdges(vertex.getIncoming(), graph); EdgeSet outgoing = new EdgeSet(); outgoing.addEdges(vertex.getOutgoing(), graph); EdgesForVertex e4v = new EdgesForVertex(); e4v.incoming = incoming.withGraph(graph); e4v.outgoing = outgoing.withGraph(graph); return e4v; } /** * Get edges inside a bbox. * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/edges") @Produces({ MediaType.APPLICATION_JSON }) public Object getEdges( @QueryParam("lowerLeft") String lowerLeft, @QueryParam("upperRight") String upperRight, @QueryParam("exactClass") String className, @QueryParam("skipTransit") boolean skipTransit, @QueryParam("skipStreets") boolean skipStreets, @QueryParam("skipNoGeometry") boolean skipNoGeometry, @QueryParam("routerId") String routerId) { initIndexes(routerId); if (className != null && className.equals("")) className = null; Envelope envelope = getEnvelope(lowerLeft, upperRight); EdgeSet out = new EdgeSet(); Graph graph = graphService.getGraph(routerId); STRtree edgeIndex = edgeIndices.get(routerId); @SuppressWarnings("unchecked") List<Edge> query = edgeIndex.query(envelope); out.edges = new ArrayList<WrappedEdge>(); for (Edge e : query) { if (skipStreets && (e instanceof StreetEdge)) continue; if (skipTransit && !(e instanceof StreetEdge)) continue; if (skipNoGeometry && e.getGeometry() == null) continue; if (className != null && !e.getClass().getName().endsWith("." + className)) continue; out.edges.add(new WrappedEdge(e, graph.getIdForEdge(e))); } return out.withGraph(graph); } /** * Count vertices and edges inside a bbox. * * @return */ @Secured({ "ROLE_USER" }) @GET @Path("/countFeatures") @Produces({ MediaType.APPLICATION_JSON }) public FeatureCount countVertices( @QueryParam("lowerLeft") String lowerLeft, @QueryParam("upperRight") String upperRight, @QueryParam("routerId") String routerId) { initIndexes(routerId); Envelope envelope = getEnvelope(lowerLeft, upperRight); FeatureCount out = new FeatureCount(); @SuppressWarnings("unchecked") STRtree vertexIndex = vertexIndices.get(routerId); List<Vertex> vertexQuery = vertexIndex.query(envelope); out.vertices = vertexQuery.size(); @SuppressWarnings("unchecked") STRtree edgeIndex = edgeIndices.get(routerId); List<Edge> edgeQuery = edgeIndex.query(envelope); out.edges = edgeQuery.size(); return out; } /** Envelopes are in latitude, longitude format */ public static Envelope getEnvelope(String lowerLeft, String upperRight) { String[] lowerLeftParts = lowerLeft.split(","); String[] upperRightParts = upperRight.split(","); Envelope envelope = new Envelope(Double.parseDouble(lowerLeftParts[1]), Double.parseDouble(upperRightParts[1]), Double.parseDouble(lowerLeftParts[0]), Double.parseDouble(upperRightParts[0])); return envelope; } @Secured({ "ROLE_USER" }) @GET @Path("/annotations") @Produces({ MediaType.APPLICATION_JSON }) public Object getAnnotations(@QueryParam("routerId") String routerId) { Graph graph = graphService.getGraph(routerId); List<GraphBuilderAnnotation> builderAnnotations = graph.getBuilderAnnotations(); Annotations annotations = new Annotations(); List<Annotation> out = new ArrayList<Annotation>(); annotations.annotations = out; if (builderAnnotations != null) { for (GraphBuilderAnnotation annotation : builderAnnotations) { Annotation outAnnotation = new Annotation(); out.add(outAnnotation); outAnnotation.annotation = annotation.getClass().toString(); // TODO: adapt to class annotations rather than generic enum-based annotations // Collection<Object> referencedObjects = annotation.getReferencedObjects(); // for (Object object : referencedObjects) { // AnnotationObject annotationObj = new AnnotationObject(); // applyObjectToAnnotation(graph, annotationObj, object); // outAnnotation.addObject(annotationObj); // } } } return annotations; } private void applyObjectToAnnotation(Graph graph, AnnotationObject annotation, Object o) { if (o instanceof Edge) { annotation.edge = graph.getIdForEdge((Edge) o); } else if (o instanceof Vertex) { annotation.vertex = ((Vertex) o).getLabel(); } else if (o instanceof String) { annotation.message = (String) o; } else if (o instanceof IdentityBean) { IdentityBean<?> bean = (IdentityBean<?>) o; Object id = bean.getId(); applyObjectToAnnotation(graph, annotation, id); } else if (o instanceof AgencyAndId) { AgencyAndId id = (AgencyAndId) o; annotation.agency = id.getAgencyId(); annotation.id = id.getId(); } else if (o instanceof Collection) { Collection<?> collection = (Collection<?>) o; if (collection.isEmpty()) return; Object first = collection.iterator().next(); applyObjectToAnnotation(graph, annotation, first); } } }