/* 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.routing.transit_index;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.onebusaway.gtfs.model.Route;
import org.onebusaway.gtfs.model.Stop;
import org.onebusaway.gtfs.model.Trip;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.gtfs.GtfsLibrary;
import org.opentripplanner.model.json_serialization.EncodedPolylineJSONSerializer;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.edgetype.PatternInterlineDwell;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.transit_index.adapters.LineStringAdapter;
import org.opentripplanner.routing.transit_index.adapters.StopAgencyAndIdAdapter;
import org.opentripplanner.routing.transit_index.adapters.TripsModelInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
/**
* This represents a particular stop pattern on a particular route. For example, the N train has at least four different variants: express (over the
* Manhattan bridge), and local (via lower Manhattan and the tunnel) x to Astoria and to Coney Island. During construction, it sometimes has a fifth
* variant: along the D line to Coney Island after 59th St (or from Coney Island to 59th).
*
* This is needed because route names are intended for customer information, but scheduling personnel need to know about where a particular trip
* actually goes.
*
* Variant names are guaranteed to be unique (among variants for a
* route) but not stable. They are generated to be as useful as can
* be reasonably done by machine. For instance, if a variant is the
* only variant of the N that ends at Coney Island, the name will be
* "N to Coney Island". But if multiple variants end at Coney Island
* (but have different stops elsewhere), that name would not be
* chosen. OTP also tries start and intermediate stations ("from
* Coney Island", or "via Whitehall", or even combinations ("from
* Coney Island via Whitehall"). But if there is no way to create a
* unique name from start/end/intermediate stops, then the best we can
* do is to create a "like [trip id]" name, which at least tells you
* where in the GTFS you can find a related trip.
*
* @author novalis
*
*/
@XmlRootElement(name = "RouteVariant")
public class RouteVariant implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(RouteVariant.class);
private static final long serialVersionUID = -3110443015998033630L;
/*
* This indicates that trips with multipledirection_ids are part of this variant. It should probably never be used, because generally trips making
* the same stops in the same order will have the same direction
*/
private static final String MULTIDIRECTION = "[multidirection]";
private String name; // "N via Whitehall"
private TraverseMode mode;
private ArrayList<TripsModelInfo> trips;
private ArrayList<Stop> stops;
/** An unordered list of all segments for this route */
@JsonIgnore
private ArrayList<RouteSegment> segments;
/**
* An ordered list of segments that represents one characteristic trip (or trip pattern) on this variant
*/
@JsonIgnore
private ArrayList<RouteSegment> exemplarSegments;
@JsonIgnore
private ArrayList<PatternInterlineDwell> interlines;
private Route route;
private String direction;
private LineString geometry;
public RouteVariant() {
// needed for JAXB but unused
}
public RouteVariant(Route route, ArrayList<Stop> stops) {
this.route = route;
this.stops = stops;
trips = new ArrayList<TripsModelInfo>();
segments = new ArrayList<RouteSegment>();
exemplarSegments = new ArrayList<RouteSegment>();
interlines = new ArrayList<PatternInterlineDwell>();
this.mode = GtfsLibrary.getTraverseMode(route);
}
public void addExemplarSegment(RouteSegment segment) {
exemplarSegments.add(segment);
}
public void addSegment(RouteSegment segment) {
segments.add(segment);
}
@JsonIgnore
public List<RouteSegment> getSegments() {
return segments;
}
public boolean isExemplarSet() {
return !exemplarSegments.isEmpty();
}
public void cleanup() {
trips.trimToSize();
stops.trimToSize();
exemplarSegments.trimToSize();
// topological sort on segments to make sure that they are in order
// since segments only know about their next edges, we must build a mapping from prior-edge
// to segment; while we're at it, we find the first segment.
HashMap<Edge, RouteSegment> successors = new HashMap<Edge, RouteSegment>();
RouteSegment segment = null;
for (RouteSegment s : exemplarSegments) {
if (s.hopIn == null) {
segment = s;
} else {
successors.put(s.hopIn, s);
}
}
int i = 0;
while (segment != null) {
exemplarSegments.set(i++, segment);
segment = successors.get(segment.hopOut);
}
if (i != exemplarSegments.size()) {
LOG.error("Failed to organize hops in route variant " + name);
}
}
@JsonIgnore
public List<RouteSegment> segmentsAfter(RouteSegment segment) {
HashMap<Edge, RouteSegment> successors = new HashMap<Edge, RouteSegment>();
for (RouteSegment s : segments) {
if (s.hopIn != null) {
successors.put(s.hopIn, s);
}
}
// skip this seg
segment = successors.get(segment.hopOut);
ArrayList<RouteSegment> out = new ArrayList<RouteSegment>();
while (segment != null) {
out.add(segment);
segment = successors.get(segment.hopOut);
}
return out;
}
@XmlElementWrapper
@XmlElement(name = "stop")
@XmlJavaTypeAdapter(StopAgencyAndIdAdapter.class)
public List<Stop> getStops() {
return stops;
}
public Route getRoute() {
return route;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@XmlElementWrapper
@XmlElement(name = "trip")
public List<TripsModelInfo> getTrips() {
return trips;
}
public String toString() {
return "RouteVariant(" + name + ")";
}
public void setDirection(String direction) {
this.direction = direction;
}
@XmlElement
public String getDirection() {
return direction;
}
@JsonSerialize(using = EncodedPolylineJSONSerializer.class)
@XmlJavaTypeAdapter(LineStringAdapter.class)
public LineString getGeometry() {
if (geometry == null) {
List<Coordinate> coords = new ArrayList<Coordinate>();
for (RouteSegment segment : exemplarSegments) {
if (segment.hopOut != null) {
Geometry segGeometry = segment.getGeometry();
coords.addAll(Arrays.asList(segGeometry.getCoordinates()));
}
}
Coordinate[] coordArray = new Coordinate[coords.size()];
geometry = GeometryUtils.getGeometryFactory().createLineString(
coords.toArray(coordArray));
}
return geometry;
}
@JsonIgnore
public TraverseMode getTraverseMode() {
return mode;
}
public void setGeometry(LineString geometry) {
this.geometry = geometry;
}
public void addInterline(PatternInterlineDwell dwell) {
interlines.add(dwell);
}
@JsonIgnore
public List<PatternInterlineDwell> getInterlines() {
return interlines;
}
public void addTrip(Trip trip, int number) {
this.trips.add(new TripsModelInfo(trip.getTripHeadsign(), number, trip.getServiceId()
.getId(), trip.getId()));
if (direction == null) {
direction = trip.getDirectionId();
} else {
if (!direction.equals(trip.getDirectionId())) {
direction = MULTIDIRECTION;
}
}
}
}