
org.opentripplanner.netex.mapping.ServiceLinkMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
package org.opentripplanner.netex.mapping;
import java.util.Arrays;
import java.util.List;
import javax.xml.bind.JAXBElement;
import net.opengis.gml._3.LineStringType;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.graph_builder.DataImportIssueStore;
import org.opentripplanner.graph_builder.issues.MissingProjectionInServiceLink;
import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap;
import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById;
import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory;
import org.opentripplanner.transit.model.framework.EntityById;
import org.opentripplanner.transit.model.network.StopPattern;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.util.geometry.GeometryUtils;
import org.rutebanken.netex.model.JourneyPattern;
import org.rutebanken.netex.model.LinkInLinkSequence_VersionedChildStructure;
import org.rutebanken.netex.model.LinkSequenceProjection_VersionStructure;
import org.rutebanken.netex.model.ServiceLink;
import org.rutebanken.netex.model.ServiceLinkInJourneyPattern_VersionedChildStructure;
/**
* Maps NeTEx ServiceLinks to arrays of LineStrings.
*/
class ServiceLinkMapper {
private static final GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
private final FeedScopedIdFactory idFactory;
private final ReadOnlyHierarchicalMapById serviceLinkById;
private final ReadOnlyHierarchicalMap quayIdByStopPointRef;
private final EntityById stopById;
private final DataImportIssueStore issueStore;
private final double maxStopToShapeSnapDistance;
ServiceLinkMapper(
FeedScopedIdFactory idFactory,
ReadOnlyHierarchicalMapById serviceLinkById,
ReadOnlyHierarchicalMap quayIdByStopPointRef,
EntityById stopById,
DataImportIssueStore issueStore,
double maxStopToShapeSnapDistance
) {
this.idFactory = idFactory;
this.serviceLinkById = serviceLinkById;
this.quayIdByStopPointRef = quayIdByStopPointRef;
this.stopById = stopById;
this.issueStore = issueStore;
this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance;
}
List getGeometriesByJourneyPattern(
JourneyPattern journeyPattern,
StopPattern stopPattern
) {
LineString[] geometries = new LineString[stopPattern.getSize() - 1];
if (journeyPattern.getLinksInSequence() != null) {
List linksInJourneyPattern = journeyPattern
.getLinksInSequence()
.getServiceLinkInJourneyPatternOrTimingLinkInJourneyPattern();
for (int i = 0; i < linksInJourneyPattern.size(); i++) {
var linkInLinkSequence = linksInJourneyPattern.get(i);
if (
linkInLinkSequence instanceof ServiceLinkInJourneyPattern_VersionedChildStructure serviceLinkInJourneyPattern
) {
String serviceLinkRef = serviceLinkInJourneyPattern.getServiceLinkRef().getRef();
ServiceLink serviceLink = serviceLinkById.lookup(serviceLinkRef);
if (serviceLink != null) {
geometries[i] = mapServiceLink(serviceLink, stopPattern, i);
} else {
issueStore.add(
"MissingServiceLink",
"ServiceLink %s not found in journey pattern %s",
serviceLinkRef,
journeyPattern.getId()
);
}
}
}
}
// Make sure all geometries are generated
for (int i = 0; i < stopPattern.getSize() - 1; ++i) {
if (geometries[i] == null) {
geometries[i] = createSimpleGeometry(stopPattern.getStop(i), stopPattern.getStop(i + 1));
}
}
return Arrays.asList(geometries);
}
private LineString mapServiceLink(
ServiceLink serviceLink,
StopPattern stopPattern,
int stopIndex
) {
if (
serviceLink.getProjections() == null ||
serviceLink.getProjections().getProjectionRefOrProjection() == null
) {
issueStore.add(new MissingProjectionInServiceLink(serviceLink.getId()));
return null;
} else if (!isFromToPointRefsValid(serviceLink, stopPattern, stopIndex)) {
return null;
}
for (JAXBElement> projectionElement : serviceLink
.getProjections()
.getProjectionRefOrProjection()) {
Object projectionObj = projectionElement.getValue();
if (projectionObj instanceof LinkSequenceProjection_VersionStructure linkSequenceProjection) {
LineStringType lineString = linkSequenceProjection.getLineString();
if (!isProjectionValid(lineString, serviceLink.getId())) {
return null;
}
List positionList = lineString.getPosList().getValue();
Coordinate[] coordinates = new Coordinate[positionList.size() / 2];
for (int i = 0; i < positionList.size(); i += 2) {
coordinates[i / 2] = new Coordinate(positionList.get(i + 1), positionList.get(i));
}
final LineString geometry = geometryFactory.createLineString(coordinates);
if (
!isGeometryValid(geometry, serviceLink.getId()) ||
!areEndpointsWithinTolerance(
geometry,
stopPattern.getStop(stopIndex),
stopPattern.getStop(stopIndex + 1),
serviceLink.getId()
)
) {
return null;
}
return geometry;
}
}
issueStore.add(
"ServiceLinkWithoutProjection",
"Ignore ServiceLink without projection: %s",
serviceLink.getId()
);
return null;
}
/** create a 2-point linestring (a straight line segment) between the two stops */
private LineString createSimpleGeometry(StopLocation s0, StopLocation s1) {
Coordinate[] coordinates = new Coordinate[] {
s0.getCoordinate().asJtsCoordinate(),
s1.getCoordinate().asJtsCoordinate(),
};
CoordinateSequence sequence = new PackedCoordinateSequence.Double(coordinates, 2);
return geometryFactory.createLineString(sequence);
}
private boolean isFromToPointRefsValid(
ServiceLink serviceLink,
StopPattern stopPattern,
int stopIndex
) {
String fromPointQuayId = quayIdByStopPointRef.lookup(serviceLink.getFromPointRef().getRef());
RegularStop fromPointStop = stopById.get(idFactory.createId(fromPointQuayId));
String toPointQuayId = quayIdByStopPointRef.lookup(serviceLink.getToPointRef().getRef());
RegularStop toPointStop = stopById.get(idFactory.createId(toPointQuayId));
if (fromPointStop == null || toPointStop == null) {
issueStore.add(
"ServiceLinkWithoutQuay",
"Service link with missing or unknown quays. Link: %s",
serviceLink
);
return false;
} else if (!fromPointStop.equals(stopPattern.getStop(stopIndex))) {
issueStore.add(
"ServiceLinkQuayMismatch",
"Service link %s with quays different from point in journey pattern. Link point: %s, journey pattern point: %s",
serviceLink,
stopPattern.getStop(stopIndex).getId().getId(),
fromPointQuayId
);
return false;
} else if (!toPointStop.equals(stopPattern.getStop(stopIndex + 1))) {
issueStore.add(
"ServiceLinkQuayMismatch",
"Service link %s with quays different to point in journey pattern. Link point: %s, journey pattern point: %s",
serviceLink,
stopPattern.getStop(stopIndex).getId().getId(),
toPointQuayId
);
return false;
}
return true;
}
private boolean isProjectionValid(LineStringType lineString, String id) {
if (lineString == null) {
issueStore.add(
"ServiceLinkWithoutLineString",
"Ignore linkSequenceProjection without linestring for: %s",
id
);
return false;
}
List coordinates = lineString.getPosList().getValue();
if (coordinates.size() < 4) {
issueStore.add(
"ServiceLinkGeometryError",
"Ignore linkSequenceProjection with invalid linestring, " +
"containing fewer than two coordinates for: %s",
id
);
return false;
} else if (coordinates.size() % 2 != 0) {
issueStore.add(
"ServiceLinkGeometryError",
"Ignore linkSequenceProjection with invalid linestring, " +
"containing odd number of values for coordinates: %s",
id
);
return false;
}
return true;
}
private boolean isGeometryValid(Geometry geometry, String id) {
Coordinate[] coordinates = geometry.getCoordinates();
if (coordinates.length < 2) {
issueStore.add(
"ServiceLinkGeometryError",
"Ignore linkSequenceProjection with invalid linestring, " +
"containing fewer than two coordinates for: %s",
id
);
return false;
}
if (geometry.getLength() == 0) {
issueStore.add(
"ServiceLinkGeometryError",
"Ignore linkSequenceProjection with invalid linestring, having distance of 0 for: %s",
id
);
return false;
}
for (Coordinate coordinate : coordinates) {
if (Double.isNaN(coordinate.x) || Double.isNaN(coordinate.y)) {
issueStore.add(
"ServiceLinkGeometryError",
"Ignore linkSequenceProjection with invalid linestring, " +
"containing coordinate with NaN for: %s",
id
);
return false;
}
}
return true;
}
private boolean areEndpointsWithinTolerance(
Geometry geometry,
StopLocation fromStop,
StopLocation toStop,
String id
) {
Coordinate[] coordinates = geometry.getCoordinates();
Coordinate geometryStartCoordinate = coordinates[0];
Coordinate geometryEndCoordinate = coordinates[coordinates.length - 1];
Coordinate startCoordinate = fromStop.getCoordinate().asJtsCoordinate();
Coordinate endCoordinate = toStop.getCoordinate().asJtsCoordinate();
if (
SphericalDistanceLibrary.fastDistance(startCoordinate, geometryStartCoordinate) >
maxStopToShapeSnapDistance
) {
issueStore.add(
"ServiceLinkGeometryTooFar",
"Ignore linkSequenceProjection with too long distance between stop and start of linestring, " +
" stop %s, distance: %s, link id: %s",
fromStop,
SphericalDistanceLibrary.fastDistance(startCoordinate, geometryStartCoordinate),
id
);
return false;
} else if (
SphericalDistanceLibrary.fastDistance(endCoordinate, geometryEndCoordinate) >
maxStopToShapeSnapDistance
) {
issueStore.add(
"ServiceLinkGeometryTooFar",
"Ignore linkSequenceProjection with too long distance between stop and end of linestring, " +
" stop %s, distance: %s, link id: %s",
toStop,
SphericalDistanceLibrary.fastDistance(endCoordinate, geometryEndCoordinate),
id
);
return false;
}
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy