Skip to content

Commit

Permalink
feat(graphql): add graphql fetcher for shapes as encoded polylines
Browse files Browse the repository at this point in the history
  • Loading branch information
landonreed committed Nov 30, 2020
1 parent 04b0d3a commit 782adc1
Show file tree
Hide file tree
Showing 7 changed files with 568 additions and 2 deletions.
23 changes: 21 additions & 2 deletions src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.conveyal.gtfs.graphql.fetchers.JDBCFetcher;
import com.conveyal.gtfs.graphql.fetchers.MapFetcher;
import com.conveyal.gtfs.graphql.fetchers.NestedJDBCFetcher;
import com.conveyal.gtfs.graphql.fetchers.PolylineFetcher;
import com.conveyal.gtfs.graphql.fetchers.RowCountFetcher;
import com.conveyal.gtfs.graphql.fetchers.SQLColumnFetcher;
import com.conveyal.gtfs.graphql.fetchers.SourceObjectFetcher;
Expand Down Expand Up @@ -164,6 +165,12 @@ public class GraphQLGtfsSchema {
.field(MapFetcher.field("point_type", GraphQLInt))
.build();

// Represents a set of rows from shapes.txt joined by shape_id
public static final GraphQLObjectType shapeType = newObject().name("shape")
.field(string("shape_id"))
.field(string("polyline"))
.build();


// Represents rows from frequencies.txt
public static final GraphQLObjectType frequencyType = newObject().name("frequency")
Expand Down Expand Up @@ -644,6 +651,20 @@ public class GraphQLGtfsSchema {
// DataFetchers can either be class instances implementing the interface, or a static function reference
.dataFetcher(new JDBCFetcher("patterns"))
.build())
.field(newFieldDefinition()
.name("shapes")
.type(new GraphQLList(shapeType))
.argument(intArg(ID_ARG))
.argument(intArg(LIMIT_ARG))
.argument(intArg(OFFSET_ARG))
.argument(floatArg(MIN_LAT))
.argument(floatArg(MIN_LON))
.argument(floatArg(MAX_LAT))
.argument(floatArg(MAX_LON))
.argument(multiStringArg("shape_id"))
// DataFetchers can either be class instances implementing the interface, or a static function reference
.dataFetcher(new PolylineFetcher())
.build())
// Then the fields for the sub-tables within the feed (loaded directly from GTFS).
.field(newFieldDefinition()
.name("agency")
Expand Down Expand Up @@ -798,8 +819,6 @@ public class GraphQLGtfsSchema {
.newSchema()
.query(feedQuery)
// .query(patternsForStopQuery)
// TODO: Add mutations.
// .mutation(someMutation)
.build();


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.conveyal.gtfs.graphql.fetchers;

import com.conveyal.gtfs.graphql.GTFSGraphQL;
import com.conveyal.gtfs.util.PolylineUtils;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.apache.commons.dbutils.DbUtils;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* GraphQL fetcher to get encoded polylines for all shapes in a GTFS feed.
*/
public class PolylineFetcher implements DataFetcher {
public static final Logger LOG = LoggerFactory.getLogger(PolylineFetcher.class);

@Override
public Object get(DataFetchingEnvironment environment) {
GeometryFactory gf = new GeometryFactory();
List<Shape> shapes = new ArrayList<>();
Map<String, Object> parentFeedMap = environment.getSource();
String namespace = (String) parentFeedMap.get("namespace");
Connection connection = null;
try {
connection = GTFSGraphQL.getConnection();
Statement statement = connection.createStatement();
String sql = String.format(
"select shape_id, shape_pt_lon, shape_pt_lat from %s.shapes order by shape_id",
namespace
);
LOG.info("SQL: {}", sql);
if (statement.execute(sql)) {
ResultSet result = statement.getResultSet();
String currentShapeId = null;
String nextShapeId;
List<Point> shapePoints = new ArrayList<>();
while (result.next()) {
// Get values from SQL row.
nextShapeId = result.getString(1);
double lon = result.getDouble(2);
double lat = result.getDouble(3);
if (currentShapeId != null && !nextShapeId.equals(currentShapeId)) {
// Finish current shape if new shape_id is encountered.
shapes.add(new Shape(currentShapeId, shapePoints));
// Start building new shape.
shapePoints = new ArrayList<>();
}
// Update current shape_id and add shape point to list.
currentShapeId = nextShapeId;
shapePoints.add(gf.createPoint(new Coordinate(lon, lat)));
}
// Add the final shape when result iteration is finished.
shapes.add(new Shape(currentShapeId, shapePoints));
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DbUtils.closeQuietly(connection);
}
return shapes;
}

/**
* Simple class to return shapes for GraphQL response format.
*/
public static class Shape {
public String shape_id;
public String polyline;

public Shape(String shape_id, List<Point> shapePoints) {
this.shape_id = shape_id;
// Encode the shapepoints as a polyline
this.polyline = PolylineUtils.encode(shapePoints, 6);
}
}
}
Loading

0 comments on commit 782adc1

Please sign in to comment.