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

org.opentripplanner.transit.service.SiteRepository Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.transit.service;

import jakarta.inject.Inject;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.model.site.GroupOfStations;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.MultiModalStation;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.site.StopLocationsGroup;
import org.opentripplanner.utils.collection.CollectionsView;
import org.opentripplanner.utils.collection.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Repository for Stop entities.
 */
public class SiteRepository implements Serializable {

  private static final Logger LOG = LoggerFactory.getLogger(SiteRepository.class);

  private final AtomicInteger stopIndexCounter;
  private final Map regularStopById;
  private final Map stationById;
  private final Map multiModalStationById;
  private final Map groupOfStationsById;
  private final Map areaStopById;
  private final Map groupStopById;
  private transient SiteRepositoryIndex index;

  @Inject
  public SiteRepository() {
    this.stopIndexCounter = new AtomicInteger(0);
    this.regularStopById = Map.of();
    this.stationById = Map.of();
    this.multiModalStationById = Map.of();
    this.groupOfStationsById = Map.of();
    this.areaStopById = Map.of();
    this.groupStopById = Map.of();
    this.index = createIndex();
  }

  SiteRepository(SiteRepositoryBuilder builder) {
    this.stopIndexCounter = builder.stopIndexCounter();
    this.regularStopById = builder.regularStopsById().asImmutableMap();
    this.stationById = builder.stationById().asImmutableMap();
    this.multiModalStationById = builder.multiModalStationById().asImmutableMap();
    this.groupOfStationsById = builder.groupOfStationById().asImmutableMap();
    this.areaStopById = builder.areaStopById().asImmutableMap();
    this.groupStopById = builder.groupStopById().asImmutableMap();
    reindex();
  }

  /**
   * Merge child into main. The child model must be created using the {@code main.withContext()}
   * method, if not this method will fail! If a duplicate key exist, then child value is kept -
   * this feature is normally not allowed, but not enforced here.
   */
  private SiteRepository(SiteRepository main, SiteRepository child) {
    this.stopIndexCounter = assertSameStopIndexCounterIsUsedToCreateBothModels(main, child);
    this.areaStopById = MapUtils.combine(main.areaStopById, child.areaStopById);
    this.regularStopById = MapUtils.combine(main.regularStopById, child.regularStopById);
    this.groupOfStationsById = MapUtils.combine(
      main.groupOfStationsById,
      child.groupOfStationsById
    );
    this.groupStopById = MapUtils.combine(main.groupStopById, child.groupStopById);
    this.multiModalStationById = MapUtils.combine(
      main.multiModalStationById,
      child.multiModalStationById
    );
    this.stationById = MapUtils.combine(main.stationById, child.stationById);
    reindex();
  }

  /**
   * Create a new builder based on an empty model. This is useful in unit-tests, but should
   * NOT be used in the main code. It is not possible to merge the result with another
   * {@link SiteRepository}, because they do not share the same context(stopIndexCounter).
   * 

* In the application code the correct way is to retrieve a model instance and then use the * {@link #withContext()} method to create a builder. */ public static SiteRepositoryBuilder of() { return new SiteRepositoryBuilder(new AtomicInteger(0)); } /** * Create a new builder attached to the existing model. The entities of the existing model are * NOT copied into the builder, but the builder has access to the model - allowing it to check * for duplicates and injecting information from the model(indexing). The changes in the * SiteRepositoryBuilder can then be merged into the original model - this is for now left to the * caller. *

* USE THIS TO CREATE A SAFE BUILDER IN PRODUCTION CODE. You MAY use this method in unit-tests, * the alternative is the {@link #of()} method. This method should be used if the test have a * SiteRepository and the {@link #of()} method should be used if a stop-model in not needed. */ public SiteRepositoryBuilder withContext() { return new SiteRepositoryBuilder(this.stopIndexCounter); } /** * Return a regular transit stop if found (not flex stops). */ public RegularStop getRegularStop(FeedScopedId id) { return regularStopById.get(id); } /** * Return all regular transit stops, not flex stops and flex group of stops. */ public Collection listRegularStops() { return regularStopById.values(); } /** * Find regular stops within a geographical area. */ public Collection findRegularStops(Envelope envelope) { return index.findRegularStops(envelope); } public boolean hasAreaStops() { return !areaStopById.isEmpty(); } /** * Flex locations are generated by GTFS graph builder, but consumed only after the street graph is * built. */ @Nullable public AreaStop getAreaStop(FeedScopedId id) { return areaStopById.get(id); } /** * Return all flex stops, not regular transit stops and flex group of stops. */ public Collection listAreaStops() { return areaStopById.values(); } /** * Find flex stops within a geographical area. */ public Collection findAreaStops(Envelope envelope) { return index.findAreaStops(envelope); } /** * Return all flex groups of stops. */ public Collection listGroupStops() { return groupStopById.values(); } @Nullable public StopLocation stopByIndex(int stopIndex) { return index.stopByIndex(stopIndex); } public int stopIndexSize() { return index.stopIndexSize(); } /** * Return regular transit stop, flex stop or flex group of stops. */ @Nullable public StopLocation getStopLocation(FeedScopedId id) { return getById(id, regularStopById, areaStopById, groupStopById); } /** * Return all stops including regular transit stops, flex stops and flex group of stops. */ public Collection listStopLocations() { return new CollectionsView<>( regularStopById.values(), areaStopById.values(), groupStopById.values() ); } @Nullable public Station getStationById(FeedScopedId id) { return stationById.get(id); } public Collection listStations() { return stationById.values(); } @Nullable public MultiModalStation getMultiModalStation(FeedScopedId id) { return multiModalStationById.get(id); } public Collection listMultiModalStations() { return multiModalStationById.values(); } @Nullable public MultiModalStation getMultiModalStationForStation(Station station) { return index.getMultiModalStationForStation(station); } public Collection listGroupOfStations() { return groupOfStationsById.values(); } /** * Finds a {@link StopLocationsGroup} by id. Return a station, multimodal station, or group of * station. */ @Nullable public StopLocationsGroup getStopLocationsGroup(FeedScopedId id) { return getById(id, stationById, multiModalStationById, groupOfStationsById); } /** * Returns all {@link StopLocationsGroup}s present, including stations, group of stations and * multimodal stations. */ public Collection listStopLocationGroups() { return new CollectionsView<>( stationById.values(), multiModalStationById.values(), groupOfStationsById.values() ); } /** * @param id Id of Stop, Station, MultiModalStation or GroupOfStations * @return The coordinate for the transit entity */ @Nullable public WgsCoordinate getCoordinateById(FeedScopedId id) { // GroupOfStations GroupOfStations groupOfStations = groupOfStationsById.get(id); if (groupOfStations != null) { return groupOfStations.getCoordinate(); } // Multimodal station MultiModalStation multiModalStation = multiModalStationById.get(id); if (multiModalStation != null) { return multiModalStation.getCoordinate(); } // Station Station station = stationById.get(id); if (station != null) { return station.getCoordinate(); } // Single stop (regular transit and flex) StopLocation stop = getStopLocation(id); return stop == null ? null : stop.getCoordinate(); } /** * Return all stops associated with the given id. If a Station, a MultiModalStation, or a * GroupOfStations matches the id, then all child stops are returned. If the id matches a regular * stops, area stop or stop group, then a list with one item is returned. * An empty list is if nothing is found. */ public Collection findStopOrChildStops(FeedScopedId id) { StopLocationsGroup stops = getStopLocationsGroup(id); if (stops != null) { return stops.getChildStops(); } // Single stop (regular transit and flex) StopLocation stop = getStopLocation(id); return stop == null ? List.of() : List.of(stop); } /** * Call this method after deserializing this class. This will reindex the SiteRepository. */ public void reindexAfterDeserialization() { reindex(); } public SiteRepository merge(SiteRepository child) { return new SiteRepository(this, child); } private void reindex() { LOG.info("Index site repository..."); index = createIndex(); LOG.info("Index site repository complete."); } private SiteRepositoryIndex createIndex() { return new SiteRepositoryIndex( regularStopById.values(), areaStopById.values(), groupStopById.values(), multiModalStationById.values(), stopIndexCounter.get() ); } @Nullable @SafeVarargs private static V getById(FeedScopedId id, Map... maps) { if (id == null) { return null; } for (Map map : maps) { V v = map.get(id); if (v != null) { return v; } } return null; } /** * The 'stopIndexCounter' must be the same instance, hence the '!=' operator. */ @SuppressWarnings("NumberEquality") private static AtomicInteger assertSameStopIndexCounterIsUsedToCreateBothModels( SiteRepository main, SiteRepository child ) { if (main.stopIndexCounter != child.stopIndexCounter) { throw new IllegalArgumentException( "Two Stop repositories can only be merged if they are created with the same stopIndexCounter. " + "This is archived by using the 'SiteRepository.withContext()' method. We do this to avoid " + "duplicates/gaps in the stopIndex." ); } return main.stopIndexCounter; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy