All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.netex.mapping.NetexMapper Maven / Gradle / Ivy

package org.opentripplanner.netex.mapping;

import com.google.common.collect.Multimap;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.xml.bind.JAXBElement;
import org.opentripplanner.graph_builder.DataImportIssueStore;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.calendar.ServiceCalendar;
import org.opentripplanner.model.impl.OtpTransitServiceBuilder;
import org.opentripplanner.netex.index.api.NetexEntityIndexReadOnlyView;
import org.opentripplanner.netex.mapping.calendar.CalendarServiceBuilder;
import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory;
import org.opentripplanner.netex.mapping.support.NetexMapperIndexes;
import org.opentripplanner.transit.model.basic.Notice;
import org.opentripplanner.transit.model.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.GroupOfRoutes;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.Trip;
import org.rutebanken.netex.model.Authority;
import org.rutebanken.netex.model.Branding;
import org.rutebanken.netex.model.FlexibleLine;
import org.rutebanken.netex.model.FlexibleStopPlace;
import org.rutebanken.netex.model.GroupOfStopPlaces;
import org.rutebanken.netex.model.JourneyPattern;
import org.rutebanken.netex.model.Line;
import org.rutebanken.netex.model.LineRefs_RelStructure;
import org.rutebanken.netex.model.NoticeAssignment;
import org.rutebanken.netex.model.StopPlace;
import org.rutebanken.netex.model.VersionOfObjectRefStructure;

/**
 * 

* This is the ROOT mapper to map from the Netex domin model into the OTP internal model. This class * delegates to type/argegate specific mappers and take the result from each such mapper and add the * result to the {@link OtpTransitServiceBuilder}. *

*

* The transit builder is updated with the new OTP model entities, holding ALL entities parsed so * fare including previous Netex files in the same bundle. This enable the mapping code to make * direct references between entities in the OTP domain model. *

*/ public class NetexMapper { private static final int LEVEL_SHARED = 0; private static final int LEVEL_GROUP = 1; private final FeedScopedIdFactory idFactory; private final OtpTransitServiceBuilder transitBuilder; private final Deduplicator deduplicator; private final DataImportIssueStore issueStore; private final CalendarServiceBuilder calendarServiceBuilder; private final TripCalendarBuilder tripCalendarBuilder; private final Set ferryIdsNotAllowedForBicycle; private final double maxStopToShapeSnapDistance; private final boolean noTransfersOnIsolatedStops; /** Map entries that cross reference entities within a group/operator, for example Interchanges. */ private GroupNetexMapper groupMapper; /** All read netex entities by their id */ private NetexEntityIndexReadOnlyView currentNetexIndex; /** * Shared/cached entity index, used by more than one mapper. This index provides alternative * indexes to netex entites, as well as global indexes to OTP domain objects needed in the mapping * process. Some of these indexes are feed scoped, and some are file group level scoped. As a rule * of tomb the indexes for OTP Model entities are global(small memory overhead), while the indexes * for the Netex entities follow the main index {@link #currentNetexIndex}, hence sopped by file * group. */ private NetexMapperIndexes currentMapperIndexes = null; private int level = LEVEL_SHARED; public NetexMapper( OtpTransitServiceBuilder transitBuilder, String feedId, Deduplicator deduplicator, DataImportIssueStore issueStore, Set ferryIdsNotAllowedForBicycle, double maxStopToShapeSnapDistance, boolean noTransfersOnIsolatedStops ) { this.transitBuilder = transitBuilder; this.deduplicator = deduplicator; this.idFactory = new FeedScopedIdFactory(feedId); this.issueStore = issueStore; this.ferryIdsNotAllowedForBicycle = ferryIdsNotAllowedForBicycle; this.noTransfersOnIsolatedStops = noTransfersOnIsolatedStops; this.maxStopToShapeSnapDistance = maxStopToShapeSnapDistance; this.calendarServiceBuilder = new CalendarServiceBuilder(idFactory); this.tripCalendarBuilder = new TripCalendarBuilder(this.calendarServiceBuilder, issueStore); } /** * Prepare to for mapping of a new sub-level of entities(shared-files, shared-group-files and * group-files). This is a life-cycle method used to notify this class that a new dataset is about * to be processed. Any existing intermediate state must be saved, so it can be accessed during * the next call to {@link #mapNetexToOtp(NetexEntityIndexReadOnlyView)} and after. */ public NetexMapper push() { ++level; this.tripCalendarBuilder.push(); setupGroupMapping(); return this; } /** * It is now safe to discard any intermediate state generated by the last call to {@link * #mapNetexToOtp(NetexEntityIndexReadOnlyView)}. */ public NetexMapper pop() { performGroupMapping(); this.tripCalendarBuilder.pop(); // A new mapper is created for every call to {@link #mapNetexToOtp} this.currentMapperIndexes = currentMapperIndexes.getParent(); --level; return this; } /** * Any post-processing step in the mapping is done in this method. The method is called ONCE after * all other mapping is complete. Note! Hierarchical data structures are not accessible anymore. */ public void finishUp() { // Add Calendar data created during the mapping of dayTypes, dayTypeAssignments, // datedServiceJourney and ServiceJourneys transitBuilder.getCalendarDates().addAll(calendarServiceBuilder.createServiceCalendar()); // Add the empty service id, as it can be used for routes expected to be added from realtime // updates or DSJs which are replaced, and where we want to keep the original DSJ ServiceCalendar emptyCalendar = calendarServiceBuilder.createEmptyCalendar(); if ( transitBuilder .getTripsById() .values() .stream() .anyMatch(trip -> emptyCalendar.getServiceId().equals(trip.getServiceId())) ) { transitBuilder.getCalendars().add(emptyCalendar); } } /** *

* This method mapes the last Netex file imported using the *local* entities in the hierarchical * {@link NetexEntityIndexReadOnlyView}. *

*

* Note that the order in which the elements are mapped is important. For example, if a file * contains Authorities, Line and Notices - they need to be mapped in that order, since Route have * a reference on Agency, and Notice may reference on Route. *

* * @param netexIndex The parsed Netex entities to be mapped */ public void mapNetexToOtp(NetexEntityIndexReadOnlyView netexIndex) { this.currentNetexIndex = netexIndex; this.currentMapperIndexes = new NetexMapperIndexes(netexIndex, currentMapperIndexes); // Be careful, the order matter. For example a Route has a reference to Agency; Hence Agency must be mapped // before Route - if both entities are defined in the same file. mapAuthorities(); mapOperators(); mapBrandings(); // The tariffZoneMapper is used to map all currently valid zones and to map the correct // referenced zone in StopPlace - which may not be the most currently valid zone. // This is a workaround until versioned entities are supported by OTP var tariffZoneMapper = mapTariffZones(); mapStopPlaceAndQuays(tariffZoneMapper); mapMultiModalStopPlaces(); mapGroupsOfStopPlaces(); mapFlexibleStopPlaces(); addDatedServiceJourneysToTripCalendar(); mapDayTypeAssignments(); // DayType and DSJ is mapped to a service calendar and a serviceId is generated Map serviceIds = createCalendarForServiceJourney(); mapRoute(); mapGroupsOfLines(); mapTripPatterns(serviceIds); mapNoticeAssignments(); addEntriesToGroupMapperForPostProcessingLater(); } /* PRIVATE METHODS */ private void setupGroupMapping() { if (level != LEVEL_GROUP) { return; } this.groupMapper = new GroupNetexMapper(idFactory, issueStore, transitBuilder); } /** * Group mappings should be done after all individual processed files and most entities are * mapped. The group mapping should only be used to map entities(relations) that reference * elements in other files within a group(netex namespace); */ private void performGroupMapping() { if (level != LEVEL_GROUP) { return; } this.groupMapper.mapGroupEntries(); // Throw away group data and make it available for garbage collection this.groupMapper = null; } private void mapAuthorities() { AuthorityToAgencyMapper agencyMapper = new AuthorityToAgencyMapper( idFactory, currentNetexIndex.getTimeZone() ); for (Authority authority : currentNetexIndex.getAuthoritiesById().localValues()) { Agency agency = agencyMapper.mapAuthorityToAgency(authority); transitBuilder.getAgenciesById().add(agency); } } private void mapBrandings() { BrandingMapper mapper = new BrandingMapper(idFactory); for (Branding branding : currentNetexIndex.getBrandingById().localValues()) { transitBuilder.getBrandingsById().add(mapper.mapBranding(branding)); } } private void mapGroupsOfLines() { GroupOfRoutesMapper mapper = new GroupOfRoutesMapper(idFactory); currentNetexIndex .getGroupsOfLinesById() .localValues() .forEach(gol -> { GroupOfRoutes model = mapper.mapGroupOfRoutes(gol); Optional .ofNullable(gol.getMembers()) .stream() .map(LineRefs_RelStructure::getLineRef) .filter(Objects::nonNull) .flatMap(Collection::stream) .filter(Objects::nonNull) .map(JAXBElement::getValue) .filter(Objects::nonNull) .map(VersionOfObjectRefStructure::getRef) .filter(Objects::nonNull) .forEach(ref -> { FeedScopedId routeId = idFactory.createId(ref); // At this point no routes are created yet // So we put all group of lines in multimap // RouteMapper can then use this map to populate Routes with correct GroupsOfLines transitBuilder.getGroupsOfRoutesByRouteId().put(routeId, model); }); // Create this index as well // In case relation is set on Line transitBuilder.getGroupOfRouteById().add(model); }); } private void mapOperators() { OperatorToAgencyMapper mapper = new OperatorToAgencyMapper(idFactory); for (org.rutebanken.netex.model.Operator operator : currentNetexIndex .getOperatorsById() .localValues()) { transitBuilder.getOperatorsById().add(mapper.mapOperator(operator)); } } private TariffZoneMapper mapTariffZones() { TariffZoneMapper tariffZoneMapper = new TariffZoneMapper( getStartOfPeriod(), idFactory, currentNetexIndex.getTariffZonesById() ); transitBuilder.getFareZonesById().addAll(tariffZoneMapper.listAllCurrentFareZones()); return tariffZoneMapper; } private void mapStopPlaceAndQuays(TariffZoneMapper tariffZoneMapper) { StopAndStationMapper stopMapper = new StopAndStationMapper( idFactory, currentNetexIndex.getQuayById(), tariffZoneMapper, ZoneId.of(currentNetexIndex.getTimeZone()), issueStore, noTransfersOnIsolatedStops ); for (String stopPlaceId : currentNetexIndex.getStopPlaceById().localKeys()) { Collection stopPlaceAllVersions = currentNetexIndex .getStopPlaceById() .lookup(stopPlaceId); stopMapper.mapParentAndChildStops(stopPlaceAllVersions); } transitBuilder.getStops().addAll(stopMapper.resultStops); transitBuilder.getStations().addAll(stopMapper.resultStations); currentMapperIndexes.addStationByMultiModalStationRfs( stopMapper.resultStationByMultiModalStationRfs ); } private void mapMultiModalStopPlaces() { MultiModalStationMapper mapper = new MultiModalStationMapper(issueStore, idFactory); for (StopPlace multiModalStopPlace : currentNetexIndex .getMultiModalStopPlaceById() .localValues()) { var stations = currentMapperIndexes .getStationsByMultiModalStationRfs() .get(multiModalStopPlace.getId()); var multiModalStation = mapper.map(multiModalStopPlace, stations); transitBuilder.getMultiModalStationsById().add(multiModalStation); } } private void mapGroupsOfStopPlaces() { GroupOfStationsMapper groupOfStationsMapper = new GroupOfStationsMapper( issueStore, idFactory, transitBuilder.getMultiModalStationsById(), transitBuilder.getStations() ); for (GroupOfStopPlaces groupOfStopPlaces : currentNetexIndex .getGroupOfStopPlacesById() .localValues()) { transitBuilder.getGroupsOfStationsById().add(groupOfStationsMapper.map(groupOfStopPlaces)); } } private void mapFlexibleStopPlaces() { Collection flexibleStopPlaces = currentNetexIndex .getFlexibleStopPlacesById() .localValues(); // Building the indices in FlexStopLocationMapper is expensive, so skip it if not needed if (flexibleStopPlaces.size() == 0) { return; } FlexStopsMapper flexStopsMapper = new FlexStopsMapper( idFactory, transitBuilder.getStops().values() ); for (FlexibleStopPlace flexibleStopPlace : flexibleStopPlaces) { StopLocation stopLocation = flexStopsMapper.map(flexibleStopPlace); if (stopLocation instanceof AreaStop) { transitBuilder.getAreaStops().add((AreaStop) stopLocation); } else if (stopLocation instanceof GroupStop groupStop) { transitBuilder.getGroupStops().add(groupStop); } } } private void addDatedServiceJourneysToTripCalendar() { tripCalendarBuilder.addDatedServiceJourneys( currentNetexIndex.getOperatingDayById(), currentMapperIndexes.getDatedServiceJourneysBySjId() ); } private void mapDayTypeAssignments() { tripCalendarBuilder.addDayTypeAssignments( currentNetexIndex.getDayTypeById(), currentNetexIndex.getDayTypeAssignmentByDayTypeId(), currentNetexIndex.getOperatingDayById(), currentNetexIndex.getOperatingPeriodById() ); } private Map createCalendarForServiceJourney() { return tripCalendarBuilder.createTripCalendar( currentNetexIndex.getServiceJourneyById().localValues() ); } private void mapRoute() { RouteMapper routeMapper = new RouteMapper( issueStore, idFactory, transitBuilder.getAgenciesById(), transitBuilder.getOperatorsById(), transitBuilder.getBrandingsById(), transitBuilder.getGroupsOfRoutesByRouteId(), transitBuilder.getGroupOfRouteById(), currentNetexIndex, currentNetexIndex.getTimeZone(), ferryIdsNotAllowedForBicycle ); for (Line line : currentNetexIndex.getLineById().localValues()) { Route route = routeMapper.mapRoute(line); if (route != null) { transitBuilder.getRoutes().add(route); } } for (FlexibleLine line : currentNetexIndex.getFlexibleLineById().localValues()) { Route route = routeMapper.mapRoute(line); if (route != null) { transitBuilder.getRoutes().add(route); } } } private void mapTripPatterns(Map serviceIds) { TripPatternMapper tripPatternMapper = new TripPatternMapper( issueStore, idFactory, transitBuilder.getOperatorsById(), transitBuilder.getStops(), transitBuilder.getAreaStops(), transitBuilder.getGroupStops(), transitBuilder.getRoutes(), currentNetexIndex.getRouteById(), currentNetexIndex.getJourneyPatternsById(), currentNetexIndex.getQuayIdByStopPointRef(), currentNetexIndex.getFlexibleStopPlaceByStopPointRef(), currentNetexIndex.getDestinationDisplayById(), currentNetexIndex.getServiceJourneyById(), currentNetexIndex.getServiceLinkById(), currentNetexIndex.getFlexibleLineById(), currentNetexIndex.getOperatingDayById(), currentNetexIndex.getDatedServiceJourneys(), currentMapperIndexes.getDatedServiceJourneysBySjId(), serviceIds, deduplicator, maxStopToShapeSnapDistance ); for (JourneyPattern journeyPattern : currentNetexIndex.getJourneyPatternsById().localValues()) { TripPatternMapperResult result = tripPatternMapper.mapTripPattern(journeyPattern); for (Map.Entry> it : result.tripStopTimes.entrySet()) { transitBuilder.getStopTimesSortedByTrip().put(it.getKey(), it.getValue()); transitBuilder.getTripsById().add(it.getKey()); } for (var it : result.tripPatterns.entries()) { transitBuilder.getTripPatterns().put(it.getKey(), it.getValue()); } currentMapperIndexes.addStopTimesByNetexId(result.stopTimeByNetexId); groupMapper.scheduledStopPointsIndex.putAll(result.scheduledStopPointsIndex); transitBuilder.getTripOnServiceDates().addAll(result.tripOnServiceDates); } } private void mapNoticeAssignments() { NoticeAssignmentMapper noticeAssignmentMapper = new NoticeAssignmentMapper( issueStore, idFactory, currentNetexIndex.getServiceJourneyById().localValues(), currentNetexIndex.getNoticeById(), transitBuilder.getRoutes(), transitBuilder.getTripsById(), currentMapperIndexes.getStopTimesByNetexId() ); for (NoticeAssignment noticeAssignment : currentNetexIndex .getNoticeAssignmentById() .localValues()) { Multimap noticesByElementId; noticesByElementId = noticeAssignmentMapper.map(noticeAssignment); transitBuilder.getNoticeAssignments().putAll(noticesByElementId); } } private void addEntriesToGroupMapperForPostProcessingLater() { if (level != 0) { groupMapper.addInterchange( currentNetexIndex.getServiceJourneyInterchangeById().localValues() ); } } /** * The start of period is used to find the valid entities based on the current time. This should * probably be configurable in the future, or even better incorporate the version number into the * entity id, so we can operate with more than one version of an entity in OTPs internal model. */ private LocalDateTime getStartOfPeriod() { String timeZone = currentNetexIndex.getTimeZone(); if (timeZone == null) { LocalDateTime time = LocalDateTime.now(ZoneId.of("UTC")); issueStore.add( "NetexImportTimeZone", "No timezone set for the current NeTEx input data file. The import " + "start-of-period is set to " + time + " UTC, used to check entity validity " + "periods." ); return time; } return LocalDateTime.now(ZoneId.of(timeZone)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy