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

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

The newest version!
package org.opentripplanner.netex.mapping;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nullable;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.framework.geometry.HashGridSpatialIndex;
import org.opentripplanner.framework.i18n.NonLocalizedString;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.issue.api.Issue;
import org.opentripplanner.netex.mapping.support.FeedScopedIdFactory;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.SiteRepositoryBuilder;
import org.rutebanken.netex.model.FlexibleArea;
import org.rutebanken.netex.model.FlexibleStopPlace;
import org.rutebanken.netex.model.KeyListStructure;
import org.rutebanken.netex.model.KeyValueStructure;

class FlexStopsMapper {

  /**
   * Key-value pair used until proper NeTEx support is added
   */
  private static final String FLEXIBLE_STOP_AREA_TYPE_KEY = "FlexibleStopAreaType";
  /**
   * Key-value pair used until proper NeTEx support is added
   */
  private static final String UNRESTRICTED_PUBLIC_TRANSPORT_AREAS_VALUE =
    "UnrestrictedPublicTransportAreas";
  private final FeedScopedIdFactory idFactory;
  private final HashGridSpatialIndex stopsSpatialIndex;
  private final SiteRepositoryBuilder siteRepositoryBuilder;
  private final DataImportIssueStore issueStore;
  private final TransportModeMapper transportModeMapper = new TransportModeMapper();

  FlexStopsMapper(
    FeedScopedIdFactory idFactory,
    Collection stops,
    SiteRepositoryBuilder siteRepositoryBuilder,
    DataImportIssueStore issueStore
  ) {
    this.idFactory = idFactory;
    this.stopsSpatialIndex = new HashGridSpatialIndex<>();
    for (RegularStop stop : stops) {
      Envelope env = new Envelope(stop.getCoordinate().asJtsCoordinate());
      this.stopsSpatialIndex.insert(env, stop);
    }
    this.siteRepositoryBuilder = siteRepositoryBuilder;
    this.issueStore = issueStore;
  }

  /**
   * Maps NeTEx FlexibleStopPlace to FlexStopLocation. The support for GroupStop is
   * dependent on a key/value in the NeTEx file, until proper NeTEx support is added.
   */
  StopLocation map(FlexibleStopPlace flexibleStopPlace) {
    List stops = new ArrayList<>();
    TransitMode flexibleStopTransitMode = mapTransitMode(flexibleStopPlace);
    var areas = flexibleStopPlace.getAreas().getFlexibleAreaOrFlexibleAreaRefOrHailAndRideArea();
    List areaGeometries = new ArrayList<>();
    for (var area : areas) {
      if (!(area instanceof FlexibleArea flexibleArea)) {
        issueStore.add(
          Issue.issue(
            "UnsupportedFlexibleStopPlaceAreaType",
            "FlexibleStopPlace %s contains an unsupported area %s.",
            flexibleStopPlace.getId(),
            area
          )
        );
        continue;
      }

      Geometry flexibleAreaGeometry = mapGeometry(flexibleArea);
      areaGeometries.add(flexibleAreaGeometry);

      if (shouldAddStopsFromArea(flexibleArea, flexibleStopPlace)) {
        stops.addAll(
          findStopsInFlexArea(flexibleArea, flexibleAreaGeometry, flexibleStopTransitMode)
        );
      } else {
        AreaStop areaStop = mapFlexArea(
          flexibleArea,
          flexibleAreaGeometry,
          flexibleStopPlace.getName().getValue()
        );
        if (areaStop != null) {
          stops.add(areaStop);
        }
      }
    }

    if (stops.isEmpty()) {
      return null;
    } else {
      // We create a new GroupStop, even if the stop place consists of a single area, in order to
      // get the ids for the area and stop place correct
      var builder = siteRepositoryBuilder
        .groupStop(idFactory.createId(flexibleStopPlace.getId()))
        .withName(new NonLocalizedString(flexibleStopPlace.getName().getValue()))
        .withEncompassingAreaGeometries(areaGeometries);
      stops.forEach(builder::addLocation);
      return builder.build();
    }
  }

  /**
   * Allows pickup / drop off along any eligible street inside the area
   */
  AreaStop mapFlexArea(FlexibleArea area, Geometry geometry, String backupName) {
    if (geometry == null) {
      return null;
    }

    var areaName = area.getName();
    return siteRepositoryBuilder
      .areaStop(idFactory.createId(area.getId()))
      .withName(new NonLocalizedString(areaName != null ? areaName.getValue() : backupName))
      .withGeometry(geometry)
      .build();
  }

  /**
   * Allows pickup / drop off at all regular stops inside the area that match the transit mode of the
   * flexible stop place.
   */
  List findStopsInFlexArea(
    FlexibleArea area,
    @Nullable Geometry geometry,
    @Nullable TransitMode flexibleStopTransitMode
  ) {
    if (geometry == null || flexibleStopTransitMode == null) {
      return List.of();
    }
    List stops = stopsSpatialIndex
      .query(geometry.getEnvelopeInternal())
      .stream()
      .filter(stop -> flexibleStopTransitMode == stop.getVehicleType())
      .filter(stop -> geometry.contains(stop.getGeometry()))
      .toList();

    if (stops.isEmpty()) {
      issueStore.add(
        Issue.issue(
          "MissingStopsInUnrestrictedPublicTransportAreas",
          "FlexibleArea %s with type UnrestrictedPublicTransportAreas does not contain any regular stop.",
          area.getId()
        )
      );
      return List.of();
    }

    return stops;
  }

  private Geometry mapGeometry(FlexibleArea area) {
    try {
      return OpenGisMapper.mapGeometry(area.getPolygon());
    } catch (Exception e) {
      issueStore.add(
        Issue.issue(
          "InvalidFlexAreaGeometry",
          "FlexibleArea %s has an invalid geometry.",
          area.getId()
        )
      );
      return null;
    }
  }

  @Nullable
  private TransitMode mapTransitMode(FlexibleStopPlace flexibleStopPlace) {
    try {
      return transportModeMapper.mapAllVehicleModesOfTransport(
        flexibleStopPlace.getTransportMode()
      );
    } catch (TransportModeMapper.UnsupportedModeException e) {
      return null;
    }
  }

  private boolean shouldAddStopsFromArea(FlexibleArea flexibleArea, FlexibleStopPlace parentStop) {
    var flexibleAreaType = getFlexibleStopAreaType(flexibleArea.getKeyList());
    var parentStopType = getFlexibleStopAreaType(parentStop.getKeyList());

    if (UNRESTRICTED_PUBLIC_TRANSPORT_AREAS_VALUE.equals(flexibleAreaType)) {
      return true;
    } else {
      return (
        UNRESTRICTED_PUBLIC_TRANSPORT_AREAS_VALUE.equals(parentStopType) && flexibleAreaType == null
      );
    }
  }

  private static String getFlexibleStopAreaType(KeyListStructure keyListStructure) {
    String flexibleStopAreaType = null;
    if (keyListStructure != null) {
      for (KeyValueStructure k : keyListStructure.getKeyValue()) {
        if (k.getKey().equals(FLEXIBLE_STOP_AREA_TYPE_KEY)) {
          flexibleStopAreaType = k.getValue();
          break;
        }
      }
    }
    return flexibleStopAreaType;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy