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

org.opentripplanner.ext.vehicleparking.hslpark.HslParkToVehicleParkingMapper Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.ext.vehicleparking.hslpark;

import com.bedatadriven.jackson.datatype.jts.parsers.GenericGeometryParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.locationtech.jts.geom.Geometry;
import org.opentripplanner.model.calendar.openinghours.OHCalendar;
import org.opentripplanner.model.calendar.openinghours.OpeningHoursCalendarService;
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingGroup;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingSpaces;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingState;
import org.opentripplanner.transit.model.basic.I18NString;
import org.opentripplanner.transit.model.basic.NonLocalizedString;
import org.opentripplanner.transit.model.basic.TranslatedString;
import org.opentripplanner.transit.model.basic.WgsCoordinate;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.util.geometry.GeometryUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Maps a HSL Park facility into a {@link VehicleParking}.
 */
public class HslParkToVehicleParkingMapper {

  private static final Logger log = LoggerFactory.getLogger(HslParkToVehicleParkingMapper.class);

  private static final GenericGeometryParser GEOMETRY_PARSER = new GenericGeometryParser(
    GeometryUtils.getGeometryFactory()
  );

  private final String feedId;

  private final OpeningHoursCalendarService openingHoursCalendarService;

  private final ZoneId zoneId;

  public HslParkToVehicleParkingMapper(
    String feedId,
    OpeningHoursCalendarService openingHoursCalendarService,
    ZoneId zoneId
  ) {
    this.feedId = feedId;
    this.openingHoursCalendarService = openingHoursCalendarService;
    this.zoneId = zoneId;
  }

  public static FeedScopedId createIdForNode(JsonNode jsonNode, String idName, String feedId) {
    String id = jsonNode.path(idName).asText();
    return new FeedScopedId(feedId, id);
  }

  public static Integer parseIntegerValue(JsonNode jsonNode, String fieldName) {
    if (!jsonNode.has(fieldName)) {
      return null;
    }
    return jsonNode.get(fieldName).asInt();
  }

  public VehicleParking parsePark(
    JsonNode jsonNode,
    Map hubForPark
  ) {
    var vehicleParkId = createIdForNode(jsonNode, "id", feedId);
    try {
      var capacity = parseVehicleSpaces(
        jsonNode.path("builtCapacity"),
        "BICYCLE",
        "CAR",
        "DISABLED"
      );
      Map translations = new HashMap<>();
      JsonNode nameNode = jsonNode.path("name");
      nameNode
        .fieldNames()
        .forEachRemaining(lang -> {
          String name = nameNode.path(lang).asText();
          if (!name.equals("")) {
            translations.put(lang, nameNode.path(lang).asText());
          }
        });
      I18NString name = translations.isEmpty()
        ? new NonLocalizedString(vehicleParkId.getId())
        : TranslatedString.getI18NString(translations, true, false);
      Geometry geometry = GEOMETRY_PARSER.geometryFromJson(jsonNode.path("location"));

      var stateText = jsonNode.path("status").asText();
      var state = stateMapper(stateText);

      var tags = parseTags(jsonNode);
      var maybeCapacity = Optional.ofNullable(capacity);
      var bicyclePlaces = maybeCapacity
        .map(c -> hasPlaces(capacity.getBicycleSpaces()))
        .orElse(false);
      var carPlaces = maybeCapacity.map(c -> hasPlaces(capacity.getCarSpaces())).orElse(false);
      var wheelChairAccessiblePlaces = maybeCapacity
        .map(c -> hasPlaces(capacity.getWheelchairAccessibleCarSpaces()))
        .orElse(false);
      var openingHoursByDayType = jsonNode.path("openingHours").path("byDayType");
      var openingHoursCalendar = parseOpeningHours(openingHoursByDayType);
      VehicleParkingGroup vehicleParkingGroup = hubForPark.get(vehicleParkId);

      return VehicleParking
        .builder()
        .id(vehicleParkId)
        .name(name)
        .state(state)
        .coordinate(new WgsCoordinate(geometry.getCentroid()))
        .capacity(capacity)
        .bicyclePlaces(bicyclePlaces)
        .carPlaces(carPlaces)
        .wheelchairAccessibleCarPlaces(wheelChairAccessiblePlaces)
        .tags(tags)
        .openingHoursCalendar(openingHoursCalendar)
        .entrance(builder ->
          builder
            .entranceId(new FeedScopedId(feedId, vehicleParkId.getId() + "/entrance"))
            .name(name)
            .coordinate(new WgsCoordinate(geometry.getCentroid()))
            .walkAccessible(true)
            .carAccessible(carPlaces || wheelChairAccessiblePlaces)
        )
        .vehicleParkingGroup(vehicleParkingGroup)
        .build();
    } catch (Exception e) {
      log.warn("Error parsing park {}", vehicleParkId, e);
      return null;
    }
  }

  private VehicleParkingSpaces parseVehicleSpaces(
    JsonNode node,
    String bicycleTag,
    String carTag,
    String wheelchairAccessibleCarTag
  ) {
    var bicycleSpaces = parseIntegerValue(node, bicycleTag);
    var carSpaces = parseIntegerValue(node, carTag);
    var wheelchairAccessibleCarSpaces = parseIntegerValue(node, wheelchairAccessibleCarTag);

    if (bicycleSpaces == null && carSpaces == null && wheelchairAccessibleCarSpaces == null) {
      return null;
    }

    return createVehiclePlaces(carSpaces, wheelchairAccessibleCarSpaces, bicycleSpaces);
  }

  private VehicleParkingSpaces createVehiclePlaces(
    Integer carSpaces,
    Integer wheelchairAccessibleCarSpaces,
    Integer bicycleSpaces
  ) {
    return VehicleParkingSpaces
      .builder()
      .bicycleSpaces(bicycleSpaces)
      .carSpaces(carSpaces)
      .wheelchairAccessibleCarSpaces(wheelchairAccessibleCarSpaces)
      .build();
  }

  private VehicleParkingState stateMapper(String stateText) {
    if (stateText == null) {
      return VehicleParkingState.OPERATIONAL;
    }
    switch (stateText) {
      case "INACTIVE":
        return VehicleParkingState.CLOSED;
      case "TEMPORARILY_CLOSED":
        return VehicleParkingState.TEMPORARILY_CLOSED;
      case "IN_OPERATION":
      case "EXCEPTIONAL_SITUATION":
      default:
        return VehicleParkingState.OPERATIONAL;
    }
  }

  private boolean hasPlaces(Integer spaces) {
    return spaces != null && spaces > 0;
  }

  private List parseTags(JsonNode node) {
    var tagList = new ArrayList();
    ArrayNode servicesArray = (ArrayNode) node.get("services");
    if (servicesArray != null && servicesArray.isArray()) {
      for (JsonNode jsonNode : servicesArray) {
        tagList.add(feedId + ":SERVICE_" + jsonNode.asText());
      }
    }
    ArrayNode authenticationMethods = (ArrayNode) node.get("authenticationMethods");
    if (authenticationMethods != null && authenticationMethods.isArray()) {
      for (JsonNode jsonNode : authenticationMethods) {
        tagList.add(feedId + ":AUTHENTICATION_METHOD_" + jsonNode.asText());
      }
    }
    if (node.has("pricingMethod")) {
      tagList.add(feedId + ":PRICING_METHOD_" + node.path("pricingMethod").asText());
    }
    return tagList;
  }

  private record DayTypeAndDays(String typeKey, String name, List days) {}

  private static final List DAYS_FOR_DAY_TYPES = List.of(
    new DayTypeAndDays(
      "BUSINESS_DAY",
      "Business days",
      List.of(
        DayOfWeek.MONDAY,
        DayOfWeek.TUESDAY,
        DayOfWeek.WEDNESDAY,
        DayOfWeek.THURSDAY,
        DayOfWeek.FRIDAY
      )
    ),
    new DayTypeAndDays("SATURDAY", "Saturday", List.of(DayOfWeek.SATURDAY)),
    new DayTypeAndDays("SUNDAY", "Sunday", List.of(DayOfWeek.SUNDAY))
  );

  private OHCalendar parseOpeningHours(JsonNode openingHoursByDayType) {
    if (zoneId == null) {
      return null;
    }
    var calendarBuilder = openingHoursCalendarService.newBuilder(zoneId);
    for (DayTypeAndDays dayTypeAndDays : DAYS_FOR_DAY_TYPES) {
      String key = dayTypeAndDays.typeKey();
      if (openingHoursByDayType.has(key) && openingHoursByDayType.path(key).has("from")) {
        LocalTime fromTime = convertTimeStringLocalTime(
          openingHoursByDayType.path(key).path("from").asText()
        );
        LocalTime toTime = convertTimeStringLocalTime(
          openingHoursByDayType.path(key).path("until").asText()
        );
        var openingHoursBuilder = calendarBuilder.openingHours(
          dayTypeAndDays.name(),
          fromTime,
          toTime
        );
        for (DayOfWeek day : dayTypeAndDays.days()) {
          openingHoursBuilder.on(day);
        }
        openingHoursBuilder.add();
      }
    }
    return calendarBuilder.build();
  }

  /**
   * Parses a string with format "05" or "05:30" to a {@link LocalTime}.
   * If a park is open until 24h, the end time will be 24 but it should be
   * adjusted to be 23:59 for opening hours.
   */
  private LocalTime convertTimeStringLocalTime(String timeString) {
    int hours = Integer.parseInt(timeString.substring(0, 2));
    int minutes = timeString.length() > 2 ? Integer.parseInt(timeString.substring(3, 5)) : 0;
    return hours == 24 ? LocalTime.MAX : LocalTime.of(hours, minutes);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy