diff --git a/package.json b/package.json index 4420b20eec5..e52fdc52fb4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "opentripplanner", - "version": "1.5.17", + "version": "1.5.20", "description": "OpenTripPlanner (OTP) is an open source multi-modal trip planner. It depends on open data in open standard file formats (GTFS and OpenStreetMap), and includes a REST API for journey planning as well as a map-based Javascript client. OpenTripPlanner can also create travel time contour visualizations and compute accessibility indicators for planning and research applications.", "main": "index.js", "directories": { @@ -18,7 +18,7 @@ "scripts": { "deploy": "mvn deploy:deploy-file -s openmove-settings.xml -DgroupId=org.opentripplanner -DartifactId=otp -Dversion=${npm_package_version}-SNAPSHOT -Dclassifier=shaded -Dpackaging=jar -Dfile=target/otp-${npm_package_version}-SNAPSHOT-shaded.jar -Durl=https://maven.pkg.github.com/openmove/OpenTripPlanner -DrepositoryId=otp", "build": "mvn clean package -DskipTests -s openmove-settings.xml", - "debug": "java -Xmx5G -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:32935,suspend=y,server=y -jar target/otp-${npm_package_version}-SNAPSHOT-shaded.jar --build ../otp/otp/debug/ --inMemory " + "debug": "java -Xmx5G -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:32935,suspend=y,server=y -jar target/otp-${npm_package_version}-SNAPSHOT-shaded.jar --build ../otp/otp/napoli-test/ --inMemory " }, "homepage": "https://github.com/openmove/OpenTripPlanner#readme" } diff --git a/pom.xml b/pom.xml index e1ba88853b0..c5d03b56139 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.opentripplanner otp - 1.5.17-SNAPSHOT + 1.5.20-SNAPSHOT jar diff --git a/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java b/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java index 5d68045f411..bcb4cc4a787 100644 --- a/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java +++ b/src/main/java/org/opentripplanner/index/IndexGraphQLSchema.java @@ -38,6 +38,7 @@ import org.opentripplanner.api.model.TripPlan; import org.opentripplanner.api.model.VertexType; import org.opentripplanner.api.model.WalkStep; +import org.opentripplanner.common.geometry.SphericalDistanceLibrary; import org.opentripplanner.common.model.P2; import org.opentripplanner.gtfs.GtfsLibrary; import org.opentripplanner.index.model.DestinationType; @@ -238,6 +239,28 @@ public String parseLiteral(Object input) { .value("WARNING", GtfsRealtime.Alert.SeverityLevel.WARNING, "Warning alerts are used when a single stop or route has a disruption that can affect user's journey, for example: All trams on a specific route are running with irregular schedules.") .value("SEVERE", GtfsRealtime.Alert.SeverityLevel.SEVERE, "Severe alerts are used when a significant part of public transport services is affected, for example: All train services are cancelled due to technical problems.") .build(); + + public static GraphQLEnumType congestionLevelEnum = GraphQLEnumType.newEnum() + .name("CongestionLevelType") + .description("Congestion level of a vehicle") + .value("UNKNOWN_CONGESTION_LEVEL", GtfsRealtime.VehiclePosition.CongestionLevel.UNKNOWN_CONGESTION_LEVEL, "Congestion is unknown") + .value("RUNNING_SMOOTHLY", GtfsRealtime.VehiclePosition.CongestionLevel.RUNNING_SMOOTHLY, "No congestion") + .value("STOP_AND_GO", GtfsRealtime.VehiclePosition.CongestionLevel.STOP_AND_GO, "The vehicle stop and go") + .value("CONGESTION", GtfsRealtime.VehiclePosition.CongestionLevel.CONGESTION, "There is a significant congestion") + .value("SEVERE_CONGESTION", GtfsRealtime.VehiclePosition.CongestionLevel.SEVERE_CONGESTION, "People are leaving their cars!") + .build(); + + public static GraphQLEnumType occupancyStatusEnum = GraphQLEnumType.newEnum() + .name("OccupancyStatusType") + .description("Occupancy status of a vehicle") + .value("EMPTY", GtfsRealtime.VehiclePosition.OccupancyStatus.EMPTY, "The vehicle is considered empty by most measures, and has few or no passengers onboard, but is still accepting passengers.") + .value("MANY_SEATS_AVAILABLE", GtfsRealtime.VehiclePosition.OccupancyStatus.MANY_SEATS_AVAILABLE, "The vehicle has a relatively large percentage of seats available.") + .value("FEW_SEATS_AVAILABLE", GtfsRealtime.VehiclePosition.OccupancyStatus.FEW_SEATS_AVAILABLE, "The vehicle has a relatively small percentage of seats available.") + .value("STANDING_ROOM_ONLY", GtfsRealtime.VehiclePosition.OccupancyStatus.STANDING_ROOM_ONLY, "The vehicle can currently accommodate only standing passengers.") + .value("CRUSHED_STANDING_ROOM_ONLY", GtfsRealtime.VehiclePosition.OccupancyStatus.CRUSHED_STANDING_ROOM_ONLY, "The vehicle can currently accommodate only standing passengers and has limited space for them.") + .value("FULL", GtfsRealtime.VehiclePosition.OccupancyStatus.FULL, "The vehicle is considered full by most measures, but may still be allowing passengers to board.") + .value("NOT_ACCEPTING_PASSENGERS", GtfsRealtime.VehiclePosition.OccupancyStatus.NOT_ACCEPTING_PASSENGERS, "The vehicle is not accepting additional passengers.") + .build(); private final GtfsRealtimeFuzzyTripMatcher fuzzyTripMatcher; @@ -1221,9 +1244,16 @@ public IndexGraphQLSchema(GraphIndex index) { vehiclePositionType = GraphQLObjectType.newObject() .name("VehiclePosition") .withInterface(nodeInterface) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("id") + .type(new GraphQLNonNull(Scalars.GraphQLID)) + .dataFetcher(environment -> relay.toGlobalId( + vehiclePositionType.getName(), + ((RealtimeVehiclePosition) environment.getSource()).vehicleId)) + .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("vehicleId") - .type(new GraphQLNonNull(Scalars.GraphQLString)) + .type(Scalars.GraphQLString) .dataFetcher(environment -> ((RealtimeVehiclePosition) environment.getSource()).vehicleId) .build()) .field(GraphQLFieldDefinition.newFieldDefinition() @@ -1256,6 +1286,35 @@ public IndexGraphQLSchema(GraphIndex index) { .type(Scalars.GraphQLLong) .dataFetcher(environment -> ((RealtimeVehiclePosition) environment.getSource()).seconds) .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("trip") + .type(tripType) + .dataFetcher(environment -> { + FeedScopedId tripId = ((RealtimeVehiclePosition) environment.getSource()).tripId; + return index.tripForId.get(tripId); + }) + .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("congestionLevel") + .type(congestionLevelEnum) + .dataFetcher(environment -> { + return ((RealtimeVehiclePosition) environment.getSource()).congestionLevel; + }) + .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("occupancyStatus") + .type(occupancyStatusEnum) + .dataFetcher(environment -> { + return ((RealtimeVehiclePosition) environment.getSource()).occupancyStatus; + }) + .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("occupancyPercentage") + .type(Scalars.GraphQLInt) + .dataFetcher(environment -> { + return ((RealtimeVehiclePosition) environment.getSource()).occupancyPercentage; + }) + .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("stoptime") .type(stoptimeType) @@ -1357,11 +1416,15 @@ public IndexGraphQLSchema(GraphIndex index) { .dataFetcher( environment -> { Trip trip = (Trip) environment.getSource(); - return index.patternForTrip.get(trip) + List rtp = index.patternForTrip.get(trip) .getVehiclePositions() .stream() .filter(vp -> vp.tripId.equals(trip.getId())) .collect(Collectors.toList()); + if(rtp.isEmpty()) { + return null; + } + return rtp.get(0); } ) .build()) @@ -1572,6 +1635,18 @@ public int compare(TripTimeShort tripTimeShort, TripTimeShort t1) { .name("stops") .type(new GraphQLList(new GraphQLNonNull(stopType))) .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("realtimeVehiclePositions") + .type(new GraphQLList(vehiclePositionType)) + .dataFetcher( + environment -> { + return ((TripPattern) environment.getSource()) + .getVehiclePositions() + .stream() + .collect(Collectors.toList()); + } + ) + .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("geometry") .type(Scalars.GraphQLString) @@ -2868,6 +2943,57 @@ public int compare(TripTimeShort tripTimeShort, TripTimeShort t1) { .collect(Collectors.toList()) )) .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("realtimeVehiclePositions") + .description("Get all vehicle positions for the specified graph") + .type(new GraphQLList(vehiclePositionType)) + .argument(GraphQLArgument.newArgument() + .name("lat") + .description("Latitude of the location") + .type(Scalars.GraphQLFloat) + .build()) + .argument(GraphQLArgument.newArgument() + .name("lon") + .description("Longitude of the location") + .type(Scalars.GraphQLFloat) + .build()) + .argument(GraphQLArgument.newArgument() + .name("radius") + .description("Radius (in meters) to search for from the specidied location") + .type(Scalars.GraphQLInt) + .build()) + .dataFetcher(environment -> { + return index.patternForId.values() + .stream() + .flatMap(p -> p.getVehiclePositions().stream().filter( + v -> { + Coordinate coord = null; + int radius = 0; + if(environment.getArgument("lon") != null && environment.getArgument("lat") != null && environment.getArgument("radius") != null) { + + double lon = environment.getArgument("lon"); + double lat = environment.getArgument("lat"); + radius = Math.toIntExact(environment.getArgument("radius")); + + coord = new Coordinate(lon, lat); + } + if(coord != null) { + double distance = SphericalDistanceLibrary.fastDistance(new Coordinate(v.lon, v.lat), coord); + + if (distance < radius) { + return true; + } else { + return false; + } + }else { + return true; + } + + } + )) + .collect(Collectors.toList()); + }) + .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("trip") .description("Get a single trip based on its id (format is Agency:TripId)") @@ -2951,6 +3077,38 @@ public int compare(TripTimeShort tripTimeShort, TripTimeShort t1) { ) ) .build()) + .field(GraphQLFieldDefinition.newFieldDefinition() + .name("clustersByBbox") + .description("Get all clusters for the specified graph in a bounding box") + .type(new GraphQLList(clusterType)) + .argument(GraphQLArgument.newArgument() + .name("minLat") + .type(Scalars.GraphQLFloat) + .build()) + .argument(GraphQLArgument.newArgument() + .name("minLon") + .type(Scalars.GraphQLFloat) + .build()) + .argument(GraphQLArgument.newArgument() + .name("maxLat") + .type(Scalars.GraphQLFloat) + .build()) + .argument(GraphQLArgument.newArgument() + .name("maxLon") + .type(Scalars.GraphQLFloat) + .build()) + .dataFetcher(environment -> { + Envelope envelope = new Envelope( + new Coordinate(environment.getArgument("minLon"), environment.getArgument("minLat")), + new Coordinate(environment.getArgument("maxLon"), environment.getArgument("maxLat"))); + return new ArrayList<>( + index.stopClusterForId.values() + .stream() + .filter(c -> envelope.contains(new Coordinate(c.lon,c.lat))) + .collect(Collectors.toList()) + ); + }) + .build()) .field(GraphQLFieldDefinition.newFieldDefinition() .name("cluster") .description("Get a single cluster based on its id") diff --git a/src/main/java/org/opentripplanner/index/model/RealtimeVehiclePosition.java b/src/main/java/org/opentripplanner/index/model/RealtimeVehiclePosition.java index fbac97663b6..52d78f964e9 100644 --- a/src/main/java/org/opentripplanner/index/model/RealtimeVehiclePosition.java +++ b/src/main/java/org/opentripplanner/index/model/RealtimeVehiclePosition.java @@ -1,6 +1,7 @@ package org.opentripplanner.index.model; import com.google.transit.realtime.GtfsRealtime.VehiclePosition.CongestionLevel; +import com.google.transit.realtime.GtfsRealtime.VehiclePosition.OccupancyStatus; import com.google.transit.realtime.GtfsRealtime.VehiclePosition.VehicleStopStatus; import org.opentripplanner.model.FeedScopedId; @@ -28,4 +29,8 @@ public class RealtimeVehiclePosition { public int nextStopSequenceId; public CongestionLevel congestionLevel; + public OccupancyStatus occupancyStatus; + + public int occupancyPercentage; + } diff --git a/src/main/java/org/opentripplanner/routing/edgetype/ParkAndRideEdge.java b/src/main/java/org/opentripplanner/routing/edgetype/ParkAndRideEdge.java index 57bc99d6dd0..94e624d87f9 100644 --- a/src/main/java/org/opentripplanner/routing/edgetype/ParkAndRideEdge.java +++ b/src/main/java/org/opentripplanner/routing/edgetype/ParkAndRideEdge.java @@ -9,7 +9,6 @@ import org.opentripplanner.routing.vertextype.ParkAndRideVertex; import org.locationtech.jts.geom.LineString; -import org.opentripplanner.updater.car_park.ODHCarParkDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java index fd540d03334..2e8cf261f29 100644 --- a/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java +++ b/src/main/java/org/opentripplanner/routing/impl/GraphPathFinder.java @@ -53,7 +53,7 @@ public class GraphPathFinder { private static final Logger LOG = LoggerFactory.getLogger(GraphPathFinder.class); private static final double DEFAULT_MAX_WALK = 2000; - private static final double CLAMP_MAX_WALK = 15000; + private static final double CLAMP_MAX_WALK = 150000; Router router; diff --git a/src/main/java/org/opentripplanner/updater/car_park/CarParkUpdater.java b/src/main/java/org/opentripplanner/updater/car_park/CarParkUpdater.java index 3193ef6804a..79ee46931a8 100644 --- a/src/main/java/org/opentripplanner/updater/car_park/CarParkUpdater.java +++ b/src/main/java/org/opentripplanner/updater/car_park/CarParkUpdater.java @@ -92,6 +92,9 @@ protected void configurePolling(Graph graph, JsonNode config) throws Exception { if (sourceType.equals("park-and-ride")) { source = new ODHCarParkDataSource(); } + if (sourceType.equals("park-openmove")) { + source = new OMCarParkDataSource(); + } } if (source == null) { diff --git a/src/main/java/org/opentripplanner/updater/car_park/OMCarParkDataSource.java b/src/main/java/org/opentripplanner/updater/car_park/OMCarParkDataSource.java new file mode 100644 index 00000000000..ca9e94c363a --- /dev/null +++ b/src/main/java/org/opentripplanner/updater/car_park/OMCarParkDataSource.java @@ -0,0 +1,211 @@ +package org.opentripplanner.updater.car_park; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.geotools.geometry.jts.JTS; +import org.geotools.referencing.CRS; +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.LinearRing; +import org.locationtech.jts.geom.Polygon; +import org.opengis.geometry.MismatchedDimensionException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; +import org.opengis.referencing.operation.TransformException; +import org.opentripplanner.routing.car_park.CarPark; +import org.opentripplanner.routing.graph.Graph; +import org.opentripplanner.util.HttpUtils; +import org.opentripplanner.util.NonLocalizedString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Load car parks from the ODH Park API. + * + * @author fede + */ +public class OMCarParkDataSource extends GenericJsonCarParkDataSource{ + + private static final Logger log = LoggerFactory.getLogger(ODHCarParkDataSource.class); + + private GeometryFactory gf = new GeometryFactory(); + + public OMCarParkDataSource() { + super("stations"); + } + + public CarPark makeCarPark(JsonNode node) { + if (node.path("station_id").isMissingNode()) return null; + + CarPark station = new CarPark(); + station.id = node.path("station_id").asText(); + station.name = new NonLocalizedString(node.path("name").asText()); + try { + station.geometry = parseGeometry(node.path("geometry")); + station.y = station.geometry.getCentroid().getY(); + station.x = station.geometry.getCentroid().getX(); + //station.realTimeData = node.path("realtime").asBoolean(); + station.maxCapacity = node.path("capacity").asInt(); +// String stationStatus = node.path("status").asText(); +// if (stationStatus.equals("INACTIVE") || stationStatus.equals("TEMPORARILY_CLOSED")) { +// return null; +// } else if (station.realTimeData) { +// station.spacesAvailable = node.path("free").asInt(); +// station.spacesForecast.put(0, station.spacesAvailable); +// } else { +// station.spacesAvailable = station.maxCapacity; +// station.spacesForecast.put(0, station.spacesAvailable); +// } + if(node.path("free").isNull()) { + station.spacesAvailable = station.maxCapacity; + station.spacesForecast.put(0, station.spacesAvailable); + }else { + station.spacesAvailable = node.path("free").asInt(); + station.spacesForecast.put(0, station.spacesAvailable); + } + + if(node.has("forecasts") && node.path("forecasts").isArray()){ + int number = 15; + int counter = 1; + for(JsonNode forecast : node.path("forecasts")){ + int forecastInt = forecast.asInt(); + station.spacesForecast.put((counter * number), forecastInt); + } + } + + return station; + } catch (Exception e) { + log.warn("Error parsing car park " + station.id, e); + return null; + } + } + + public boolean update() { + return super.update(); + } + + + /** + * Note that the JSON being passed in here is for configuration of the OTP component, it's completely separate + * from the JSON coming in from the update source. + */ + @Override + public void configure (Graph graph, JsonNode jsonNode) { + super.configure(graph, jsonNode); + } + + // TODO: These are inlined from GeometryDeserializer + private Geometry parseGeometry(JsonNode root) { + String typeName = root.get("type").asText(); + if(typeName.equals("Point")) { + Geometry sourceGeometry = this.gf.createPoint(this.parseCoordinate(root.get("coordinates"))); + + try { + CoordinateReferenceSystem sourceCRS = CRS.decode("EPSG:4326"); + CoordinateReferenceSystem targetCRS = CRS.decode("EPSG:3857"); + MathTransform transform = CRS.findMathTransform(sourceCRS, targetCRS); + MathTransform transform2 = CRS.findMathTransform(targetCRS, sourceCRS); + Geometry targetGeometry = JTS.transform( sourceGeometry, transform); + Geometry bufferedTargetGeometry = targetGeometry.buffer(50); + return JTS.transform( bufferedTargetGeometry, transform2); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return sourceGeometry; + } else if(typeName.equals("MultiPoint")) { + return this.gf.createMultiPoint(this.parseLineString(root.get("coordinates"))); + } else if(typeName.equals("LineString")) { + return this.gf.createLineString(this.parseLineString(root.get("coordinates"))); + } else if(typeName.equals("MultiLineString")) { + return this.gf.createMultiLineString(this.parseLineStrings(root.get("coordinates"))); + } else { + JsonNode arrayOfPolygons; + if(typeName.equals("Polygon")) { + arrayOfPolygons = root.get("coordinates"); + return this.parsePolygonCoordinates(arrayOfPolygons); + } else if(typeName.equals("MultiPolygon")) { + arrayOfPolygons = root.get("coordinates"); + return this.gf.createMultiPolygon(this.parsePolygons(arrayOfPolygons)); + } else if(typeName.equals("GeometryCollection")) { + return this.gf.createGeometryCollection(this.parseGeometries(root.get("geometries"))); + } else { + throw new UnsupportedOperationException(); + } + } + } + + private Geometry[] parseGeometries(JsonNode arrayOfGeoms) { + Geometry[] items = new Geometry[arrayOfGeoms.size()]; + + for(int i = 0; i != arrayOfGeoms.size(); ++i) { + items[i] = this.parseGeometry(arrayOfGeoms.get(i)); + } + + return items; + } + + private Polygon parsePolygonCoordinates(JsonNode arrayOfRings) { + return this.gf.createPolygon(this.parseExteriorRing(arrayOfRings), this.parseInteriorRings(arrayOfRings)); + } + + private Polygon[] parsePolygons(JsonNode arrayOfPolygons) { + Polygon[] polygons = new Polygon[arrayOfPolygons.size()]; + + for(int i = 0; i != arrayOfPolygons.size(); ++i) { + polygons[i] = this.parsePolygonCoordinates(arrayOfPolygons.get(i)); + } + + return polygons; + } + + private LinearRing parseExteriorRing(JsonNode arrayOfRings) { + return this.gf.createLinearRing(this.parseLineString(arrayOfRings.get(0))); + } + + private LinearRing[] parseInteriorRings(JsonNode arrayOfRings) { + LinearRing[] rings = new LinearRing[arrayOfRings.size() - 1]; + + for(int i = 1; i < arrayOfRings.size(); ++i) { + rings[i - 1] = this.gf.createLinearRing(this.parseLineString(arrayOfRings.get(i))); + } + + return rings; + } + + private Coordinate parseCoordinate(JsonNode array) { + return new Coordinate(array.get(0).asDouble(), array.get(1).asDouble()); + } + + private Coordinate[] parseLineString(JsonNode array) { + Coordinate[] points = new Coordinate[array.size()]; + + for(int i = 0; i != array.size(); ++i) { + points[i] = this.parseCoordinate(array.get(i)); + } + + return points; + } + + private LineString[] parseLineStrings(JsonNode array) { + LineString[] strings = new LineString[array.size()]; + + for(int i = 0; i != array.size(); ++i) { + strings[i] = this.gf.createLineString(this.parseLineString(array.get(i))); + } + + return strings; + } +} + diff --git a/src/main/java/org/opentripplanner/updater/vehicle_positions/VehiclePositionPatternMatcher.java b/src/main/java/org/opentripplanner/updater/vehicle_positions/VehiclePositionPatternMatcher.java index a88a50039e4..aaecd43cfb1 100644 --- a/src/main/java/org/opentripplanner/updater/vehicle_positions/VehiclePositionPatternMatcher.java +++ b/src/main/java/org/opentripplanner/updater/vehicle_positions/VehiclePositionPatternMatcher.java @@ -17,6 +17,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -126,6 +127,16 @@ private RealtimeVehiclePosition parseVehiclePosition(VehiclePosition vehiclePosi } newPosition.vehicleId = vehiclePosition.getVehicle().getId(); } + + if(newPosition.vehicleId == null) { + if(vehiclePosition.hasTrip()) { + newPosition.vehicleId = vehiclePosition.getTrip().getTripId(); + }else { + UUID uuid = UUID.randomUUID(); + String uuidAsString = uuid.toString(); + newPosition.vehicleId = uuidAsString; + } + } if (vehiclePosition.hasCurrentStatus()) { newPosition.stopStatus = vehiclePosition.getCurrentStatus(); @@ -134,6 +145,13 @@ private RealtimeVehiclePosition parseVehiclePosition(VehiclePosition vehiclePosi if (vehiclePosition.hasCongestionLevel()) { newPosition.congestionLevel = vehiclePosition.getCongestionLevel(); } + + if (vehiclePosition.hasOccupancyStatus()) { + newPosition.occupancyStatus = vehiclePosition.getOccupancyStatus(); + } + if (vehiclePosition.hasOccupancyPercentage()) { + newPosition.occupancyPercentage = vehiclePosition.getOccupancyPercentage(); + } if (vehiclePosition.hasTimestamp()) { newPosition.seconds = vehiclePosition.getTimestamp();