From bd21a045b61f1ae090cbe62fc5f2d9b1e04b0b2b Mon Sep 17 00:00:00 2001 From: teijosol Date: Thu, 13 Jun 2024 12:18:57 +0300 Subject: [PATCH] =?UTF-8?q?DPO-2301=20Waze-feedin=20t=C3=A4ydent=C3=A4mine?= =?UTF-8?q?n=20(#130)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * DPO-2301 new waze type&subtype code, first version * DPO-2301 enable some old tests * DPO-2301 add hazard-types * DPO-2301 add roadworks, more subtypes * DPO-2301 fix roadClosed-handling, add test * DPO-2301 convert maintenance work & construction work types * DPO-2301 roadwork filtering, some debug * DPO-2301 fix roadwork valid check * DPO-2301 test with just roadworks * DPO-2301 better convert, add test * DPO-2301 better tests * DPO-2301 fix tests * DPO-2301 use list for subtypes, filter out low-severity road-works * DPO-2301 filter out TransitInformation * DPO-2301 remove mappings for other * DPO-2301 review fixes * DPO-2009 for address geocoding, use middle point from middle linestring of multilinestring * DPO-2009 use first linestring --- .../controller/waze/WazeFeedControllerV1.java | 2 +- .../converter/waze/WazeDatex2Converter.java | 84 +++++---- .../waze/WazeDatex2JsonConverter.java | 10 +- .../tie/converter/waze/WazeTypeConverter.java | 110 +++++++++++ .../v1/TrafficAnnouncementProperties.java | 4 +- .../tie/dto/wazefeed/WazeFeedIncidentDto.java | 66 ++++++- .../helper/WazeDatex2MessageConverter.java | 81 ++++---- .../datex2/TrafficAnnouncementType.java | 1 - .../service/{ => waze}/WazeFeedService.java | 16 +- .../WazeReverseGeocodingService.java | 27 ++- .../converter/waze/WazeTypeConverterTest.java | 132 +++++++++++++ .../tie/service/WazeFeedServiceTest.java | 80 ++++++-- .../service/WazeFeedServiceTestHelper.java | 26 ++- .../WazeReverseGeocodingServiceTest.java | 34 +++- src/test/resources/wazefeed/Flood.json | 76 ++++++++ src/test/resources/wazefeed/Flood.xml | 91 +++++++++ src/test/resources/wazefeed/Roadwork.json | 176 ++++++++++++++++++ src/test/resources/wazefeed/Roadwork.xml | 120 ++++++++++++ 18 files changed, 996 insertions(+), 140 deletions(-) create mode 100644 src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverter.java rename src/main/java/fi/livi/digitraffic/tie/service/{ => waze}/WazeFeedService.java (78%) rename src/main/java/fi/livi/digitraffic/tie/service/{ => waze}/WazeReverseGeocodingService.java (82%) create mode 100644 src/test/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverterTest.java create mode 100644 src/test/resources/wazefeed/Flood.json create mode 100644 src/test/resources/wazefeed/Flood.xml create mode 100644 src/test/resources/wazefeed/Roadwork.json create mode 100644 src/test/resources/wazefeed/Roadwork.xml diff --git a/src/main/java/fi/livi/digitraffic/tie/controller/waze/WazeFeedControllerV1.java b/src/main/java/fi/livi/digitraffic/tie/controller/waze/WazeFeedControllerV1.java index 3d0a40823..db70a2584 100644 --- a/src/main/java/fi/livi/digitraffic/tie/controller/waze/WazeFeedControllerV1.java +++ b/src/main/java/fi/livi/digitraffic/tie/controller/waze/WazeFeedControllerV1.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController; import fi.livi.digitraffic.tie.dto.wazefeed.WazeFeedAnnouncementDto; -import fi.livi.digitraffic.tie.service.WazeFeedService; +import fi.livi.digitraffic.tie.service.waze.WazeFeedService; import io.swagger.v3.oas.annotations.Hidden; import io.swagger.v3.oas.annotations.Operation; diff --git a/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2Converter.java b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2Converter.java index 77418604a..d50a692fc 100644 --- a/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2Converter.java +++ b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2Converter.java @@ -8,6 +8,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import fi.livi.digitraffic.tie.datex2.*; +import fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; @@ -16,18 +18,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; -import fi.livi.digitraffic.tie.datex2.D2LogicalModel; -import fi.livi.digitraffic.tie.datex2.OverallPeriod; -import fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagement; -import fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementExtensionType; -import fi.livi.digitraffic.tie.datex2.Situation; -import fi.livi.digitraffic.tie.datex2.SituationPublication; -import fi.livi.digitraffic.tie.datex2.SituationRecord; -import fi.livi.digitraffic.tie.datex2.ValidityStatusEnum; import fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementFeature; import fi.livi.digitraffic.tie.dto.wazefeed.WazeDatex2FeatureDto; import fi.livi.digitraffic.tie.model.trafficmessage.datex2.Datex2; -import fi.livi.digitraffic.tie.model.trafficmessage.datex2.SituationType; import fi.livi.digitraffic.tie.service.trafficmessage.Datex2XmlStringToObjectMarshaller; import fi.livi.digitraffic.tie.service.trafficmessage.TrafficMessageImsJsonConverterV1; @@ -44,17 +37,16 @@ public WazeDatex2Converter(final TrafficMessageImsJsonConverterV1 datex2JsonConv } public Optional convertToWazeDatex2FeatureDto(final Datex2 datex2) { - final TrafficAnnouncementFeature feature; final String jsonMessage = datex2.getJsonMessage(); try { feature = datex2JsonConverterV1.convertToFeatureJsonObject_V1( jsonMessage, - SituationType.TRAFFIC_ANNOUNCEMENT, + datex2.getSituationType(), datex2.getTrafficAnnouncementType(), false, - datex2.getModified() + datex2.getModified() ); } catch (final JsonProcessingException e) { logger.error("method=convertToWazeDatex2FeatureDto json string conversion to feature object failed", e); @@ -83,8 +75,8 @@ public static boolean hasActiveSituationRecords(final WazeDatex2FeatureDto wazeD final String situationId = wazeDatex2FeatureDto.feature.getProperties().situationId; final boolean result = getSituationRecords(situationId, wazeDatex2FeatureDto.d2LogicalModel) - .stream() - .anyMatch(WazeDatex2Converter::isActiveSituationRecord); + .stream() + .anyMatch(WazeDatex2Converter::isActiveSituationRecord); if (!result) { logger.info("method=hasActiveSituationRecords situation {} did not have any valid situation records", situationId); @@ -97,13 +89,13 @@ public static boolean isActiveSituationRecord(final SituationRecord situationRec final ValidityStatusEnum validityStatus = situationRecord.getValidity().getValidityStatus(); switch (validityStatus) { - case ACTIVE: - return true; - case DEFINED_BY_VALIDITY_TIME_SPEC: - final OverallPeriod validityTimeSpec = situationRecord.getValidity().getValidityTimeSpecification(); - final Instant now = Instant.now(); - final Instant overallEndTime = validityTimeSpec.getOverallEndTime(); - return now.isAfter(validityTimeSpec.getOverallStartTime()) && + case ACTIVE: + return true; + case DEFINED_BY_VALIDITY_TIME_SPEC: + final OverallPeriod validityTimeSpec = situationRecord.getValidity().getValidityTimeSpecification(); + final Instant now = Instant.now(); + final Instant overallEndTime = validityTimeSpec.getOverallEndTime(); + return now.isAfter(validityTimeSpec.getOverallStartTime()) && overallEndTime == null || now.isBefore(overallEndTime); } @@ -124,20 +116,48 @@ public static List getSituationRecords(final String situationId } return situations.stream() - .map(Situation::getSituationRecords) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + .map(Situation::getSituationRecords) + .flatMap(Collection::stream) + .collect(Collectors.toList()); } public static boolean hasIceRoadOpenRecord(final WazeDatex2FeatureDto wazeDatex2FeatureDto) { final String situationId = wazeDatex2FeatureDto.feature.getProperties().situationId; return getSituationRecords(situationId, wazeDatex2FeatureDto.d2LogicalModel) - .stream() - .filter(sr -> sr instanceof RoadOrCarriagewayOrLaneManagement) - .anyMatch(sr -> Optional.of((RoadOrCarriagewayOrLaneManagement) sr) - .map(RoadOrCarriagewayOrLaneManagement::getRoadOrCarriagewayOrLaneManagementExtension) - .map(RoadOrCarriagewayOrLaneManagementExtensionType::getRoadOrCarriagewayOrLaneManagementType) - .map(x -> x.equals(ICE_ROAD_OPEN)) - .orElse(false)); + .stream() + .filter(sr -> sr instanceof RoadOrCarriagewayOrLaneManagement) + .anyMatch(sr -> Optional.of((RoadOrCarriagewayOrLaneManagement) sr) + .map(RoadOrCarriagewayOrLaneManagement::getRoadOrCarriagewayOrLaneManagementExtension) + .map(RoadOrCarriagewayOrLaneManagementExtensionType::getRoadOrCarriagewayOrLaneManagementType) + .map(x -> x.equals(ICE_ROAD_OPEN)) + .orElse(false)); + } + + public static boolean hasTransitInformationRecord(final WazeDatex2FeatureDto wazeDatex2FeatureDto) { + final String situationId = wazeDatex2FeatureDto.feature.getProperties().situationId; + return getSituationRecords(situationId, wazeDatex2FeatureDto.d2LogicalModel) + .stream() + .anyMatch(sr -> sr instanceof TransitInformation); + } + + public static boolean isValidWazeEntry(final WazeDatex2FeatureDto feature) { + return hasGeometry(feature) + && hasActiveSituationRecords(feature) + && isValidType(feature); + } + + private static boolean isValidType(final WazeDatex2FeatureDto feature) { + // if it is a traffic announcement, we check that it's not ice road open or TransitInformation + if(feature.feature.getProperties().getSituationType() == SituationType.TRAFFIC_ANNOUNCEMENT) { + return !hasIceRoadOpenRecord(feature) && !hasTransitInformationRecord(feature); + } + + // low serverity road-works are not relevant + if (feature.datex2.getMessage().contains(("low"))) { + return false; + } + + return feature.datex2.getMessage().contains("roadClosed") + || feature.datex2.getMessage().contains("constructionWork"); } -} +} \ No newline at end of file diff --git a/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2JsonConverter.java b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2JsonConverter.java index 288d7e182..5b0dd2089 100644 --- a/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2JsonConverter.java +++ b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeDatex2JsonConverter.java @@ -27,7 +27,7 @@ import fi.livi.digitraffic.tie.metadata.geojson.Geometry; import fi.livi.digitraffic.tie.metadata.geojson.MultiLineString; import fi.livi.digitraffic.tie.metadata.geojson.Point; -import fi.livi.digitraffic.tie.service.WazeReverseGeocodingService; +import fi.livi.digitraffic.tie.service.waze.WazeReverseGeocodingService; @ConditionalOnWebApplication @Component @@ -40,11 +40,15 @@ public class WazeDatex2JsonConverter { private final WazeReverseGeocodingService wazeReverseGeocodingService; + private final WazeTypeConverter wazeTypeConverter; + @Autowired public WazeDatex2JsonConverter(final WazeDatex2MessageConverter wazeDatex2MessageConverter, - final WazeReverseGeocodingService wazeReverseGeocodingService) { + final WazeReverseGeocodingService wazeReverseGeocodingService, + final WazeTypeConverter wazeTypeConverter) { this.wazeDatex2MessageConverter = wazeDatex2MessageConverter; this.wazeReverseGeocodingService = wazeReverseGeocodingService; + this.wazeTypeConverter = wazeTypeConverter; } public Optional convertToWazeFeedAnnouncementDto(final WazeDatex2FeatureDto wazeDatex2FeatureDto) { @@ -52,7 +56,7 @@ public Optional convertToWazeFeedAnnouncementDto(final Waze final TrafficAnnouncementProperties properties = feature.getProperties(); final String situationId = properties.situationId; - final Optional maybeType = this.convertToWazeType(properties.getTrafficAnnouncementType()); + final Optional maybeType = wazeTypeConverter.convertToWazeType(wazeDatex2FeatureDto); final TrafficAnnouncement announcement = properties.announcements.get(0); final Optional> maybeGeometry = Optional.ofNullable(feature.getGeometry()); diff --git a/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverter.java b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverter.java new file mode 100644 index 000000000..12d93e1f9 --- /dev/null +++ b/src/main/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverter.java @@ -0,0 +1,110 @@ +package fi.livi.digitraffic.tie.converter.waze; + +import fi.livi.digitraffic.tie.dto.trafficmessage.v1.Restriction; +import fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType; +import fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementProperties; +import fi.livi.digitraffic.tie.dto.wazefeed.WazeDatex2FeatureDto; +import fi.livi.digitraffic.tie.dto.wazefeed.WazeFeedIncidentDto; +import fi.livi.digitraffic.tie.model.trafficmessage.datex2.Datex2; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static fi.livi.digitraffic.tie.dto.wazefeed.WazeFeedIncidentDto.WazeType.*; + +@ConditionalOnWebApplication +@Component +public class WazeTypeConverter { + public Optional convertToWazeType(final WazeDatex2FeatureDto dto) { + final TrafficAnnouncementProperties properties = dto.feature.getProperties(); + + if (properties.getSituationType() == SituationType.TRAFFIC_ANNOUNCEMENT && properties.getTrafficAnnouncementType() == null) { + return Optional.empty(); + } + + if (isRoadClosed(dto.datex2)) { + if (properties.getSituationType() == SituationType.TRAFFIC_ANNOUNCEMENT) { + return Optional.of(ROAD_CLOSED_HAZARD); + } else { + return Optional.of(ROAD_CLOSED_CONSTRUCTION); + } + } + + // Check datex2 for any common type for TrafficAnnouncement or MaintenanceWork + final WazeFeedIncidentDto.WazeType commonWazeType = getCommonWazeTypeFromDatex2(dto.datex2); + if (commonWazeType != null) { + return Optional.of(commonWazeType); + } + + if (properties.getSituationType() == SituationType.TRAFFIC_ANNOUNCEMENT) { + // traffic announcement + // old functionality + switch (properties.getTrafficAnnouncementType()) { + case ACCIDENT_REPORT: + case PRELIMINARY_ACCIDENT_REPORT: + return Optional.of(ACCIDENT_NONE); + case GENERAL: + return Optional.of(HAZARD_NONE); + default: + return Optional.empty(); + } + } else if (properties.getSituationType() == SituationType.ROAD_WORK) { + // road works + + // default type for ROAD_WORK + return Optional.empty(); + } + + throw new IllegalArgumentException("Could not figure out type for id " + dto.feature.getProperties().situationId); + } + + /** + * Check if any restriction is of type ROAD_CLOSED + */ + private boolean isRoadClosed(final Datex2 d2) { + return d2.getMessage().contains("roadClosed"); + } + + // note, this is also a priority listing + // if multiple mappings are present, the one higher in the list will be selected + private static List> typeMapping = List.of( + Pair.of("hazardsOnTheRoad", HAZARD_ON_ROAD), + Pair.of("objectOnTheRoad", HAZARD_ON_ROAD_OBJECT), + Pair.of("laneClosures", HAZARD_ON_ROAD_LANE_CLOSED), + Pair.of("animalsOnTheRoad", HAZARD_ON_SHOULDER_ANIMALS), + Pair.of("largeAnimalsOnTheRoad", HAZARD_ON_SHOULDER_ANIMALS), + Pair.of("herdOfAnimalsOnTheRoad", HAZARD_ON_SHOULDER_ANIMALS), + + Pair.of("badWeather", HAZARD_WEATHER), + + Pair.of("oilOnRoad", HAZARD_ON_ROAD_OIL), + Pair.of("roadSurfaceInPoorCondition", HAZARD_ON_ROAD_POT_HOLE), + Pair.of("ice", HAZARD_ON_ROAD_ICE), + Pair.of("freezingRain", HAZARD_WEATHER_FREEZING_RAIN), + Pair.of("heavyRain", HAZARD_WEATHER_HEAVY_RAIN), + Pair.of("damagingHail", HAZARD_WEATHER_HAIL), + Pair.of("blizzard", HAZARD_WEATHER_HEAVY_SNOW), + Pair.of("heavySnowfall", HAZARD_WEATHER_HEAVY_SNOW), + + Pair.of("flooding", HAZARD_WEATHER_FLOOD), + Pair.of("denseFog", HAZARD_WEATHER_FOG), + Pair.of("fog", HAZARD_WEATHER_FOG), + + Pair.of("stationaryTraffic", JAM_STAND_STILL_TRAFFIC), + Pair.of("heavyTraffic", JAM_HEAVY_TRAFFIC), + Pair.of("queuingTraffic", JAM_MODERATE_TRAFFIC) + ); + + private WazeFeedIncidentDto.WazeType getCommonWazeTypeFromDatex2(final Datex2 d2) { + return typeMapping.stream() + .filter(e -> d2.getMessage().contains(e.getKey())) + .findFirst() + .map(e -> e.getValue()) + .orElse(null); + } +} \ No newline at end of file diff --git a/src/main/java/fi/livi/digitraffic/tie/dto/trafficmessage/v1/TrafficAnnouncementProperties.java b/src/main/java/fi/livi/digitraffic/tie/dto/trafficmessage/v1/TrafficAnnouncementProperties.java index e74ef7357..9cd5a0329 100644 --- a/src/main/java/fi/livi/digitraffic/tie/dto/trafficmessage/v1/TrafficAnnouncementProperties.java +++ b/src/main/java/fi/livi/digitraffic/tie/dto/trafficmessage/v1/TrafficAnnouncementProperties.java @@ -39,10 +39,10 @@ public class TrafficAnnouncementProperties extends PropertiesV1 { @Schema(description = "The type of the traffic announcement. Omitted for other situation types. Note that ended and retracted are not actual types.") private TrafficAnnouncementType trafficAnnouncementType; - @Schema(description = "Annoucement release time", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "Announcement release time", requiredMode = Schema.RequiredMode.REQUIRED) public final ZonedDateTime releaseTime; - @Schema(description = "Annoucement version time", requiredMode = Schema.RequiredMode.REQUIRED) + @Schema(description = "Announcement version time", requiredMode = Schema.RequiredMode.REQUIRED) public final ZonedDateTime versionTime; @Schema(description = "Contains announcement's different language versions available.", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/src/main/java/fi/livi/digitraffic/tie/dto/wazefeed/WazeFeedIncidentDto.java b/src/main/java/fi/livi/digitraffic/tie/dto/wazefeed/WazeFeedIncidentDto.java index 06f2b38fb..7c86271f7 100644 --- a/src/main/java/fi/livi/digitraffic/tie/dto/wazefeed/WazeFeedIncidentDto.java +++ b/src/main/java/fi/livi/digitraffic/tie/dto/wazefeed/WazeFeedIncidentDto.java @@ -11,30 +11,78 @@ public class WazeFeedIncidentDto implements Serializable { public final String id; public final String description; - public final Type type; + public final String type; + public final String subtype; public final WazeFeedLocationDto location; public final String starttime; public final String endtime; public WazeFeedIncidentDto(final String id, final String street, final String description, final WazeFeedLocationDto.Direction direction, - final String polyline, final Type type, final String starttime, final String endtime) { + final String polyline, final WazeType type, final String starttime, final String endtime) { this.id = id; this.location = new WazeFeedLocationDto(street, polyline, direction); this.description = description; - this.type = type; + this.type = type == null ? null : type.type.name(); + this.subtype = type == null ? null : type.getSubtype(); this.starttime = starttime; this.endtime = endtime; - } + } public enum Type { - // Defined the only type currently in use in our Waze integration. More can be added as needed + // Defined types currently in use in our Waze integration. More can be added as needed // See: https://developers.google.com/waze/data-feed/incident-information HAZARD, - ACCIDENT; + ACCIDENT, + ROAD_CLOSED, + JAM, + POLICE; + } + + public enum WazeType { + // Defined subtypes currently in use in our Waze integration. + // See: https://developers.google.com/waze/data-feed/cifs-specification#incident-subtypes + + ACCIDENT_NONE(Type.ACCIDENT), + + HAZARD_NONE(Type.HAZARD), + HAZARD_ON_ROAD(Type.HAZARD), + HAZARD_ON_ROAD_CONSTRUCTION(Type.HAZARD), + HAZARD_ON_ROAD_ICE(Type.HAZARD), + HAZARD_ON_ROAD_LANE_CLOSED(Type.HAZARD), + HAZARD_ON_ROAD_OBJECT(Type.HAZARD), + HAZARD_ON_ROAD_OIL(Type.HAZARD), + HAZARD_ON_ROAD_POT_HOLE(Type.HAZARD), + HAZARD_ON_ROAD_TRAFFIC_LIGHT_FAULT(Type.HAZARD), + HAZARD_ON_SHOULDER_ANIMALS(Type.HAZARD), + HAZARD_WEATHER(Type.HAZARD), + HAZARD_WEATHER_FLOOD(Type.HAZARD), + HAZARD_WEATHER_FOG(Type.HAZARD), + HAZARD_WEATHER_FREEZING_RAIN(Type.HAZARD), + HAZARD_WEATHER_HAIL(Type.HAZARD), + HAZARD_WEATHER_HEAVY_RAIN(Type.HAZARD), + HAZARD_WEATHER_HEAVY_SNOW(Type.HAZARD), + + ROAD_CLOSED_NONE(Type.ROAD_CLOSED), + ROAD_CLOSED_HAZARD(Type.ROAD_CLOSED), + ROAD_CLOSED_CONSTRUCTION(Type.ROAD_CLOSED), + + JAM_NONE(Type.JAM), + JAM_MODERATE_TRAFFIC(Type.JAM), + JAM_HEAVY_TRAFFIC(Type.JAM), + JAM_STAND_STILL_TRAFFIC(Type.JAM), + ; + + public final Type type; + WazeType(final Type type) { + this.type = type; + } + + public String getSubtype() { + if(name().endsWith("_NONE")) { + return null; + } - @JsonCreator - public static Type fromValue(final String value) { - return Type.valueOf(value.toUpperCase()); + return name(); } } } \ No newline at end of file diff --git a/src/main/java/fi/livi/digitraffic/tie/helper/WazeDatex2MessageConverter.java b/src/main/java/fi/livi/digitraffic/tie/helper/WazeDatex2MessageConverter.java index 0d51adb70..397c0ac2b 100644 --- a/src/main/java/fi/livi/digitraffic/tie/helper/WazeDatex2MessageConverter.java +++ b/src/main/java/fi/livi/digitraffic/tie/helper/WazeDatex2MessageConverter.java @@ -172,6 +172,7 @@ import static fi.livi.digitraffic.tie.datex2.ReroutingManagementTypeEnum.USE_ENTRY; import static fi.livi.digitraffic.tie.datex2.ReroutingManagementTypeEnum.USE_EXIT; import static fi.livi.digitraffic.tie.datex2.ReroutingManagementTypeEnum.USE_INTERSECTION_OR_JUNCTION; +import static fi.livi.digitraffic.tie.datex2.RoadMaintenanceTypeEnum.*; import static fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementTypeEnum.CARRIAGEWAY_CLOSURES; import static fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementTypeEnum.CAR_POOL_LANE_IN_OPERATION; import static fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementTypeEnum.CLEAR_A_LANE_FOR_EMERGENCY_VEHICLES; @@ -242,6 +243,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import fi.livi.digitraffic.tie.datex2.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -251,53 +253,6 @@ import com.sun.xml.ws.util.StringUtils; import fi.livi.digitraffic.tie.converter.waze.WazeDatex2Converter; -import fi.livi.digitraffic.tie.datex2.AbnormalTraffic; -import fi.livi.digitraffic.tie.datex2.AbnormalTrafficExtensionType; -import fi.livi.digitraffic.tie.datex2.AbnormalTrafficTypeEnum; -import fi.livi.digitraffic.tie.datex2.Accident; -import fi.livi.digitraffic.tie.datex2.AccidentTypeEnum; -import fi.livi.digitraffic.tie.datex2.AnimalPresenceObstruction; -import fi.livi.digitraffic.tie.datex2.AnimalPresenceTypeEnum; -import fi.livi.digitraffic.tie.datex2.AuthorityOperation; -import fi.livi.digitraffic.tie.datex2.D2LogicalModel; -import fi.livi.digitraffic.tie.datex2.DisturbanceActivity; -import fi.livi.digitraffic.tie.datex2.EnvironmentalObstruction; -import fi.livi.digitraffic.tie.datex2.EnvironmentalObstructionTypeEnum; -import fi.livi.digitraffic.tie.datex2.EquipmentOrSystemFault; -import fi.livi.digitraffic.tie.datex2.EquipmentOrSystemFaultExtensionType; -import fi.livi.digitraffic.tie.datex2.EquipmentOrSystemFaultTypeEnum; -import fi.livi.digitraffic.tie.datex2.EquipmentOrSystemTypeEnum; -import fi.livi.digitraffic.tie.datex2.ExtendedEquipmentOrSystemFaultTypeEnum; -import fi.livi.digitraffic.tie.datex2.ExtendedRoadOrCarriagewayOrLaneManagementTypeEnum; -import fi.livi.digitraffic.tie.datex2.ExtendedTrafficTrendTypeEnum; -import fi.livi.digitraffic.tie.datex2.GeneralNetworkManagement; -import fi.livi.digitraffic.tie.datex2.GeneralNetworkManagementTypeEnum; -import fi.livi.digitraffic.tie.datex2.GeneralObstruction; -import fi.livi.digitraffic.tie.datex2.InfrastructureDamageObstruction; -import fi.livi.digitraffic.tie.datex2.InfrastructureDamageTypeEnum; -import fi.livi.digitraffic.tie.datex2.NonWeatherRelatedRoadConditionTypeEnum; -import fi.livi.digitraffic.tie.datex2.NonWeatherRelatedRoadConditions; -import fi.livi.digitraffic.tie.datex2.ObstructionTypeEnum; -import fi.livi.digitraffic.tie.datex2.PoorEnvironmentConditions; -import fi.livi.digitraffic.tie.datex2.PoorEnvironmentTypeEnum; -import fi.livi.digitraffic.tie.datex2.PublicEvent; -import fi.livi.digitraffic.tie.datex2.PublicEventTypeEnum; -import fi.livi.digitraffic.tie.datex2.ReroutingManagement; -import fi.livi.digitraffic.tie.datex2.ReroutingManagementTypeEnum; -import fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagement; -import fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementExtensionType; -import fi.livi.digitraffic.tie.datex2.RoadOrCarriagewayOrLaneManagementTypeEnum; -import fi.livi.digitraffic.tie.datex2.Situation; -import fi.livi.digitraffic.tie.datex2.SituationPublication; -import fi.livi.digitraffic.tie.datex2.SituationRecord; -import fi.livi.digitraffic.tie.datex2.SpeedManagement; -import fi.livi.digitraffic.tie.datex2.TrafficFlowCharacteristicsEnum; -import fi.livi.digitraffic.tie.datex2.TrafficTrendTypeEnum; -import fi.livi.digitraffic.tie.datex2.TransitInformation; -import fi.livi.digitraffic.tie.datex2.VehicleObstruction; -import fi.livi.digitraffic.tie.datex2.VehicleObstructionTypeEnum; -import fi.livi.digitraffic.tie.datex2.WeatherRelatedRoadConditionTypeEnum; -import fi.livi.digitraffic.tie.datex2.WeatherRelatedRoadConditions; import fi.livi.digitraffic.tie.service.trafficmessage.Datex2XmlStringToObjectMarshaller; @Component @@ -325,6 +280,8 @@ public class WazeDatex2MessageConverter { private final Map trafficTrendTypeEnumMap = new HashMap<>(); private final Map vehicleObstructionTypeMap = new HashMap<>(); private final Map weatherRelatedRoadConditionTypeMap = new HashMap<>(); + private final Map constructionWorksTypeMap = new HashMap<>(); + private final Map maintenanceWorksTypeMap = new HashMap<>(); @Autowired public WazeDatex2MessageConverter(final Datex2XmlStringToObjectMarshaller datex2XmlStringToObjectMarshaller) { @@ -566,7 +523,6 @@ private void constructMaps() { publicEventTypeEnumStringMap.put(TRADE_FAIR, "trade fair"); publicEventTypeEnumStringMap.put(WATER_SPORTS_MEETING, "water sports meeting"); publicEventTypeEnumStringMap.put(WINTER_SPORTS_MEETING, "winter sports meeting"); - publicEventTypeEnumStringMap.put(PublicEventTypeEnum.OTHER, "other"); reroutingManagementTypeMap.put(DO_NOT_FOLLOW_DIVERSION_SIGNS, "Do not follow diversion signs"); reroutingManagementTypeMap.put(DO_NOT_USE_ENTRY, "Do not use entry"); @@ -644,6 +600,18 @@ private void constructMaps() { weatherRelatedRoadConditionTypeMap.put(SURFACE_WATER, "Surface water"); weatherRelatedRoadConditionTypeMap.put(WET_AND_ICY_ROAD, "Wet and icy road"); weatherRelatedRoadConditionTypeMap.put(WET_ICY_PAVEMENT, "Wet icy pavement"); + + maintenanceWorksTypeMap.put(RESURFACING_WORK, "Resurfacing work"); + maintenanceWorksTypeMap.put(MAINTENANCE_WORK, "Maintenance work"); + maintenanceWorksTypeMap.put(ROADSIDE_WORK, "Roadside work"); + maintenanceWorksTypeMap.put(ROADWORKS, "Roadworks"); + maintenanceWorksTypeMap.put(ROADWORKS_CLEARANCE, "Roadworks clearance"); + maintenanceWorksTypeMap.put(ROAD_MARKING_WORK, "Road marking work"); + maintenanceWorksTypeMap.put(GRASS_CUTTING_WORK, "Grass cutting work"); + maintenanceWorksTypeMap.put(TREE_AND_VEGETATION_CUTTING_WORK, "Tree and vegetation cutting work"); + + constructionWorksTypeMap.put(ConstructionWorkTypeEnum.CONSTRUCTION_WORK, "Construction work"); + constructionWorksTypeMap.put(ConstructionWorkTypeEnum.BLASTING_WORK, "Blasting work"); } public String export(final String situationId, final String datex2Message) { @@ -832,6 +800,17 @@ private Optional accept(final WeatherRelatedRoadConditions weatherRelate .map(x -> weatherRelatedRoadConditionTypeMap.getOrDefault(x, null)); } + private Optional accept(final ConstructionWorks constructionWorks) { + return Optional.ofNullable(constructionWorks.getConstructionWorkType()) + .map(x -> constructionWorksTypeMap.getOrDefault(x, null)); + } + + private Optional accept(final MaintenanceWorks maintenanceWorks) { + return maintenanceWorks.getRoadMaintenanceTypes().stream() + .findFirst() + .map(x -> maintenanceWorksTypeMap.getOrDefault(x, null)); + } + private Optional accept(final String situationId, final SituationRecord situationRecord) { final Optional result; final String situationRecordType; @@ -893,6 +872,12 @@ private Optional accept(final String situationId, final SituationRecord } else if (situationRecord instanceof WeatherRelatedRoadConditions) { result = accept((WeatherRelatedRoadConditions) situationRecord); situationRecordType = "WeatherRelatedRoadConditions"; + } else if (situationRecord instanceof ConstructionWorks) { + result = accept((ConstructionWorks) situationRecord); + situationRecordType = "ConstructionWorks"; + } else if (situationRecord instanceof MaintenanceWorks) { + result = accept((MaintenanceWorks) situationRecord); + situationRecordType = "MaintenanceWorks"; } else { logger.error("method=accept unknown class {} in {}", situationRecord.getClass().getSimpleName(), situationId); return Optional.empty(); diff --git a/src/main/java/fi/livi/digitraffic/tie/model/trafficmessage/datex2/TrafficAnnouncementType.java b/src/main/java/fi/livi/digitraffic/tie/model/trafficmessage/datex2/TrafficAnnouncementType.java index 191a9540d..f81a983bb 100644 --- a/src/main/java/fi/livi/digitraffic/tie/model/trafficmessage/datex2/TrafficAnnouncementType.java +++ b/src/main/java/fi/livi/digitraffic/tie/model/trafficmessage/datex2/TrafficAnnouncementType.java @@ -38,7 +38,6 @@ public String value() { return this.value; } - @JsonCreator public static TrafficAnnouncementType fromValue(final String value) { final TrafficAnnouncementType constant = CONSTANTS.get(value.toUpperCase()); if (constant == null) { diff --git a/src/main/java/fi/livi/digitraffic/tie/service/WazeFeedService.java b/src/main/java/fi/livi/digitraffic/tie/service/waze/WazeFeedService.java similarity index 78% rename from src/main/java/fi/livi/digitraffic/tie/service/WazeFeedService.java rename to src/main/java/fi/livi/digitraffic/tie/service/waze/WazeFeedService.java index 5d5d14798..46ce0b55f 100644 --- a/src/main/java/fi/livi/digitraffic/tie/service/WazeFeedService.java +++ b/src/main/java/fi/livi/digitraffic/tie/service/waze/WazeFeedService.java @@ -1,9 +1,11 @@ -package fi.livi.digitraffic.tie.service; +package fi.livi.digitraffic.tie.service.waze; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.stereotype.Service; @@ -21,6 +23,7 @@ @ConditionalOnWebApplication @Service public class WazeFeedService { + private static final Logger logger = LoggerFactory.getLogger(WazeFeedService.class); private final Datex2Repository datex2Repository; private final WazeDatex2Converter wazeDatex2Converter; @@ -37,18 +40,21 @@ public WazeFeedService(final Datex2Repository datex2Repository, @Transactional(readOnly = true) public WazeFeedAnnouncementDto findActive() { - final List activeIncidents = datex2Repository.findAllActiveBySituationTypeWithJson(1, SituationType.TRAFFIC_ANNOUNCEMENT.name()); + final List activeIncidents = datex2Repository.findAllActiveBySituationTypeWithJson(1, + SituationType.TRAFFIC_ANNOUNCEMENT.name(), SituationType.ROAD_WORK.name()); + + logger.info("method=findActive active incidents count=" + activeIncidents.size()); final List incidents = activeIncidents.stream() .map(this.wazeDatex2Converter::convertToWazeDatex2FeatureDto) .flatMap(Optional::stream) - .filter(WazeDatex2Converter::hasGeometry) - .filter(WazeDatex2Converter::hasActiveSituationRecords) - .filter(x -> !WazeDatex2Converter.hasIceRoadOpenRecord(x)) + .filter(WazeDatex2Converter::isValidWazeEntry) .map(this.wazeDatex2JsonConverter::convertToWazeFeedAnnouncementDto) .flatMap(Optional::stream) .collect(Collectors.toList()); + logger.info("method=findActive valid incidents count=" + incidents.size()); + return new WazeFeedAnnouncementDto(incidents); } } \ No newline at end of file diff --git a/src/main/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingService.java b/src/main/java/fi/livi/digitraffic/tie/service/waze/WazeReverseGeocodingService.java similarity index 82% rename from src/main/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingService.java rename to src/main/java/fi/livi/digitraffic/tie/service/waze/WazeReverseGeocodingService.java index a63c2fc84..e946967eb 100644 --- a/src/main/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingService.java +++ b/src/main/java/fi/livi/digitraffic/tie/service/waze/WazeReverseGeocodingService.java @@ -1,11 +1,15 @@ -package fi.livi.digitraffic.tie.service; +package fi.livi.digitraffic.tie.service.waze; import static fi.livi.digitraffic.tie.conf.RoadCacheConfiguration.CACHE_REVERSE_GEOCODE; import java.io.IOException; import java.util.Collection; +import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,16 +60,25 @@ private Optional getPoint(final Geometry geometry) { if (geometry instanceof Point) { return Optional.of((Point) geometry); } else if (geometry instanceof MultiLineString) { + // get first linestring and middle coordinates return ((MultiLineString) geometry).getCoordinates().stream() - .flatMap(Collection::stream) .findFirst() - .map(pair -> new Point(pair.get(0), pair.get(1))); + .flatMap(this::middleElement) + .flatMap(pair -> Optional.of(new Point(pair.get(0), pair.get(1)))); } logger.warn(String.format("method=getPoint Unknown geometry type %s", geometry.getClass().getSimpleName())); return Optional.empty(); } +private Optional middleElement(final List list) { + if(list.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(list.get(list.size() / 2)); +} + private Optional closestStreetName(final ReverseGeocode reverseGeocode) { return reverseGeocode.results.stream() .reduce((accumulator, element) -> accumulator.distance > element.distance ? element : accumulator) @@ -77,9 +90,15 @@ private Optional fetch(final Point point) { final Double longitude = point.getLongitude(); logger.info(String.format(Locale.US, "method=fetch Get reverse geocoding for lat: %f, lon: %f", latitude, longitude)); + return wazeReverseGeocodingApi .fetch(latitude, longitude) - .flatMap(this::parseReverseGeocodeJson); + .flatMap(this::parseReverseGeocodeJson) + .or(() -> { + logger.info(String.format(Locale.US, "method=fetch empty response for lat: %f, lon: %f", latitude, longitude)); + + return Optional.empty(); + }); } private Optional parseReverseGeocodeJson(final String input) { diff --git a/src/test/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverterTest.java b/src/test/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverterTest.java new file mode 100644 index 000000000..c53f43033 --- /dev/null +++ b/src/test/java/fi/livi/digitraffic/tie/converter/waze/WazeTypeConverterTest.java @@ -0,0 +1,132 @@ +package fi.livi.digitraffic.tie.converter.waze; + +import fi.livi.digitraffic.tie.AbstractSpringJUnitTest; +import fi.livi.digitraffic.tie.dto.trafficmessage.v1.*; +import fi.livi.digitraffic.tie.dto.wazefeed.WazeDatex2FeatureDto; +import fi.livi.digitraffic.tie.dto.wazefeed.WazeFeedIncidentDto; +import fi.livi.digitraffic.tie.metadata.geojson.Geometry; +import fi.livi.digitraffic.tie.metadata.geojson.Point; +import fi.livi.digitraffic.tie.model.trafficmessage.datex2.Datex2; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; + +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static fi.livi.digitraffic.tie.dto.wazefeed.WazeFeedIncidentDto.WazeType.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class WazeTypeConverterTest extends AbstractSpringJUnitTest { + @Autowired + private WazeTypeConverter wazeTypeConverter; + + private Optional testConvert(final SituationType situationType, + final TrafficAnnouncementType trafficAnnouncementType) { + return testConvert(situationType, trafficAnnouncementType, (p) -> {}); + } + + private Optional testConvert(final SituationType situationType, + final TrafficAnnouncementType trafficAnnouncementType, + final Consumer consumer) { + final Geometry g = new Point(1.0, 2.2); + final TrafficAnnouncementProperties properties = new TrafficAnnouncementProperties("id", 1, situationType, + trafficAnnouncementType, + ZonedDateTime.now(), ZonedDateTime.now(), new ArrayList<>(), new Contact()); + + final TrafficAnnouncementFeature feature = new TrafficAnnouncementFeature(g, properties); + final Datex2 d2 = new Datex2(); + final WazeDatex2FeatureDto dto = new WazeDatex2FeatureDto(d2, null, feature); + + d2.setMessage(""); + + consumer.accept(dto); + + return wazeTypeConverter.convertToWazeType(dto); + } + + private void assertType(final WazeFeedIncidentDto.WazeType expected, final Optional value) { + if(expected != null) { + assertTrue(value.isPresent()); + assertEquals(expected, value.get()); + } else { + assertFalse(value.isPresent()); + } + } + + private static final Consumer ADD_ROAD_CLOSED = (p) -> { + p.datex2.setMessage("roadClosed"); + }; + + @Test + public void noTrafficAnnouncementType() { + final Optional type = testConvert(SituationType.TRAFFIC_ANNOUNCEMENT, null); + + assertType(null, type); + } + + @Test + public void trafficAnnouncement_roadClosed() { + final Optional type = testConvert(SituationType.TRAFFIC_ANNOUNCEMENT, TrafficAnnouncementType.GENERAL, ADD_ROAD_CLOSED); + + assertType(ROAD_CLOSED_HAZARD, type); + } + + @Test + public void roadwork_roadClosed() { + final Optional type = testConvert(SituationType.ROAD_WORK, TrafficAnnouncementType.GENERAL, ADD_ROAD_CLOSED); + + assertType(ROAD_CLOSED_CONSTRUCTION, type); + } + + @Test + public void jam_standStillTraffic() { + final Optional type = testConvert(SituationType.ROAD_WORK, TrafficAnnouncementType.GENERAL, (dto -> { + dto.datex2.setMessage("stationaryTraffic"); + })); + + assertType(JAM_STAND_STILL_TRAFFIC, type); + } + + @Test + public void jam_heavyTraffic() { + final Optional type = testConvert(SituationType.ROAD_WORK, TrafficAnnouncementType.GENERAL, (dto -> { + dto.datex2.setMessage("heavyTraffic"); + })); + + assertType(JAM_HEAVY_TRAFFIC, type); + } + + @Test + public void jam_moderateTraffic() { + final Optional type = testConvert(SituationType.ROAD_WORK, TrafficAnnouncementType.GENERAL, (dto -> { + dto.datex2.setMessage("queuingTraffic"); + })); + + assertType(JAM_MODERATE_TRAFFIC, type); + } + + @ParameterizedTest + @MethodSource("convertTrafficAnnouncementProvider") + public void convertTrafficAnnouncement(final TrafficAnnouncementType taType, final WazeFeedIncidentDto.WazeType expected) { + final Optional type = testConvert(SituationType.TRAFFIC_ANNOUNCEMENT, taType); + + assertType(expected, type); + } + + private static Stream convertTrafficAnnouncementProvider() { + return Stream.of( + Arguments.of(TrafficAnnouncementType.ENDED, null), + Arguments.of(TrafficAnnouncementType.GENERAL, HAZARD_NONE), + Arguments.of(TrafficAnnouncementType.PRELIMINARY_ACCIDENT_REPORT, ACCIDENT_NONE), + Arguments.of(TrafficAnnouncementType.ACCIDENT_REPORT, ACCIDENT_NONE) + ); + } +} diff --git a/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTest.java b/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTest.java index df6c0e739..97f04e311 100644 --- a/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTest.java +++ b/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTest.java @@ -7,16 +7,22 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.when; import java.io.IOException; import java.time.Instant; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import fi.livi.digitraffic.tie.dao.trafficmessage.datex2.Datex2Repository; +import fi.livi.digitraffic.tie.model.trafficmessage.datex2.Datex2; +import fi.livi.digitraffic.tie.model.trafficmessage.datex2.SituationType; +import fi.livi.digitraffic.tie.model.trafficmessage.datex2.TrafficAnnouncementType; +import fi.livi.digitraffic.tie.service.waze.WazeFeedService; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; @@ -24,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.context.annotation.Import; import fi.livi.digitraffic.tie.AbstractRestWebTest; @@ -56,6 +63,9 @@ public class WazeFeedServiceTest extends AbstractRestWebTest { @MockBean private WazeReverseGeocodingApi wazeReverseGeocodingApi; + @SpyBean + private Datex2Repository datex2Repository; + public static final String EXAMPLE_WAZE_REVERSE_GEOCODING_RESPONSE = "{\"lat\":60.1,\"lon\":21.3,\"radius\":50,\"result\":[{\"distance\":3.1415,\"names\":[\"Lautta\"]},{\"distance\":20.24164959825527,\"names\":[\"192 - Kivimaantie\"]}]}"; @BeforeEach @@ -68,20 +78,56 @@ public void cleanup() { wazeFeedServiceTestHelper.cleanup(); } + + private void assertWazeType(final WazeFeedIncidentDto incident, final WazeFeedIncidentDto.WazeType expected) { + assertEquals(expected.type.name(), incident.type); + assertEquals(expected.getSubtype(), incident.subtype); + + + } @Test - @Disabled - public void getAListOfWazeAnnouncements() { + public void getAListOfWazeAnnouncements() throws IOException { wazeFeedServiceTestHelper.insertSituation(); final WazeFeedAnnouncementDto announcement = wazeFeedService.findActive(); final List incidents = announcement.incidents; assertEquals(1, incidents.size()); + assertWazeType(incidents.get(0), WazeFeedIncidentDto.WazeType.ACCIDENT_NONE); } @Test - @Disabled - public void announcementIsProperlyFormatted() { + public void flood() throws IOException { + final Datex2 d2 = new Datex2(SituationType.ROAD_WORK, null); + d2.setMessage(readDatex2MessageFromFile("Flood.xml")); + d2.setJsonMessage(readDatex2MessageFromFile("Flood.json")); + + when(datex2Repository.findAllActiveBySituationTypeWithJson(anyInt(), any(String[].class))).thenReturn(Arrays.asList(d2)); + + final WazeFeedAnnouncementDto announcement = wazeFeedService.findActive(); + assertEquals(1, announcement.incidents.size()); + + final WazeFeedIncidentDto incident = announcement.incidents.get(0); + assertWazeType(incident, WazeFeedIncidentDto.WazeType.ROAD_CLOSED_HAZARD); + } + + @Test + public void roadwork() throws IOException { + final Datex2 d2 = new Datex2(SituationType.ROAD_WORK, null); + d2.setMessage(readDatex2MessageFromFile("Roadwork.xml")); + d2.setJsonMessage(readDatex2MessageFromFile("Roadwork.json")); + + when(datex2Repository.findAllActiveBySituationTypeWithJson(anyInt(), any(String[].class))).thenReturn(Arrays.asList(d2)); + + final WazeFeedAnnouncementDto announcement = wazeFeedService.findActive(); + assertEquals(1, announcement.incidents.size()); + + final WazeFeedIncidentDto incident = announcement.incidents.get(0); + assertWazeType(incident, WazeFeedIncidentDto.WazeType.ROAD_CLOSED_CONSTRUCTION); + } + + @Test + public void announcementIsProperlyFormatted() throws IOException { final String situationId = "GUID12345"; final ZonedDateTime startTime = ZonedDateTime.parse("2021-07-28T13:09:47.470Z"); @@ -89,7 +135,7 @@ public void announcementIsProperlyFormatted() { new WazeFeedServiceTestHelper.SituationParams(situationId, startTime, ACCIDENT_REPORT, RoadAddressLocation.Direction.BOTH); - wazeFeedServiceTestHelper.insertSituation(params, ""); + wazeFeedServiceTestHelper.insertSituation(params, readDatex2MessageFromFile("TrafficSituationAbnormalTraffic.xml")); final WazeFeedAnnouncementDto announcement = wazeFeedService.findActive(); final List incidents = announcement.incidents; @@ -98,15 +144,13 @@ public void announcementIsProperlyFormatted() { final WazeFeedIncidentDto incident = incidents.get(0); assertEquals(situationId, incident.id); - assertEquals(WazeFeedIncidentDto.Type.ACCIDENT, incident.type); - assertEquals("", incident.description); - assertTrue(incident.description.length() <= 40); + assertWazeType(incident, WazeFeedIncidentDto.WazeType.HAZARD_ON_ROAD_LANE_CLOSED); + assertEquals("Accident. Lane closures. Queuing traffic.", incident.description); assertEquals("FINTRAFFIC", incident.reference); } @Test - @Disabled - public void pointInAnnouncement() { + public void pointInAnnouncement() throws IOException { final Point point = new Point(25.182835, 61.575153); wazeFeedServiceTestHelper.insertSituation("GUID1234", RoadAddressLocation.Direction.BOTH, point); @@ -133,8 +177,7 @@ public void incorrectGeometryType() { } @Test - @Disabled - public void onewayDirectionInAccidents() { + public void onewayDirectionInAccidents() throws IOException { wazeFeedServiceTestHelper.insertSituation("GUID1234", RoadAddressLocation.Direction.POS); wazeFeedServiceTestHelper.insertSituation("GUID1235", RoadAddressLocation.Direction.NEG); wazeFeedServiceTestHelper.insertSituation("GUID1236", RoadAddressLocation.Direction.UNKNOWN); @@ -147,8 +190,7 @@ public void onewayDirectionInAccidents() { } @Test - @Disabled - public void unsupportedGeometryTypesAreFilteredFromResults() { + public void unsupportedGeometryTypesAreFilteredFromResults() throws IOException { final List> coords = List.of(List.of(25.180874, 61.569262), List.of(25.180826, 61.569394)); final MultiLineString geometry = new MultiLineString(); geometry.addLineString(coords); @@ -166,8 +208,7 @@ public void unsupportedGeometryTypesAreFilteredFromResults() { } @Test - @Disabled - public void bothDirectionsCoordinatesAreReturnedAsProperlyFormattedPolyline() { + public void bothDirectionsCoordinatesAreReturnedAsProperlyFormattedPolyline() throws IOException { final MultiLineString geometry = new MultiLineString(); geometry.addLineString(List.of(List.of(25.180874, 61.569262), List.of(25.180826, 61.569394))); @@ -184,7 +225,6 @@ public void bothDirectionsCoordinatesAreReturnedAsProperlyFormattedPolyline() { } @Test - @Disabled public void noIncidents() { final WazeFeedAnnouncementDto announcement = wazeFeedService.findActive(); final List incidents = announcement.incidents; @@ -318,13 +358,13 @@ public void oneDirectionMultiLineStringToPolyline() { @Test @Disabled - public void filterPreliminaryAccidentReports() { + public void filterPreliminaryAccidentReports() throws IOException { final WazeFeedServiceTestHelper.SituationParams params = new WazeFeedServiceTestHelper.SituationParams(); params.situationId = wazeFeedServiceTestHelper.nextSituationRecord(); params.trafficAnnouncementType = PRELIMINARY_ACCIDENT_REPORT; // datex2 database record having preliminary accident report type - wazeFeedServiceTestHelper.insertSituation(params); + wazeFeedServiceTestHelper.insertSituation(params, readDatex2MessageFromFile("TrafficSituationEquipmentOrSystemFault.xml")); // datex2 announcement type column having incorrect announcement type, but real preliminary accident report type still in json format params.situationId = wazeFeedServiceTestHelper.nextSituationRecord(); diff --git a/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTestHelper.java b/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTestHelper.java index 3597cd684..395ae5a74 100644 --- a/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTestHelper.java +++ b/src/test/java/fi/livi/digitraffic/tie/service/WazeFeedServiceTestHelper.java @@ -53,12 +53,12 @@ void cleanup() { datex2Repository.deleteAll(); } - void insertSituation() { + void insertSituation() throws IOException { final String situationId = "GUID1234"; insertSituation(situationId, RoadAddressLocation.Direction.BOTH); } - void insertSituation(final String situationId, final RoadAddressLocation.Direction direction) { + void insertSituation(final String situationId, final RoadAddressLocation.Direction direction) throws IOException { final MultiLineString geometry = new MultiLineString(); final List> coordinates = new ArrayList<>(); @@ -70,11 +70,12 @@ void insertSituation(final String situationId, final RoadAddressLocation.Directi insertSituation(situationId, direction, geometry); } - void insertSituation(final String situationId, final RoadAddressLocation.Direction direction, final Geometry geometry) { + void insertSituation(final String situationId, final RoadAddressLocation.Direction direction, final Geometry geometry) throws IOException { final SituationParams params = new SituationParams( situationId, ZonedDateTime.now(), + fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType.TRAFFIC_ANNOUNCEMENT, fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementType.ACCIDENT_REPORT, direction, geometry @@ -83,8 +84,8 @@ void insertSituation(final String situationId, final RoadAddressLocation.Directi insertSituation(params); } - void insertSituation(final SituationParams params) { - insertSituation(params, ""); + void insertSituation(final SituationParams params) throws IOException { + insertSituation(params, readDatex2MessageFromFile("TrafficSituationEquipmentOrSystemFault.xml")); } void insertSituation(final SituationParams params, final String datex2Message) { @@ -93,8 +94,9 @@ void insertSituation(final SituationParams params, final String datex2Message) { void insertSituation(final String situationId, final String situationRecordId, final String datex2Message, final SituationParams params, final fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementType datex2TrafficAnnouncementType) { - final Datex2 datex2 = new Datex2(SituationType.TRAFFIC_ANNOUNCEMENT, - TrafficAnnouncementType.fromValue(datex2TrafficAnnouncementType.name())); + final TrafficAnnouncementType taType = datex2TrafficAnnouncementType == null ? null : + TrafficAnnouncementType.fromValue(datex2TrafficAnnouncementType.name()); + final Datex2 datex2 = new Datex2(SituationType.valueOf(params.situationType.name()), taType); final Datex2Situation situation = new Datex2Situation(); final Datex2SituationRecord situationRecord = new Datex2SituationRecord(); final ZonedDateTime dateTimeNow = ZonedDateTime.now(); @@ -135,6 +137,8 @@ static String readDatex2MessageFromFile(final String file) throws IOException { static class SituationParams { String situationId; Geometry geometry; + + fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType situationType; fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementType trafficAnnouncementType; final ZonedDateTime startTime; final RoadAddressLocation.Direction direction; @@ -150,16 +154,18 @@ static class SituationParams { SituationParams(final String situationId, final ZonedDateTime startTime, final fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementType trafficAnnouncementType, final RoadAddressLocation.Direction direction) { - this(situationId, startTime, trafficAnnouncementType, direction, null); + this(situationId, startTime, fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType.TRAFFIC_ANNOUNCEMENT, trafficAnnouncementType, direction, null); this.geometry = createDummyGeometry(); } SituationParams(final String situationId, final ZonedDateTime startTime, + final fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType situationType, final fi.livi.digitraffic.tie.dto.trafficmessage.v1.TrafficAnnouncementType trafficAnnouncementType, final RoadAddressLocation.Direction direction, - final Geometry geometry) { + final Geometry geometry) { this.situationId = situationId; this.startTime = startTime; + this.situationType = situationType; this.trafficAnnouncementType = trafficAnnouncementType; this.direction = direction; this.geometry = geometry; @@ -233,7 +239,7 @@ private TrafficAnnouncementProperties createTrafficAnnouncementProperties() { return new TrafficAnnouncementProperties( this.situationId, 11, - fi.livi.digitraffic.tie.dto.trafficmessage.v1.SituationType.TRAFFIC_ANNOUNCEMENT, + this.situationType, this.trafficAnnouncementType, null, null, diff --git a/src/test/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingServiceTest.java b/src/test/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingServiceTest.java index e2d52d565..ad9566cad 100644 --- a/src/test/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingServiceTest.java +++ b/src/test/java/fi/livi/digitraffic/tie/service/WazeReverseGeocodingServiceTest.java @@ -8,9 +8,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import fi.livi.digitraffic.tie.service.waze.WazeReverseGeocodingService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -47,17 +49,39 @@ public void shouldReturnNearestStreetName() { assertEquals(streetName, result.orElse(null)); } - @Test void shouldUseTheFirstCoordinatePairFromMultiLineString() { - final double latitude = 26.26; - final double longitude = 65.65; - final List> coords = List.of(List.of(longitude, latitude), List.of(24.24, 63.63)); + @Test + void shouldUseTheMiddleCoordinatePairFromMultiLineString() { + final double latitude1 = 26.26; + final double longitude1 = 65.65; + final double latitude2 = 24.24; + final double longitude2 = 63.63; + + final List> coords = List.of(List.of(longitude1, latitude1), List.of(longitude2, latitude2)); final MultiLineString geometry = new MultiLineString(); geometry.addLineString(coords); when(this.wazeReverseGeocodingApi.fetch(anyDouble(), anyDouble())).thenReturn(Optional.empty()); wazeReverseGeocodingService.getStreetName(geometry); - verify(this.wazeReverseGeocodingApi, times(1)).fetch(latitude, longitude); + verify(this.wazeReverseGeocodingApi, times(1)).fetch(latitude2, longitude2); + } + + @Test + void testEmptyMultilineString() { + final MultiLineString geometry = new MultiLineString(); + + wazeReverseGeocodingService.getStreetName(geometry); + + verify(this.wazeReverseGeocodingApi, times(0)).fetch(anyDouble(), anyDouble()); + } + + @Test + void testMultilineStringWithEmptyLinestring() { + final MultiLineString geometry = new MultiLineString(List.of(List.of())); + + wazeReverseGeocodingService.getStreetName(geometry); + + verify(this.wazeReverseGeocodingApi, times(0)).fetch(anyDouble(), anyDouble()); } @Test diff --git a/src/test/resources/wazefeed/Flood.json b/src/test/resources/wazefeed/Flood.json new file mode 100644 index 000000000..3d2209c61 --- /dev/null +++ b/src/test/resources/wazefeed/Flood.json @@ -0,0 +1,76 @@ +{ + "geometry" : { + "type" : "MultiLineString", + "coordinates" : [ [ [ 29.976927, 65.950123 ], [ 29.977492, 65.950316 ], [ 29.977905, 65.950467 ], [ 29.978208, 65.950605 ], [ 29.978627, 65.950805 ], [ 29.979001, 65.951019 ], [ 29.979389, 65.951296 ], [ 29.979763, 65.951543 ], [ 29.980019, 65.951699 ], [ 29.980292, 65.951848 ], [ 29.980661, 65.952009 ], [ 29.981108, 65.952193 ], [ 29.98148, 65.952324 ], [ 29.981735, 65.952419 ], [ 29.9821, 65.952539 ], [ 29.982552, 65.952674 ], [ 29.983524, 65.95288 ], [ 29.984261, 65.953005 ], [ 29.985047, 65.953108 ], [ 29.985765, 65.953173 ], [ 29.986223, 65.953213 ], [ 29.986862, 65.953242 ], [ 29.987358, 65.953244 ], [ 29.987875, 65.953248 ], [ 29.988669, 65.9532 ], [ 29.98931, 65.953152 ], [ 29.990147, 65.95308 ], [ 29.990913, 65.953018 ], [ 29.991383, 65.952977 ], [ 29.992346, 65.952902 ], [ 29.993365, 65.952813 ], [ 29.994182, 65.952749 ], [ 29.995152, 65.952668 ], [ 29.996027, 65.952604 ], [ 29.996412, 65.952563 ], [ 29.996841, 65.952532 ], [ 29.997251, 65.952504 ], [ 29.997923, 65.95244 ], [ 29.999039, 65.952347 ], [ 29.999696, 65.952301 ], [ 30.000894, 65.952232 ], [ 30.001696, 65.95223 ], [ 30.002435, 65.952249 ], [ 30.003369, 65.952303 ], [ 30.004155, 65.952388 ], [ 30.004813, 65.952482 ], [ 30.005379, 65.952586 ], [ 30.005759, 65.952664 ], [ 30.006146, 65.95276 ], [ 30.006376, 65.95282 ], [ 30.006988, 65.952989 ], [ 30.007405, 65.953134 ], [ 30.00768, 65.953232 ], [ 30.008149, 65.953443 ], [ 30.008485, 65.953616 ], [ 30.008891, 65.953816 ], [ 30.009081, 65.953941 ], [ 30.009356, 65.954138 ], [ 30.009599, 65.954341 ], [ 30.00982, 65.954569 ], [ 30.009959, 65.954725 ], [ 30.010182, 65.95498 ], [ 30.010287, 65.955109 ], [ 30.010419, 65.955236 ], [ 30.010742, 65.955627 ], [ 30.011037, 65.955941 ], [ 30.011448, 65.956394 ], [ 30.011758, 65.956773 ], [ 30.012124, 65.957169 ], [ 30.012607, 65.957721 ], [ 30.012855, 65.95798 ], [ 30.013104, 65.95826 ], [ 30.013261, 65.958473 ], [ 30.013421, 65.958758 ], [ 30.013526, 65.958963 ], [ 30.013552, 65.959077 ], [ 30.013565, 65.959132 ], [ 30.013586, 65.959374 ], [ 30.013592, 65.959566 ], [ 30.013599, 65.959831 ], [ 30.01357, 65.960105 ], [ 30.013514, 65.960382 ], [ 30.013439, 65.960676 ], [ 30.013359, 65.961006 ], [ 30.013225, 65.961416 ], [ 30.012992, 65.961885 ], [ 30.012755, 65.962269 ], [ 30.012585, 65.962539 ], [ 30.012366, 65.962812 ], [ 30.012049, 65.963133 ], [ 30.011543, 65.963609 ], [ 30.01116, 65.963948 ], [ 30.010794, 65.964255 ], [ 30.010726, 65.964322 ], [ 30.010687, 65.964354 ], [ 30.01055, 65.964468 ], [ 30.010343, 65.964615 ], [ 30.010044, 65.964754 ], [ 30.009813, 65.964846 ], [ 30.009589, 65.964907 ], [ 30.009284, 65.964971 ], [ 30.00919, 65.964983 ] ] ] + }, + "type" : "Feature", + "properties" : { + "situationId" : "GUID50428713", + "version" : 1, + "situationType" : "traffic announcement", + "trafficAnnouncementType" : "general", + "releaseTime" : "2024-05-15T08:28:34.070Z", + "versionTime" : "2024-05-15T08:28:34.033Z", + "announcements" : [ { + "language" : "fi", + "title" : "Tie 18871, eli Kiviranta, Kuusamo. Liikennetiedote. ", + "location" : { + "countryCode" : 6, + "locationTableNumber" : 17, + "locationTableVersion" : "1.11.43", + "description" : "Tie 18871, eli Kiviranta, Kuusamo.\nTarkempi paikka: Paikasta Nevalan tienhaara 2,1 km, vaikutusalue 3,0 km, suuntaan Myllyaho." + }, + "locationDetails" : { + "roadAddressLocation" : { + "primaryPoint" : { + "municipality" : "Kuusamo", + "province" : "Pohjois-Pohjanmaa", + "country" : "Suomi", + "roadAddress" : { + "road" : 18871, + "roadSection" : 2, + "distance" : 2103 + }, + "roadName" : "Kiviranta", + "alertCLocation" : { + "locationCode" : 35215, + "name" : "Nevalan tienhaara", + "distance" : 2103 + } + }, + "secondaryPoint" : { + "municipality" : "Kuusamo", + "province" : "Pohjois-Pohjanmaa", + "country" : "Suomi", + "roadAddress" : { + "road" : 18871, + "roadSection" : 2, + "distance" : 5101 + }, + "roadName" : "Kiviranta", + "alertCLocation" : { + "locationCode" : 35216, + "name" : "Myllyaho", + "distance" : 5879 + } + }, + "direction" : "unknown" + } + }, + "features" : [ { + "name" : "Tulvavesi on noussut tielle" + }, { + "name" : "Tie on suljettu liikenteeltä" + } ], + "timeAndDuration" : { + "startTime" : "2024-05-15T08:26:00.000Z" + }, + "additionalInformation" : "Liikenne- ja kelitiedot verkossa: https://liikennetilanne.fintraffic.fi/", + "sender" : "Fintraffic Tieliikennekeskus Tampere" + } ], + "contact" : { + "phone" : "02002100", + "email" : "tampere.liikennekeskus@fintraffic.fi" + } + } +} \ No newline at end of file diff --git a/src/test/resources/wazefeed/Flood.xml b/src/test/resources/wazefeed/Flood.xml new file mode 100644 index 000000000..a8991a9f4 --- /dev/null +++ b/src/test/resources/wazefeed/Flood.xml @@ -0,0 +1,91 @@ + + + + + fi + FTA + + + + 2024-05-15T08:28:37.809Z + + fi + FTA + + + + restrictedToAuthoritiesTrafficOperatorsAndPublishers + real + + + 2024-05-15T08:28:34.070Z + 2024-05-15T08:28:34.033Z + 2024-05-15T08:26:42.051Z + certain + high + + active + + 2024-05-15T08:26:00.000Z + + + + + + Tie 18871, eli Kiviranta, Kuusamo. Liikennetiedote. + + Tie 18871, eli Kiviranta, Kuusamo. + Tarkempi paikka: Paikasta Nevalan tienhaara 2,1 km, vaikutusalue 3,0 km, suuntaan Myllyaho. + + Tulvavesi on noussut tielle. + Tie on suljettu liikenteeltä. + + + + + + 6 + 17 + 1.11.43 + + unknown + + + + 35215 + + + 2103 + + + + + 35216 + + + 5879 + + + + + flooding + + + 2024-05-15T08:28:34.070Z + 2024-05-15T08:28:34.033Z + 2024-05-15T08:26:42.051Z + certain + high + + active + + 2024-05-15T08:26:00.000Z + + + + mandatory + roadClosed + + + + diff --git a/src/test/resources/wazefeed/Roadwork.json b/src/test/resources/wazefeed/Roadwork.json new file mode 100644 index 000000000..b3841989e --- /dev/null +++ b/src/test/resources/wazefeed/Roadwork.json @@ -0,0 +1,176 @@ +{ + "geometry" : { + "type" : "Point", + "coordinates" : [ 22.85828, 60.898445 ] + }, + "type" : "Feature", + "properties" : { + "situationId" : "GUID50428624", + "version" : 1, + "situationType" : "road work", + "releaseTime" : "2024-05-14T10:02:08.018Z", + "versionTime" : "2024-05-14T10:02:08.017Z", + "announcements" : [ { + "language" : "fi", + "title" : "Tie 12603, eli Auvaistentie, Loimaa. Tietyö. ", + "location" : { + "countryCode" : 6, + "locationTableNumber" : 17, + "locationTableVersion" : "1.11.43", + "description" : "Tie 12603, eli Auvaistentie, Loimaa.\nTarkempi paikka: Paikasta Kytömaantien risteys 400 m, suuntaan Ylhäinen. Mökköisten silta." + }, + "locationDetails" : { + "roadAddressLocation" : { + "primaryPoint" : { + "municipality" : "Loimaa", + "province" : "Varsinais-Suomi", + "country" : "Suomi", + "roadAddress" : { + "road" : 12603, + "roadSection" : 1, + "distance" : 3697 + }, + "roadName" : "Auvaistentie", + "alertCLocation" : { + "locationCode" : 24700, + "name" : "Kytömaantien risteys", + "distance" : 413 + } + }, + "secondaryPoint" : { + "municipality" : "Loimaa", + "province" : "Varsinais-Suomi", + "country" : "Suomi", + "roadAddress" : { + "road" : 12603, + "roadSection" : 1, + "distance" : 3697 + }, + "roadName" : "Auvaistentie", + "alertCLocation" : { + "locationCode" : 24701, + "name" : "Ylhäinen", + "distance" : 455 + } + }, + "direction" : "unknown" + } + }, + "features" : [ ], + "roadWorkPhases" : [ { + "id" : "GUID50430489", + "location" : { + "countryCode" : 6, + "locationTableNumber" : 17, + "locationTableVersion" : "1.11.43", + "description" : "Tie 12603, eli Auvaistentie, Loimaa.\nTarkempi paikka: Paikasta Kytömaantien risteys 400 m, suuntaan Ylhäinen. Mökköisten silta." + }, + "locationDetails" : { + "roadAddressLocation" : { + "primaryPoint" : { + "municipality" : "Loimaa", + "province" : "Varsinais-Suomi", + "country" : "Suomi", + "roadAddress" : { + "road" : 12603, + "roadSection" : 1, + "distance" : 3697 + }, + "roadName" : "Auvaistentie", + "alertCLocation" : { + "locationCode" : 24700, + "name" : "Kytömaantien risteys", + "distance" : 413 + } + }, + "secondaryPoint" : { + "municipality" : "Loimaa", + "province" : "Varsinais-Suomi", + "country" : "Suomi", + "roadAddress" : { + "road" : 12603, + "roadSection" : 1, + "distance" : 3697 + }, + "roadName" : "Auvaistentie", + "alertCLocation" : { + "locationCode" : 24701, + "name" : "Ylhäinen", + "distance" : 455 + } + }, + "direction" : "unknown" + } + }, + "worktypes" : [ { + "type" : "bridge", + "description" : "Siltatyö" + }, { + "type" : "road construction", + "description" : "Tienrakennus" + }, { + "type" : "other", + "description" : "" + } ], + "restrictions" : [ { + "type" : "road closed", + "restriction" : { + "name" : "Tie on suljettu liikenteeltä" + } + }, { + "type" : "detour", + "restriction" : { + "name" : "Kiertotie käytössä" + } + } ], + "restrictionsLiftable" : false, + "severity" : "highest", + "workingHours" : [ { + "weekday" : "Thursday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Saturday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Wednesday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Sunday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Friday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Monday", + "startTime" : "00:00", + "endTime" : "00:00" + }, { + "weekday" : "Tuesday", + "startTime" : "00:00", + "endTime" : "00:00" + } ], + "slowTrafficTimes" : [ ], + "queuingTrafficTimes" : [ ], + "timeAndDuration" : { + "startTime" : "2024-05-12T21:00:00.000Z", + "endTime" : "2024-10-31T21:59:59.999Z" + } + } ], + "timeAndDuration" : { + "startTime" : "2024-05-12T21:00:00.000Z", + "endTime" : "2024-10-31T21:59:59.999Z" + }, + "additionalInformation" : "Liikenne- ja kelitiedot verkossa: https://liikennetilanne.fintraffic.fi/", + "sender" : "Fintraffic Tieliikennekeskus Turku" + } ], + "contact" : { + "phone" : "02002100", + "email" : "turku.liikennekeskus@fintraffic.fi" + } + } +} \ No newline at end of file diff --git a/src/test/resources/wazefeed/Roadwork.xml b/src/test/resources/wazefeed/Roadwork.xml new file mode 100644 index 000000000..e1c1eccd2 --- /dev/null +++ b/src/test/resources/wazefeed/Roadwork.xml @@ -0,0 +1,120 @@ + + + + + fi + FTA + + + + 2024-05-14T10:02:07.810Z + + fi + FTA + + + highest + + restrictedToAuthoritiesTrafficOperatorsAndPublishers + real + + + 2024-05-14T10:02:08.018Z + 2024-05-14T10:02:08.017Z + 2024-05-14T10:02:08.018Z + certain + highest + + definedByValidityTimeSpec + + 2024-05-12T21:00:00.000Z + 2024-10-31T21:59:59.999Z + + + + + + Tie 12603, eli Auvaistentie, Loimaa. Tietyö. + + Tie 12603, eli Auvaistentie, Loimaa. + Tarkempi paikka: Paikasta Kytömaantien risteys 400 m, suuntaan Ylhäinen. Mökköisten silta. Merkittävää haittaa liikenteelle. + + Siltatyö + Tienrakennus + Tie on suljettu liikenteeltä + Kiertotie käytössä + + Ajankohta: 13.05.2024 - 31.10.2024. + Työaika: ma-su 00:00-00:00 + + + + + + + + 6 + 17 + 1.11.43 + + unknown + + + + 24700 + + + 413 + + + + + 24701 + + + 455 + + + + + mandatory + roadClosed + + + 2024-05-14T10:02:08.018Z + 2024-05-14T10:02:08.017Z + 2024-05-14T10:02:08.018Z + certain + highest + + definedByValidityTimeSpec + + 2024-05-12T21:00:00.000Z + 2024-10-31T21:59:59.999Z + + + + constructionWork + + + 2024-05-14T10:02:08.018Z + 2024-05-14T10:02:08.017Z + 2024-05-14T10:02:08.018Z + certain + highest + + definedByValidityTimeSpec + + 2024-05-12T21:00:00.000Z + 2024-10-31T21:59:59.999Z + + + + + bridge + + other + + + +