// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.pt_assistant.data;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.plugins.pt_assistant.utils.RouteUtils;
/**
* Creates a representation of a route relation in the pt_assistant data model,
* then maintains a list of PTStops and PTWays of a route.
*
* @author darya
*
*/
public class PTRouteDataManager {
/* The route relation */
Relation relation;
/* Stores all relation members that are PTStops */
private List<PTStop> ptstops = new ArrayList<>();
/* Stores all relation members that are PTWays */
private List<PTWay> ptways = new ArrayList<>();
/*
* Stores relation members that could not be created because they are not
* expected in the model for public_transport version 2
*/
private Set<RelationMember> failedMembers = new HashSet<>();
public PTRouteDataManager(Relation relation) throws IllegalArgumentException {
// It is assumed that the relation is a route. Build in a check here
// (e.g. from class RouteUtils) if you want to invoke this constructor
// from outside the pt_assitant SegmentChecker)
this.relation = relation;
PTStop prev = null; // stores the last created PTStop
for (RelationMember member : this.relation.getMembers()) {
if (RouteUtils.isPTStop(member)) {
// First, check if the stop already exists (i.e. there are
// consecutive elements that belong to the same stop:
boolean stopExists = false;
if (prev != null) {
if (prev.getName() == null || prev.getName().equals("") || member.getMember().get("name") == null
|| member.getMember().get("name").equals("")) {
// if there is no name, check by proximity:
// Squared distance of 0.000004 corresponds to
// around 100 m
if (this.calculateDistanceSq(member, prev) < 0.000001) {
stopExists = true;
}
} else {
// if there is a name, check by name comparison:
if (prev.getName().equalsIgnoreCase(member.getMember().get("name"))) {
stopExists = true;
}
}
}
// check if there are consecutive elements that belong to
// the same stop:
if (stopExists) {
// this PTStop already exists, so just add a new
// element:
prev.addStopElement(member);
// TODO: something may need to be done if adding the
// element
// did not succeed. The failure is a result of the same
// stop
// having >1 stop_position, platform or stop_area.
} else {
// this PTStop does not exist yet, so create it:
try {
PTStop ptstop = new PTStop(member);
ptstops.add(ptstop);
prev = ptstop;
} catch (IllegalArgumentException ex) {
if (ex.getMessage().equals(
"The RelationMember type does not match its role " + member.getMember().getName())) {
if (!failedMembers.contains(member)) {
failedMembers.add(member);
}
} else {
throw ex;
}
}
}
} else if (RouteUtils.isPTWay(member)) {
PTWay ptway = new PTWay(member);
ptways.add(ptway);
} else {
if (!failedMembers.contains(member)) {
failedMembers.add(member);
}
}
}
}
/**
* Calculates the squared distance between the centers of bounding boxes of
* two relation members (which are supposed to be platforms or
* stop_positions)
*
* @param member1 first member
* @param member2 second member
* @return Squared distance between the centers of the bounding boxes of the
* given relation members
*/
private double calculateDistanceSq(RelationMember member1, RelationMember member2) {
LatLon coord1 = member1.getMember().getBBox().getCenter();
LatLon coord2 = member2.getMember().getBBox().getCenter();
return coord1.distanceSq(coord2);
}
/**
* Assigns the given way to a PTWay of this route relation. If multiple
* PTWays contain the same inputWay, the first found PTWay is returned.
*
* @param inputWay
* Way to be assigned to a PTWAy of this route relation
* @return PTWay that contains the geometry of the inputWay, null if not
* found
*/
public PTWay getPTWay(Way inputWay) {
for (PTWay curr : ptways) {
if (curr.isWay() && curr.getWays().get(0) == inputWay) {
return curr;
}
if (curr.isRelation()) {
for (RelationMember rm : curr.getRelation().getMembers()) {
Way wayInNestedRelation = rm.getWay();
if (wayInNestedRelation == inputWay) {
return curr;
}
}
}
}
return null; // if not found
}
public List<PTStop> getPTStops() {
return this.ptstops;
}
public List<PTWay> getPTWays() {
return this.ptways;
}
public int getPTStopCount() {
return ptstops.size();
}
public int getPTWayCount() {
return this.ptways.size();
}
public PTStop getFirstStop() {
if (this.ptstops.isEmpty()) {
return null;
}
return this.ptstops.get(0);
}
public PTStop getLastStop() {
if (this.ptstops.isEmpty()) {
return null;
}
return this.ptstops.get(ptstops.size() - 1);
}
public Set<RelationMember> getFailedMembers() {
return this.failedMembers;
}
/**
* Returns the route relation for which this manager was created:
*
* @return the route relation for which this manager was created
*/
public Relation getRelation() {
return this.relation;
}
/**
* Returns a PTStop that matches the given id. Returns null if not found
*
* @param id identifier
* @return a PTStop that matches the given id. Returns null if not found
*/
public PTStop getPTStop(long id) {
for (PTStop stop : this.ptstops) {
if (stop.getStopPosition() != null && stop.getStopPosition().getId() == id) {
return stop;
}
if (stop.getPlatform() != null && stop.getPlatform().getId() == id) {
return stop;
}
}
return null;
}
/**
* Returns a PTWay that matches the given id. Returns null if not found
*
* @param id identifier
* @return a PTWay that matches the given id. Returns null if not found
*/
public PTWay getPTWay(long id) {
for (PTWay ptway : this.ptways) {
for (Way way : ptway.getWays()) {
if (way.getId() == id) {
return ptway;
}
}
}
return null;
}
/**
* Returns all PTWays of this route that contain the given way.
*
* @param way way
* @return all PTWays of this route that contain the given way
*/
public List<PTWay> findPTWaysThatContain(Way way) {
List<PTWay> ptwaysThatContain = new ArrayList<>();
for (PTWay ptway : ptways) {
if (ptway.getWays().contains(way)) {
ptwaysThatContain.add(ptway);
}
}
return ptwaysThatContain;
}
/**
* Returns all PTWays of this route that contain the given node as their
* first or last node.
*
* @return all PTWays of this route that contain the given node as their
* first or last node
*/
public List<PTWay> findPTWaysThatContainAsEndNode(Node node) {
List<PTWay> ptwaysThatContain = new ArrayList<>();
for (PTWay ptway : ptways) {
List<Way> ways = ptway.getWays();
if (ways.get(0).firstNode() == node || ways.get(0).lastNode() == node
|| ways.get(ways.size() - 1).firstNode() == node || ways.get(ways.size() - 1).lastNode() == node) {
ptwaysThatContain.add(ptway);
}
}
return ptwaysThatContain;
}
/**
* Checks if at most one PTWay of this route refers to the given node
*
* @param node node
* @return {@code true} if at most one PTWay of this route refers to the given node
*/
public boolean isDeadendNode(Node node) {
List<PTWay> referringPtways = this.findPTWaysThatContainAsEndNode(node);
if (referringPtways.size() <= 1) {
return true;
}
return false;
}
/**
* Returns the PTWay which comes directly after the given ptway according to
* the existing route member sorting
*
* @param ptway way
* @return the PTWay which comes directly after the given ptway according to
* the existing route member sorting
*/
public PTWay getNextPTWay(PTWay ptway) {
for (int i = 0; i < ptways.size() - 1; i++) {
if (ptways.get(i) == ptway) {
return ptways.get(i + 1);
}
}
return null;
}
/**
* Returns the PTWay which comes directly before the given ptway according
* to the existing route member sorting
*
* @param ptway way
* @return the PTWay which comes directly before the given ptway according
* to the existing route member sorting
*/
public PTWay getPreviousPTWay(PTWay ptway) {
for (int i = 1; i < ptways.size(); i++) {
if (ptways.get(i) == ptway) {
return ptways.get(i - 1);
}
}
return null;
}
/**
* Returns a sequence of PTWays that are between the start way and the end
* way. The resulting list includes the start and end PTWays.
*
* @param start start way
* @param end end way
* @return a sequence of PTWays that are between the start way and the end way
*/
public List<PTWay> getPTWaysBetween(Way start, Way end) {
List<Integer> potentialStartIndices = new ArrayList<>();
List<Integer> potentialEndIndices = new ArrayList<>();
for (int i = 0; i < ptways.size(); i++) {
if (ptways.get(i).getWays().contains(start)) {
potentialStartIndices.add(i);
}
if (ptways.get(i).getWays().contains(end)) {
potentialEndIndices.add(i);
}
}
List<int[]> pairList = new ArrayList<>();
for (Integer potentialStartIndex : potentialStartIndices) {
for (Integer potentialEndIndex : potentialEndIndices) {
if (potentialStartIndex <= potentialEndIndex) {
int[] pair = {potentialStartIndex, potentialEndIndex};
pairList.add(pair);
}
}
}
int minDifference = Integer.MAX_VALUE;
int[] mostSuitablePair = {0, 0};
for (int[] pair : pairList) {
int diff = pair[1] - pair[0];
if (diff < minDifference) {
minDifference = diff;
mostSuitablePair = pair;
}
}
List<PTWay> result = new ArrayList<>();
for (int i = mostSuitablePair[0]; i <= mostSuitablePair[1]; i++) {
result.add(ptways.get(i));
}
return result;
}
/**
* Returns the common Node of two PTWays or null if there is no common Node.
* If there is more than one common Node, only the first found is returned.
*
* @param way1 first way
* @param way2 second way
* @return the common Node of two PTWays or null if there is no common Node
*/
public Node getCommonNode(PTWay way1, PTWay way2) {
List<Way> wayList1 = way1.getWays();
List<Way> wayList2 = way2.getWays();
HashSet<Node> nodeSet1 = new HashSet<>();
for (Way w : wayList1) {
nodeSet1.addAll(w.getNodes());
}
HashSet<Node> nodeSet2 = new HashSet<>();
for (Way w : wayList2) {
nodeSet2.addAll(w.getNodes());
}
for (Node n : nodeSet1) {
if (nodeSet2.contains(n)) {
return n;
}
}
return null;
}
/**
* Returns the last way of this route
*
* @return the last way of this route
*/
public Way getLastWay() {
PTWay lastPTWay = this.ptways.get(ptways.size() - 1);
if (lastPTWay == null) {
return null;
}
return lastPTWay.getWays().get(lastPTWay.getWays().size() - 1);
}
}