{"action":"created","comment":{"url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/comments/23255516","id":23255516,"diff_hunk":"@@ -0,0 +1,457 @@\n+/* This program is free software: you can redistribute it and/or\n+ modify it under the terms of the GNU Lesser General Public License\n+ as published by the Free Software Foundation, either version 3 of\n+ the License, or (at your option) any later version.\n+\n+ This program is distributed in the hope that it will be useful,\n+ but WITHOUT ANY WARRANTY; without even the implied warranty of\n+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n+ GNU General Public License for more details.\n+\n+ You should have received a copy of the GNU General Public License\n+ along with this program.  If not, see <http://www.gnu.org/licenses/>. */\n+\n+package org.opentripplanner.graph_builder.impl.shapefile;\n+\n+import java.util.*;\n+import java.util.Set;\n+\n+import org.geotools.data.FeatureSource;\n+import org.geotools.data.Query;\n+import org.geotools.factory.Hints;\n+import org.geotools.feature.FeatureCollection;\n+import org.geotools.feature.FeatureIterator;\n+import org.geotools.geometry.jts.JTS;\n+import org.geotools.referencing.ReferencingFactoryFinder;\n+import org.joda.time.DateTime;\n+import org.opengis.feature.simple.SimpleFeature;\n+import org.opengis.feature.simple.SimpleFeatureType;\n+import org.opengis.referencing.crs.CRSAuthorityFactory;\n+import org.opengis.referencing.crs.CoordinateReferenceSystem;\n+import org.opentripplanner.common.geometry.GeometryUtils;\n+import org.opentripplanner.common.model.P2;\n+import org.opentripplanner.graph_builder.impl.shapefile.AttributeFeatureConverter;\n+import org.opentripplanner.graph_builder.impl.shapefile.StringAttributeFeatureConverter;\n+import org.opentripplanner.graph_builder.services.GraphBuilder;\n+import org.opentripplanner.graph_builder.services.shapefile.FeatureSourceFactory;\n+import org.opentripplanner.graph_builder.services.shapefile.SimpleFeatureConverter;\n+import org.opentripplanner.routing.alertpatch.Alert;\n+import org.opentripplanner.routing.core.RoutingRequest;\n+import org.opentripplanner.routing.core.TraverseMode;\n+import org.opentripplanner.routing.core.TraverseModeSet;\n+import org.opentripplanner.routing.edgetype.StreetEdge;\n+import org.opentripplanner.routing.edgetype.StreetTraversalPermission;\n+import org.opentripplanner.routing.graph.Edge;\n+import org.opentripplanner.routing.graph.Graph;\n+import org.opentripplanner.routing.graph.Vertex;\n+import org.opentripplanner.routing.impl.*;\n+import org.opentripplanner.routing.services.GraphService;\n+import org.opentripplanner.routing.services.PathService;\n+import org.opentripplanner.routing.services.notes.StreetNotesService;\n+import org.opentripplanner.routing.spt.GraphPath;\n+import org.opentripplanner.routing.spt.ShortestPathTree;\n+import org.opentripplanner.routing.vertextype.StreetVertex;\n+import org.opentripplanner.routing.vertextype.IntersectionVertex;\n+import org.slf4j.Logger;\n+import org.slf4j.LoggerFactory;\n+\n+import com.vividsolutions.jts.geom.Coordinate;\n+import com.vividsolutions.jts.geom.Geometry;\n+import com.vividsolutions.jts.geom.LineString;\n+import com.vividsolutions.jts.geom.MultiLineString;\n+\n+import com.google.common.collect.HashMultimap;\n+import com.google.common.collect.Multimap;\n+import com.google.common.collect.Maps;\n+import com.google.common.collect.Sets;\n+\n+/**\n+ * Loads an NIH shapefile into an edge-based graph, associating the edges with those previously\n+ * imported from OpenStreetMap.\n+ *\n+ */\n+public class NihShapefileStreetGraphBuilderImpl implements GraphBuilder {\n+    private static Logger LOG = LoggerFactory.getLogger(ShapefileStreetGraphBuilderImpl.class);\n+\n+    private FeatureSourceFactory _featureSourceFactory;\n+\n+    private GraphServiceImpl graphService;\n+\n+    public List<String> provides() {\n+        // streets are required by the previous OSM graph builder step, so return an empty collection\n+        return Collections.emptyList();\n+    }\n+\n+    public List<String> getPrerequisites() {\n+        // require the things provided by the OSM builder\n+        return Arrays.asList(\"streets\", \"turns\");\n+    }\n+\n+    public void setFeatureSourceFactory(FeatureSourceFactory factory) {\n+        _featureSourceFactory = factory;\n+    }\n+\n+    @Override\n+    public void buildGraph(Graph graph, HashMap<Class<?>, Object> extra) {\n+        LOG.info(\"Adding to graph data from NIH Shapefile...\");\n+\n+        FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = _featureSourceFactory\n+                .getFeatureSource();\n+\n+        FeatureCollection<SimpleFeatureType, SimpleFeature> features = null;\n+\n+        try {\n+            CoordinateReferenceSystem sourceCRS = featureSource.getInfo().getCRS();\n+\n+            Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);\n+            CRSAuthorityFactory factory = ReferencingFactoryFinder.getCRSAuthorityFactory(\"EPSG\",\n+                    hints);\n+            CoordinateReferenceSystem worldCRS = factory\n+                    .createCoordinateReferenceSystem(\"EPSG:4326\");\n+\n+            Query query = new Query();\n+            query.setCoordinateSystem(sourceCRS);\n+            query.setCoordinateSystemReproject(worldCRS);\n+\n+            features = featureSource.getFeatures(query);\n+\n+        } catch (Exception ex) {\n+            throw new IllegalStateException(\"error loading shapefile street data\", ex);\n+        } finally {\n+            _featureSourceFactory.cleanup();\n+        }\n+\n+        // TODO: get rest of properties\n+        StreetVertexIndexServiceImpl vertexSvc = new StreetVertexIndexServiceImpl(graph);\n+        SimpleFeatureConverter<Long> osmIdSelector = new AttributeFeatureConverter(\"osm_id\");\n+        SimpleFeatureConverter<Integer> segmentIdSelector = new AttributeFeatureConverter(\"segment_id\");\n+        StringAttributeFeatureConverter nameSelector = new StringAttributeFeatureConverter(\"name\", \"\");\n+\n+        List<SimpleFeature> featureList = new ArrayList<SimpleFeature>();\n+        FeatureIterator<SimpleFeature> it2 = features.features();\n+        while (it2.hasNext()) {\n+            SimpleFeature feature = it2.next();\n+            featureList.add(feature);\n+        }\n+        it2.close();\n+        it2 = null;\n+\n+        LOG.info(\"Have {} NIH features to process...\", featureList.size());\n+\n+        // index edges and nodes in graph built from OSM data by their OSM IDs\n+        LOG.info(\"Indexing OSM nodes and ways by ID...\");\n+        Multimap<String, StreetEdge> edgesByWayId = HashMultimap.create();\n+        Map<String, IntersectionVertex> intersectionsByNodeId = Maps.newHashMap();\n+        Set<Vertex> vertices = Sets.newHashSet();\n+        Collection<Edge> edges = graph.getEdges();\n+        for (Edge edge: edges) {\n+            if (edge instanceof StreetEdge) {\n+                StreetEdge street = (StreetEdge) edge;\n+                edgesByWayId.put(street.getOsmId(), street);\n+                vertices.add(street.getFromVertex());\n+                vertices.add(street.getToVertex());\n+            }\n+        }\n+\n+        edges = null;\n+\n+        for (Vertex vertex : vertices) {\n+            if (vertex instanceof IntersectionVertex) {\n+                IntersectionVertex intersection = (IntersectionVertex) vertex;\n+                intersectionsByNodeId.put(intersection.getLabel(), intersection);\n+            }\n+        }\n+\n+        vertices = null;\n+        \n+        int foundStartCt = 0;\n+        int foundEndCt = 0;\n+        int missingStartCt = 0;\n+        int missingEndCt = 0;\n+\n+        for (SimpleFeature feature : featureList) {\n+            if (feature.getDefaultGeometry() == null) {\n+                LOG.warn(\"feature has no geometry: \" + feature.getIdentifier());\n+                continue;\n+            }\n+            LineString geom = toLineString((Geometry) feature.getDefaultGeometry());\n+\n+            String streetName = nameSelector.convert(feature);\n+\n+            String wayId = \"\";\n+            Long way = osmIdSelector.convert(feature);\n+            if (way != null) {\n+                wayId = way.toString();\n+            } else {\n+                LOG.warn(\"WHY IS THERE A MISSING WAY ID?!?!\");\n+            }\n+\n+            String segmentId = segmentIdSelector.convert(feature).toString();\n+            if (segmentId.equals(\"0\")) {\n+                // simply skip segments with zero IDs (only the ones with segment IDs have data associated)\n+                continue;\n+            }\n+\n+            LOG.info(\"Found NIH street named {} with Way ID {} and segment ID {}.\",\n+                     streetName, wayId, segmentId);\n+\n+            String wayLabel = \"osm:way:\" + wayId;\n+            String wayName = streetName;\n+\n+            Coordinate[] coordinates = geom.getCoordinates();\n+\n+            if (coordinates.length < 2) {\n+                //not a real linestring\n+                LOG.warn(\"Bad geometry for street with label \" + wayLabel + \" name \" + wayName);\n+                continue;\n+            }\n+\n+            Coordinate startCoordinate = new Coordinate(coordinates[0].x, coordinates[0].y);\n+            Coordinate endCoordinate = new Coordinate(coordinates[coordinates.length - 1].x,\n+                                                      coordinates[coordinates.length - 1].y);\n+\n+            StreetVertex startIntersection = vertexSvc.getIntersectionAt(startCoordinate);\n+\n+            boolean missingStart = true;\n+            boolean missingEnd = true;\n+\n+            if (startIntersection == null && !segmentId.isEmpty()) {\n+                LOG.warn(\"Could not find intersection for start coordinate: {} on {}: {}\", startCoordinate, wayLabel, wayName);\n+                missingStartCt += 1;\n+            } else {\n+                missingStart = false;\n+                if (!segmentId.isEmpty()) {\n+                    foundStartCt += 1;\n+                }\n+            }\n+\n+            StreetVertex endIntersection = vertexSvc.getIntersectionAt(endCoordinate);\n+\n+            if (endIntersection == null && !segmentId.isEmpty()) {\n+                LOG.warn(\"Could not find intersection for end coordinate: {} on {}: {}\", endCoordinate, wayLabel, wayName);\n+                missingEndCt += 1;\n+            } else {\n+                missingEnd = false;\n+                if (!segmentId.isEmpty()) {\n+                    foundEndCt += 1;\n+                }\n+            }\n+\n+            if (missingStart && missingEnd) {\n+                LOG.warn(\"Missing both start and end intersections for way {}: {}\", wayLabel, wayName);\n+                continue;\n+            }\n+\n+            // find edges between start and end vertices\n+\n+            // set of matched edges on way between start and end intersections\n+            Set<StreetEdge> matchedEdges = new HashSet();\n+\n+            // start with the already imported edges with the given OSM Way ID\n+            Collection<StreetEdge> wayEdges = edgesByWayId.get(wayLabel);\n+\n+            if (wayEdges.isEmpty()) {\n+                LOG.warn(\"Have no way edges found for OSM way {}\", wayId);\n+            }\n+\n+            boolean foundStart = false;\n+            boolean foundEnd = false;\n+\n+            ArrayList<StreetEdge> startEdges = new ArrayList();\n+            ArrayList<StreetEdge> endEdges = new ArrayList();\n+            ArrayList<Vertex> farStartVertices = new ArrayList();\n+            ArrayList<Vertex> farEndVertices = new ArrayList();\n+\n+            for (StreetEdge edge : wayEdges) {\n+                Vertex from = edge.getFromVertex();\n+                Vertex to = edge.getToVertex();\n+                String fromLabel = from.getLabel();\n+                String toLabel = to.getLabel();\n+\n+                if (fromLabel.equals(startIntersection.getLabel())) {\n+                    foundStart = true;\n+                    startEdges.add(edge);\n+                    farStartVertices.add(to);\n+                } else if (toLabel.equals(startIntersection.getLabel())) {\n+                    foundStart = true;\n+                    startEdges.add(edge);\n+                    farStartVertices.add(from);\n+                }\n+\n+                if (fromLabel.equals(endIntersection.getLabel())) {\n+                    foundEnd = true;\n+                    endEdges.add(edge);\n+                    farEndVertices.add(to);\n+                } else if (toLabel.equals(endIntersection.getLabel())) {\n+                    foundEnd = true;\n+                    endEdges.add(edge);\n+                    farEndVertices.add(from);\n+                }\n+            }\n+\n+            if (!foundStart || !foundEnd) {\n+                LOG.warn(\"Could not find start or end for {}: {}, segment {}\", wayLabel, wayName, segmentId);\n+                continue;\n+            }\n+\n+            // check if the start and end intersections are connected by a single graph segment\n+            // (if so, there will be two edges found, one the reverse of the other)\n+            boolean foundMatch = false;\n+            for (StreetEdge start : startEdges) {\n+                for (StreetEdge end : endEdges) {\n+                    if (start.isEquivalentTo(end)) {\n+                        foundMatch = true;\n+                        matchedEdges.add(start);\n+                    }\n+                }\n+            }\n+\n+            if (!foundMatch) {\n+                // check if the start and end intersections are connected by two edges (that share a vertex in between)\n+                // note the shared vertex, if found, in order to add edges that go to/from it later\n+                String sharedVertex = null;\n+                for (Vertex start : farStartVertices) {\n+                    for (Vertex end : farEndVertices) {\n+                        if (start.getLabel().equals(end.getLabel())) {\n+                            sharedVertex = start.getLabel();\n+                            break;\n+                        }\n+                    }\n+                }\n+\n+                if (sharedVertex != null) {\n+                    LOG.info(\"Start and end vertex share a midpoint at {}\", sharedVertex);\n+                    // go grab only edges that go between shared vertex and start or end edge\n+                    List<StreetEdge> candidates = new ArrayList<>();\n+                    candidates.addAll(startEdges);\n+                    candidates.addAll(endEdges);\n+                    for (StreetEdge edge: candidates) {\n+                        if (edge.getFromVertex().getLabel().equals(sharedVertex) ||\n+                                edge.getToVertex().getLabel().equals(sharedVertex)) {\n+\n+                            matchedEdges.add(edge);\n+                        }\n+                    }\n+                } else {\n+                    // There are three or more edges between the start and end points.\n+                    // Get SPT path from start to end intersections and traverse it.\n+                    TraverseModeSet modeSet = new TraverseModeSet(TraverseMode.WALK);\n+                    RoutingRequest options = new RoutingRequest(modeSet);\n+                    options.setMaxWalkDistance(99999);\n+                    options.setArriveBy(false);\n+                    options.setFromString(startIntersection.getName());\n+                    options.setToString(endIntersection.getName());\n+                    options.numItineraries = 1;\n+                    GenericAStarFactory sptServiceFactory = new GenericAStarFactory();\n+                    GraphService svc = getGraphService(graph);\n+                    PathService pathService = new ParetoPathService(svc, sptServiceFactory);\n+                    sptServiceFactory.setTraverseVisitor(null);\n+                    SPTVisitor visitor = new SPTVisitor();\n+                    pathService.setSPTVisitor(visitor);\n+                    List<GraphPath> paths = pathService.getPaths(options);\n+                    ShortestPathTree spt = visitor.spt;\n+                    LOG.info(\"Found SPT path between {} and {}:\", startIntersection.getLabel(), endIntersection.getLabel());\n+                    boolean goodPath = true;\n+                    for (GraphPath path : paths) {\n+                        LinkedList<Edge> pathEdges = path.edges;\n+                        for (Edge edge : pathEdges) {\n+                            if (edge instanceof StreetEdge) {\n+                                StreetEdge street = (StreetEdge) edge;\n+                                if (!street.getOsmId().equals(wayLabel)) {\n+                                    LOG.warn(\"Mismatched path street edge with way ID {} along way {}\", street.getOsmId(), wayLabel);\n+                                    goodPath = false;\n+                                } else {\n+                                    matchedEdges.add(street);\n+\n+                                    // TODO: surely there's an easier way to find the back edge.\n+                                    // add reverse of this edge\n+                                    boolean foundReverse = false;\n+                                    for (Edge reverse : wayEdges) {\n+                                        if (reverse.isReverseOf(street)) {\n+                                            foundReverse = true;\n+                                            matchedEdges.add((StreetEdge) reverse);\n+                                        }\n+                                    }\n+                                    if (!foundReverse) {\n+                                        LOG.warn(\"No reverse found for matched SPT edge {}: {}\", edge.getName(), ((StreetEdge) edge).getOsmId());\n+                                    }\n+                                }\n+                            } else {\n+                                LOG.warn(\"Edge on SPT is not a StreetEdge!  Cannot use it.\");\n+                            }\n+                        }\n+                    }\n+\n+                    options.cleanup();\n+\n+                    if (!goodPath) {\n+                        LOG.warn(\"Bad path for way {}: {}\", wayLabel, wayName);\n+                        continue;\n+                    }\n+                }\n+            }\n+\n+            // sanity check\n+            LOG.info(\"Have {} matched street edges for way {}.\", matchedEdges.size(), wayLabel + \" \" + wayName);\n+            boolean isBad = false;\n+            for (StreetEdge edge : matchedEdges) {\n+                if (!edge.getOsmId().equals(wayLabel)) {\n+                    LOG.warn(\"Found bad matched way!  Got: {} Should be: {}\", edge.getOsmId(), wayLabel);\n+                    isBad = true;\n+                }\n+            }\n+\n+            if (isBad) {\n+                continue;\n+            }\n+\n+            ///////////////////////////////////////////////////////////////////////\n+            // If got this far, have a good set of matched way edges to work with.\n+            // Now go set properties on them.\n+            ///////////////////////////////////////////////////////////////////////\n+\n+            // TODO: Set edge properties\n+        }\n+\n+        LOG.info(\"Found {} start and {} end intersections.\", foundStartCt, foundEndCt);\n+        if (missingStartCt > 0 || missingEndCt > 0) {\n+            LOG.warn(\"Missing {} start intersections.\", missingStartCt);\n+            LOG.warn(\"Missing {} end intersections.\", missingEndCt);\n+        }\n+    }","path":"src/main/java/org/opentripplanner/graph_builder/impl/shapefile/NihShapefileStreetGraphBuilderImpl.java","position":406,"original_position":421,"commit_id":"84e03f3bd6a5619a878e625b8c563b73b52c07a3","original_commit_id":"f64e14410ad347e044bf7a51b8db1a83e69fcfd1","user":{"login":"ddohler","id":447977,"avatar_url":"https://avatars.githubusercontent.com/u/447977?v=3","gravatar_id":"","url":"https://api.github.com/users/ddohler","html_url":"https://github.com/ddohler","followers_url":"https://api.github.com/users/ddohler/followers","following_url":"https://api.github.com/users/ddohler/following{/other_user}","gists_url":"https://api.github.com/users/ddohler/gists{/gist_id}","starred_url":"https://api.github.com/users/ddohler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ddohler/subscriptions","organizations_url":"https://api.github.com/users/ddohler/orgs","repos_url":"https://api.github.com/users/ddohler/repos","events_url":"https://api.github.com/users/ddohler/events{/privacy}","received_events_url":"https://api.github.com/users/ddohler/received_events","type":"User","site_admin":false},"body":"I'm having trouble figuring out what this function affects; does it set the object state, or does it change one of the parameters in place (`graph`, perhaps?). If possible, it would make things clearer to use a temporary variable throughout the function body, and then assign to its ultimate destination at the end so it's easy to find the output. Some additional commenting and/or breaking different phases out into functions would also help make the overall structure easier to read.","created_at":"2015-01-20T21:02:59Z","updated_at":"2015-01-20T21:02:59Z","html_url":"https://github.com/azavea/OpenTripPlanner/pull/2#discussion_r23255516","pull_request_url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2","_links":{"self":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/comments/23255516"},"html":{"href":"https://github.com/azavea/OpenTripPlanner/pull/2#discussion_r23255516"},"pull_request":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2","id":27707533,"html_url":"https://github.com/azavea/OpenTripPlanner/pull/2","diff_url":"https://github.com/azavea/OpenTripPlanner/pull/2.diff","patch_url":"https://github.com/azavea/OpenTripPlanner/pull/2.patch","issue_url":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/2","number":2,"state":"open","locked":false,"title":"Feature/shapefile import","user":{"login":"flibbertigibbet","id":960264,"avatar_url":"https://avatars.githubusercontent.com/u/960264?v=3","gravatar_id":"","url":"https://api.github.com/users/flibbertigibbet","html_url":"https://github.com/flibbertigibbet","followers_url":"https://api.github.com/users/flibbertigibbet/followers","following_url":"https://api.github.com/users/flibbertigibbet/following{/other_user}","gists_url":"https://api.github.com/users/flibbertigibbet/gists{/gist_id}","starred_url":"https://api.github.com/users/flibbertigibbet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flibbertigibbet/subscriptions","organizations_url":"https://api.github.com/users/flibbertigibbet/orgs","repos_url":"https://api.github.com/users/flibbertigibbet/repos","events_url":"https://api.github.com/users/flibbertigibbet/events{/privacy}","received_events_url":"https://api.github.com/users/flibbertigibbet/received_events","type":"User","site_admin":false},"body":"*  Track the OpenStreetMap Way IDs on the loaded edges (ways are usually broken up into several edges in the graph).\r\n*  Add a shapefile loader for the NIH segments and associate each segment with a set of graph edges on the same OSM way that go between the same start and end points.\r\n\r\nRemaining shapefile-loading tasks include setting properties on the street edges using the shapefile feature data, and loading and setting properties on intersections from the second NIH shapefile.","created_at":"2015-01-20T20:04:14Z","updated_at":"2015-01-20T21:02:59Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f43eb2aa2c1fd3fcf92f815bcf5a9f87e5902ec5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2/commits","review_comments_url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2/comments","review_comment_url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/comments/{number}","comments_url":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/2/comments","statuses_url":"https://api.github.com/repos/azavea/OpenTripPlanner/statuses/84e03f3bd6a5619a878e625b8c563b73b52c07a3","head":{"label":"flibbertigibbet:feature/shapefile-import","ref":"feature/shapefile-import","sha":"84e03f3bd6a5619a878e625b8c563b73b52c07a3","user":{"login":"flibbertigibbet","id":960264,"avatar_url":"https://avatars.githubusercontent.com/u/960264?v=3","gravatar_id":"","url":"https://api.github.com/users/flibbertigibbet","html_url":"https://github.com/flibbertigibbet","followers_url":"https://api.github.com/users/flibbertigibbet/followers","following_url":"https://api.github.com/users/flibbertigibbet/following{/other_user}","gists_url":"https://api.github.com/users/flibbertigibbet/gists{/gist_id}","starred_url":"https://api.github.com/users/flibbertigibbet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flibbertigibbet/subscriptions","organizations_url":"https://api.github.com/users/flibbertigibbet/orgs","repos_url":"https://api.github.com/users/flibbertigibbet/repos","events_url":"https://api.github.com/users/flibbertigibbet/events{/privacy}","received_events_url":"https://api.github.com/users/flibbertigibbet/received_events","type":"User","site_admin":false},"repo":{"id":9782068,"name":"OpenTripPlanner","full_name":"flibbertigibbet/OpenTripPlanner","owner":{"login":"flibbertigibbet","id":960264,"avatar_url":"https://avatars.githubusercontent.com/u/960264?v=3","gravatar_id":"","url":"https://api.github.com/users/flibbertigibbet","html_url":"https://github.com/flibbertigibbet","followers_url":"https://api.github.com/users/flibbertigibbet/followers","following_url":"https://api.github.com/users/flibbertigibbet/following{/other_user}","gists_url":"https://api.github.com/users/flibbertigibbet/gists{/gist_id}","starred_url":"https://api.github.com/users/flibbertigibbet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flibbertigibbet/subscriptions","organizations_url":"https://api.github.com/users/flibbertigibbet/orgs","repos_url":"https://api.github.com/users/flibbertigibbet/repos","events_url":"https://api.github.com/users/flibbertigibbet/events{/privacy}","received_events_url":"https://api.github.com/users/flibbertigibbet/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/flibbertigibbet/OpenTripPlanner","description":"An open source multi-modal trip planner","fork":true,"url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner","forks_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/forks","keys_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/teams","hooks_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/hooks","issue_events_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/issues/events{/number}","events_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/events","assignees_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/assignees{/user}","branches_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/branches{/branch}","tags_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/tags","blobs_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/git/refs{/sha}","trees_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/statuses/{sha}","languages_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/languages","stargazers_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/stargazers","contributors_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/contributors","subscribers_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/subscribers","subscription_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/subscription","commits_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/commits{/sha}","git_commits_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/git/commits{/sha}","comments_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/comments{/number}","issue_comment_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/issues/comments/{number}","contents_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/contents/{+path}","compare_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/merges","archive_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/downloads","issues_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/issues{/number}","pulls_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/pulls{/number}","milestones_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/milestones{/number}","notifications_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/labels{/name}","releases_url":"https://api.github.com/repos/flibbertigibbet/OpenTripPlanner/releases{/id}","created_at":"2013-04-30T23:13:32Z","updated_at":"2015-01-08T19:59:06Z","pushed_at":"2015-01-20T20:44:47Z","git_url":"git://github.com/flibbertigibbet/OpenTripPlanner.git","ssh_url":"git@github.com:flibbertigibbet/OpenTripPlanner.git","clone_url":"https://github.com/flibbertigibbet/OpenTripPlanner.git","svn_url":"https://github.com/flibbertigibbet/OpenTripPlanner","homepage":"http://www.opentripplanner.org","size":98983,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"open_issues_count":0,"forks":2,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"azavea:develop","ref":"develop","sha":"13f8f715a4b81dfbd0de0ea832aba2b72ade656e","user":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"repo":{"id":29300755,"name":"OpenTripPlanner","full_name":"azavea/OpenTripPlanner","owner":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/azavea/OpenTripPlanner","description":"An open source multi-modal trip planner","fork":true,"url":"https://api.github.com/repos/azavea/OpenTripPlanner","forks_url":"https://api.github.com/repos/azavea/OpenTripPlanner/forks","keys_url":"https://api.github.com/repos/azavea/OpenTripPlanner/keys{/key_id}","collaborators_url":"https://api.github.com/repos/azavea/OpenTripPlanner/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/azavea/OpenTripPlanner/teams","hooks_url":"https://api.github.com/repos/azavea/OpenTripPlanner/hooks","issue_events_url":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/events{/number}","events_url":"https://api.github.com/repos/azavea/OpenTripPlanner/events","assignees_url":"https://api.github.com/repos/azavea/OpenTripPlanner/assignees{/user}","branches_url":"https://api.github.com/repos/azavea/OpenTripPlanner/branches{/branch}","tags_url":"https://api.github.com/repos/azavea/OpenTripPlanner/tags","blobs_url":"https://api.github.com/repos/azavea/OpenTripPlanner/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/azavea/OpenTripPlanner/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/azavea/OpenTripPlanner/git/refs{/sha}","trees_url":"https://api.github.com/repos/azavea/OpenTripPlanner/git/trees{/sha}","statuses_url":"https://api.github.com/repos/azavea/OpenTripPlanner/statuses/{sha}","languages_url":"https://api.github.com/repos/azavea/OpenTripPlanner/languages","stargazers_url":"https://api.github.com/repos/azavea/OpenTripPlanner/stargazers","contributors_url":"https://api.github.com/repos/azavea/OpenTripPlanner/contributors","subscribers_url":"https://api.github.com/repos/azavea/OpenTripPlanner/subscribers","subscription_url":"https://api.github.com/repos/azavea/OpenTripPlanner/subscription","commits_url":"https://api.github.com/repos/azavea/OpenTripPlanner/commits{/sha}","git_commits_url":"https://api.github.com/repos/azavea/OpenTripPlanner/git/commits{/sha}","comments_url":"https://api.github.com/repos/azavea/OpenTripPlanner/comments{/number}","issue_comment_url":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/comments/{number}","contents_url":"https://api.github.com/repos/azavea/OpenTripPlanner/contents/{+path}","compare_url":"https://api.github.com/repos/azavea/OpenTripPlanner/compare/{base}...{head}","merges_url":"https://api.github.com/repos/azavea/OpenTripPlanner/merges","archive_url":"https://api.github.com/repos/azavea/OpenTripPlanner/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/azavea/OpenTripPlanner/downloads","issues_url":"https://api.github.com/repos/azavea/OpenTripPlanner/issues{/number}","pulls_url":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls{/number}","milestones_url":"https://api.github.com/repos/azavea/OpenTripPlanner/milestones{/number}","notifications_url":"https://api.github.com/repos/azavea/OpenTripPlanner/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/azavea/OpenTripPlanner/labels{/name}","releases_url":"https://api.github.com/repos/azavea/OpenTripPlanner/releases{/id}","created_at":"2015-01-15T14:22:51Z","updated_at":"2015-01-16T16:06:31Z","pushed_at":"2015-01-16T16:06:30Z","git_url":"git://github.com/azavea/OpenTripPlanner.git","ssh_url":"git@github.com:azavea/OpenTripPlanner.git","clone_url":"https://github.com/azavea/OpenTripPlanner.git","svn_url":"https://github.com/azavea/OpenTripPlanner","homepage":"http://www.opentripplanner.org","size":100529,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2"},"html":{"href":"https://github.com/azavea/OpenTripPlanner/pull/2"},"issue":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/2"},"comments":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/azavea/OpenTripPlanner/statuses/84e03f3bd6a5619a878e625b8c563b73b52c07a3"}}}}