diff --git a/ors-api/src/test/java/org/heigit/ors/apitests/routing/ResultTest.java b/ors-api/src/test/java/org/heigit/ors/apitests/routing/ResultTest.java index 875fde4817..0556a15b23 100644 --- a/ors-api/src/test/java/org/heigit/ors/apitests/routing/ResultTest.java +++ b/ors-api/src/test/java/org/heigit/ors/apitests/routing/ResultTest.java @@ -2312,7 +2312,6 @@ void testWheelchairKerbRestriction() { .assertThat() .body("any { it.key == 'routes' }", is(true)) .body("routes[0].summary.distance", is(74.1f)) - .body("routes[0].summary.duration", is(49.2f)) .statusCode(200); restrictions = new JSONObject(); @@ -2333,6 +2332,19 @@ void testWheelchairKerbRestriction() { .assertThat() .body("any { it.key == 'routes' }", is(true)) .body("routes[0].summary.distance", is(105.8f)) + .statusCode(200); + + body.put("preference", "fastest"); + + given() + .headers(CommonHeaders.jsonContent) + .pathParam("profile", "wheelchair") + .body(body.toString()) + .when() + .post(getEndPointPath() + "/{profile}") + .then() + .assertThat() + .body("any { it.key == 'routes' }", is(true)) .body("routes[0].summary.duration", is(90.7f)) .statusCode(200); } diff --git a/ors-api/src/test/resources/application-test.yml b/ors-api/src/test/resources/application-test.yml index 882a76e766..c0c6fd22bd 100644 --- a/ors-api/src/test/resources/application-test.yml +++ b/ors-api/src/test/resources/application-test.yml @@ -333,4 +333,4 @@ ors: - time_after: 2020-01-01T00:00:00Z - time_before: 2050-06-01T00:00:00Z - active: true - text: This message would be sent with every request + text: This message would be sent with every request \ No newline at end of file diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairIncline.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairIncline.java new file mode 100644 index 0000000000..3406c76948 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairIncline.java @@ -0,0 +1,19 @@ +package com.graphhopper.routing.ev; + +/** + * EncodedValue for wheelchair incline attribute. + * Stores incline percentage as a signed integer value (range: -50 to +50). + */ +public class WheelchairIncline { + public static final String KEY = "wheelchair_incline"; + + private WheelchairIncline() { + // Private constructor to prevent instantiation + } + + public static IntEncodedValue create() { + // 6 bits signed to store 0% to +50% incline + return new UnsignedIntEncodedValue(KEY, 6, false) { + }; + } +} \ No newline at end of file diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairKerb.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairKerb.java new file mode 100644 index 0000000000..764eb55ca1 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairKerb.java @@ -0,0 +1,19 @@ +package com.graphhopper.routing.ev; + +/** + * EncodedValue for wheelchair kerb/curb height attribute. + * Stores kerb height in centimeters as an unsigned integer value (range: 0-30cm). + */ +public class WheelchairKerb { + public static final String KEY = "wheelchair_kerb"; + + private WheelchairKerb() { + // Private constructor to prevent instantiation + } + + public static IntEncodedValue create() { + // 6 bits unsigned to store 0-30cm kerb height + return new UnsignedIntEncodedValue(KEY, 6, false); + } +} + diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSide.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSide.java new file mode 100644 index 0000000000..17d9168ada --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSide.java @@ -0,0 +1,12 @@ +package com.graphhopper.routing.ev; + +import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; + +// Side of the sidewalk, if attached. +public class WheelchairSide { + public static final String KEY = "wheelchair_side"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, WheelchairAttributes.Side.class); + } +} diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSmoothness.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSmoothness.java new file mode 100644 index 0000000000..303ae2c90b --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSmoothness.java @@ -0,0 +1,9 @@ +package com.graphhopper.routing.ev; + +public class WheelchairSmoothness { + public static final String KEY = "wheelchair_smoothness"; + + public static IntEncodedValue create() { + return new UnsignedIntEncodedValue(KEY, 4, false); + } +} diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSuitable.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSuitable.java new file mode 100644 index 0000000000..d05127d342 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSuitable.java @@ -0,0 +1,17 @@ +package com.graphhopper.routing.ev; + +/** + * EncodedValue for wheelchair suitable attribute. + */ +public class WheelchairSuitable { + public static final String KEY = "wheelchair_suitable"; + + private WheelchairSuitable() { + // Private constructor to prevent instantiation + } + + public static SimpleBooleanEncodedValue create() { + return new SimpleBooleanEncodedValue(KEY); + } +} + diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurface.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurface.java new file mode 100644 index 0000000000..39e6961281 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurface.java @@ -0,0 +1,9 @@ +package com.graphhopper.routing.ev; + +public class WheelchairSurface { + public static final String KEY = "wheelchair_surface"; + + public static IntEncodedValue create() { + return new UnsignedIntEncodedValue(KEY, 5, false); + } +} diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurfaceQualityKnown.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurfaceQualityKnown.java new file mode 100644 index 0000000000..850ffcfb0f --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurfaceQualityKnown.java @@ -0,0 +1,13 @@ +package com.graphhopper.routing.ev; + +public class WheelchairSurfaceQualityKnown { + public static final String KEY = "wheelchair_surface_quality_known"; + + private WheelchairSurfaceQualityKnown() { + // Private constructor to prevent instantiation + } + + public static SimpleBooleanEncodedValue create() { + return new SimpleBooleanEncodedValue(KEY, false); + } +} diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairTrackType.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairTrackType.java new file mode 100644 index 0000000000..569438256c --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairTrackType.java @@ -0,0 +1,9 @@ +package com.graphhopper.routing.ev; + +public class WheelchairTrackType { + public static final String KEY = "wheelchair_track_type"; + + public static IntEncodedValue create() { + return new UnsignedIntEncodedValue(KEY, 3, false); + } +} diff --git a/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairWidth.java b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairWidth.java new file mode 100644 index 0000000000..79faed63f8 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairWidth.java @@ -0,0 +1,19 @@ +package com.graphhopper.routing.ev; + +/** + * EncodedValue for wheelchair width attribute. + * Stores width in centimeters as an unsigned integer value (range: 0-500cm). + */ +public class WheelchairWidth { + public static final String KEY = "wheelchair_width"; + + private WheelchairWidth() { + // Private constructor to prevent instantiation + } + + public static IntEncodedValue create() { + // 9 bits unsigned to store 0-500cm (0-5 meters) + return new UnsignedIntEncodedValue(KEY, 9, false); + } +} + diff --git a/ors-engine/src/main/java/org/heigit/ors/config/profile/BuildProperties.java b/ors-engine/src/main/java/org/heigit/ors/config/profile/BuildProperties.java index 26abc83e46..2150f4b277 100644 --- a/ors-engine/src/main/java/org/heigit/ors/config/profile/BuildProperties.java +++ b/ors-engine/src/main/java/org/heigit/ors/config/profile/BuildProperties.java @@ -102,6 +102,7 @@ public void initExtStorages() { continue; } ExtendedStorageName extendedStorageName = ExtendedStorageName.getEnum(key); + switch (extendedStorageName) { case HEAVY_VEHICLE -> handleHeavyVehicle(storage); case OSM_ID -> handleOsmId(); @@ -111,6 +112,7 @@ public void initExtStorages() { case TOLLWAYS -> handleTollways(); case HILL_INDEX -> handleHillIndex(); case TRAIL_DIFFICULTY -> handleTrailDifficulty(); + case WHEELCHAIR -> handleWheelchair(); default -> { storage.initialize(extendedStorageName); this.extStorages.put(key, storage); @@ -226,6 +228,36 @@ private void handleTrailDifficulty() { } } + private void handleWheelchair() { + if (encodedValues.getWheelchairSurface() == null) { + encodedValues.setWheelchairSurface(true); + } + if (encodedValues.getWheelchairSmoothness() == null) { + encodedValues.setWheelchairSmoothness(true); + } + if (encodedValues.getWheelchairTrackType() == null) { + encodedValues.setWheelchairTrackType(true); + } + if (encodedValues.getWheelchairIncline() == null) { + encodedValues.setWheelchairIncline(true); + } + if (encodedValues.getWheelchairWidth() == null) { + encodedValues.setWheelchairWidth(true); + } + if (encodedValues.getWheelchairKerb() == null) { + encodedValues.setWheelchairKerb(true); + } + if(encodedValues.getWheelchairSuitable() == null) { + encodedValues.setWheelchairSuitable(true); + } + if (encodedValues.getWheelchairSide() == null) { + encodedValues.setWheelchairSide(true); + } + if(encodedValues.getWheelchairSurfaceQualityKnown() == null) { + encodedValues.setWheelchairSurfaceQualityKnown(true); + } + } + @JsonIgnore public String getEncoderOptionsString() { if (encoderOptions == null) return ""; diff --git a/ors-engine/src/main/java/org/heigit/ors/config/profile/EncodedValuesProperties.java b/ors-engine/src/main/java/org/heigit/ors/config/profile/EncodedValuesProperties.java index 3365f2a99a..0db1a2d04a 100644 --- a/ors-engine/src/main/java/org/heigit/ors/config/profile/EncodedValuesProperties.java +++ b/ors-engine/src/main/java/org/heigit/ors/config/profile/EncodedValuesProperties.java @@ -55,6 +55,24 @@ public class EncodedValuesProperties { private Boolean maxWeight; @JsonProperty(MaxWidth.KEY) private Boolean maxWidth; + @JsonProperty(WheelchairSurface.KEY) + private Boolean wheelchairSurface; + @JsonProperty(WheelchairSmoothness.KEY) + private Boolean wheelchairSmoothness; + @JsonProperty(WheelchairTrackType.KEY) + private Boolean wheelchairTrackType; + @JsonProperty(WheelchairSurfaceQualityKnown.KEY) + private Boolean wheelchairSurfaceQualityKnown; + @JsonProperty(WheelchairIncline.KEY) + private Boolean wheelchairIncline; + @JsonProperty(WheelchairWidth.KEY) + private Boolean wheelchairWidth; + @JsonProperty(WheelchairKerb.KEY) + private Boolean wheelchairKerb; + @JsonProperty(WheelchairSuitable.KEY) + private Boolean wheelchairSuitable; + @JsonProperty(WheelchairSide.KEY) + private Boolean wheelchairSide; @JsonProperty(AccessRestriction.KEY) private Boolean accessRestriction; @JsonProperty(Toll.KEY) @@ -97,6 +115,15 @@ private Map getProperties() { properties.put(MaxLength.KEY, maxLength); properties.put(MaxWeight.KEY, maxWeight); properties.put(MaxWidth.KEY, maxWidth); + properties.put(WheelchairSurface.KEY, wheelchairSurface); + properties.put(WheelchairSmoothness.KEY, wheelchairSmoothness); + properties.put(WheelchairTrackType.KEY, wheelchairTrackType); + properties.put(WheelchairSurfaceQualityKnown.KEY, wheelchairSurfaceQualityKnown); + properties.put(WheelchairIncline.KEY, wheelchairIncline); + properties.put(WheelchairWidth.KEY, wheelchairWidth); + properties.put(WheelchairKerb.KEY, wheelchairKerb); + properties.put(WheelchairSuitable.KEY, wheelchairSuitable); + properties.put(WheelchairSide.KEY, wheelchairSide); properties.put(AccessRestriction.KEY, accessRestriction); properties.put(Toll.KEY, toll); properties.put(HillIndex.KEY, hillIndex); @@ -139,6 +166,15 @@ public void merge(EncodedValuesProperties other) { maxLength = ofNullable(this.maxLength).orElse(other.maxLength); maxWeight = ofNullable(this.maxWeight).orElse(other.maxWeight); maxWidth = ofNullable(this.maxWidth).orElse(other.maxWidth); + wheelchairSurface = ofNullable(this.wheelchairSurface).orElse(other.wheelchairSurface); + wheelchairSmoothness = ofNullable(this.wheelchairSmoothness).orElse(other.wheelchairSmoothness); + wheelchairTrackType = ofNullable(this.wheelchairTrackType).orElse(other.wheelchairTrackType); + wheelchairSurfaceQualityKnown = ofNullable(this.wheelchairSurfaceQualityKnown).orElse(other.wheelchairSurfaceQualityKnown); + wheelchairIncline = ofNullable(this.wheelchairIncline).orElse(other.wheelchairIncline); + wheelchairWidth = ofNullable(this.wheelchairWidth).orElse(other.wheelchairWidth); + wheelchairKerb = ofNullable(this.wheelchairKerb).orElse(other.wheelchairKerb); + wheelchairSuitable = ofNullable(this.wheelchairSuitable).orElse(other.wheelchairSuitable); + wheelchairSide = ofNullable(this.wheelchairSide).orElse(other.wheelchairSide); accessRestriction = ofNullable(this.accessRestriction).orElse(other.accessRestriction); toll = ofNullable(this.toll).orElse(other.toll); hillIndex = ofNullable(this.hillIndex).orElse(other.hillIndex); @@ -147,3 +183,5 @@ public void merge(EncodedValuesProperties other) { mtbScaleUphill = ofNullable(this.mtbScaleUphill).orElse(other.mtbScaleUphill); } } + + diff --git a/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java b/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java index 6bd1af388d..2b316dca1c 100644 --- a/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java +++ b/ors-engine/src/main/java/org/heigit/ors/export/ExportRequest.java @@ -20,7 +20,7 @@ import org.heigit.ors.routing.WeightingMethod; import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; import org.heigit.ors.routing.graphhopper.extensions.storages.GraphStorageUtils; -import org.heigit.ors.routing.graphhopper.extensions.storages.WheelchairAttributesGraphStorage; +import org.heigit.ors.routing.graphhopper.extensions.util.WheelchairAttributesEncodedValues; import org.heigit.ors.util.ProfileTools; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; @@ -49,7 +49,7 @@ public void setProfile(RoutingProfile profile) { private static final int NO_TIME = -1; private IntEncodedValue osmWayIdEnc; - private WheelchairAttributesGraphStorage wheelchairAttributesGraphStorage; + private WheelchairAttributesEncodedValues wheelchairAttributesEnc; private Weighting weighting; public void setBoundingBox(BBox bbox) { @@ -89,7 +89,9 @@ public ExportResult computeExport() { // Ignore osm_way_id if not available osmWayIdEnc = null; } - wheelchairAttributesGraphStorage = GraphStorageUtils.getGraphExtension(gh.getGraphHopperStorage(), WheelchairAttributesGraphStorage.class); + + if(gh.getEncodingManager().hasEncoder("wheelchair")) + wheelchairAttributesEnc = new WheelchairAttributesEncodedValues(gh.getEncodingManager()); // filter graph for nodes in Bounding Box Set nodesInBBox = nodesInBBox(gh.getLocationIndex(), nodeAccess, graph); @@ -171,10 +173,9 @@ private void addEdgeToResultObject(ExportResult res, EdgeIterator iter, LineStri extra.put("osm_id", osmWayIdEnc.getInt(false, iter.getFlags())); } extra.put("ors_id", iter.getEdge()); - if (wheelchairAttributesGraphStorage != null) { - WheelchairAttributes attributes = new WheelchairAttributes(); - byte[] buffer = new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]; - wheelchairAttributesGraphStorage.getEdgeValues(iter.getEdge(), attributes, buffer); + if (wheelchairAttributesEnc != null) { + WheelchairAttributes attributes = wheelchairAttributesEnc.getAttributes(iter.getFlags()); + if (attributes.hasValues()) { extra.put("incline", attributes.getIncline()); extra.put("surface_quality_known", attributes.isSurfaceQualityKnown()); diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSOSMReader.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSOSMReader.java index 9544dbfb1a..ede1e40d7d 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSOSMReader.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/ORSOSMReader.java @@ -78,6 +78,19 @@ public ORSOSMReader(GraphHopperStorage storage, OSMReaderConfig osmReaderConfig, nodeTagsToStore = new HashSet<>(Arrays.asList("maxheight", "maxweight", "maxweight:hgv", "maxwidth", "maxlength", "maxlength:hgv", "maxaxleload")); osmNodeTagValues = new GHLongObjectHashMap<>(200, .5f); + if(super.encodingManager.hasEncoder("wheelchair")) { + this.processNodeTags = true; + this.processSimpleGeom = true; + extraTagKeys.add("kerb"); + extraTagKeys.add("kerb:both"); + extraTagKeys.add("kerb:left"); + extraTagKeys.add("kerb:right"); + extraTagKeys.add("kerb:height"); + extraTagKeys.add("kerb:both:height"); + extraTagKeys.add("kerb:left:height"); + extraTagKeys.add("kerb:right:height"); + } + // Look if we should do border processing - if so then we have to process the geometry for (GraphStorageBuilder b : this.procCntx.getStorageBuilders()) { if (b instanceof BordersGraphStorageBuilder) { @@ -90,19 +103,6 @@ public ORSOSMReader(GraphHopperStorage storage, OSMReaderConfig osmReaderConfig, this.processGeom = true; this.processWholeGeom = true; } - - if (b instanceof WheelchairGraphStorageBuilder) { - this.processNodeTags = true; - this.processSimpleGeom = true; - extraTagKeys.add("kerb"); - extraTagKeys.add("kerb:both"); - extraTagKeys.add("kerb:left"); - extraTagKeys.add("kerb:right"); - extraTagKeys.add("kerb:height"); - extraTagKeys.add("kerb:both:height"); - extraTagKeys.add("kerb:left:height"); - extraTagKeys.add("kerb:right:height"); - } } if (procCntx.isUseSidewalks()) { @@ -281,6 +281,10 @@ private void onProcessWay(ReaderWay way) { tags.put(internalId, tagsForNode); } } + + if (!tags.isEmpty()) { + way.setTag("ors:node_tags", nodeTags); + } } if (processGeom || processSimpleGeom) { @@ -385,6 +389,7 @@ protected void onProcessEdge(ReaderWay way, EdgeIteratorState edge, IntsRef edge } } + private void storeConditionalAccess(AcceptWay acceptWay, EdgeIteratorState edge) { for (FlagEncoder encoder : encodingManager.fetchEdgeEncoders()) { String encoderName = encoder.toString(); diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/OrsTagParserFactory.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/OrsTagParserFactory.java index f0c84f3a57..a21f1d128b 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/OrsTagParserFactory.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/OrsTagParserFactory.java @@ -6,6 +6,7 @@ import com.graphhopper.routing.util.parsers.TagParserFactory; import com.graphhopper.util.PMap; import org.heigit.ors.routing.graphhopper.extensions.util.parsers.*; +import org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair.*; public class OrsTagParserFactory implements TagParserFactory { private final int profileType; @@ -33,6 +34,15 @@ public TagParser create(String name, PMap configuration) { case GoodsAccess.KEY -> new VehicleAccessParser(GoodsAccess.create(), HeavyVehicleAttributes.GOODS); case HgvAccess.KEY -> new VehicleAccessParser(HgvAccess.create(), HeavyVehicleAttributes.HGV); case HazmatAccess.KEY -> new HazmatAccessParser(); + case WheelchairSurface.KEY -> new WheelchairSurfaceParser(); + case WheelchairSmoothness.KEY -> new WheelchairSmoothnessParser(); + case WheelchairTrackType.KEY -> new WheelchairTrackTypeParser(); + case WheelchairIncline.KEY -> new WheelchairInclineParser(); + case WheelchairSide.KEY -> new WheelchairSideParser(); + case WheelchairWidth.KEY -> new WheelchairWidthParser(); + case WheelchairKerb.KEY -> new WheelchairKerbHeightParser(); + case WheelchairSuitable.KEY -> new WheelchairSuitableParser(); + case WheelchairSurfaceQualityKnown.KEY -> new WheelchairSurfaceQualityKnownParser(); case AccessRestriction.KEY -> new AccessRestrictionsParser(profileType); case HillIndex.KEY -> new HillIndexParser(); case SacScale.KEY -> new SacScaleParser(); diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WheelchairAttributes.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WheelchairAttributes.java index 2a46948d40..0ab6db42ed 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WheelchairAttributes.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/WheelchairAttributes.java @@ -206,4 +206,39 @@ public WheelchairAttributes copy() { at.suitable = this.suitable; return at; } + + public String difference(WheelchairAttributes other) { + String diff = ""; + if(other.getSlopedKerbHeight() != getSlopedKerbHeight()) + diff += "kerbHeight: " + getSlopedKerbHeight() + " vs " + other.getSlopedKerbHeight() + "\n"; + + if(other.getIncline() != getIncline()) + diff += "incline: " + getIncline() + " vs " + other.getIncline() + "\n"; + + if(other.getWidth() != getWidth()) + diff += "width: " + getWidth() + " vs " + other.getWidth() + "\n"; + + if(other.getTrackType() != getTrackType()) + diff += "trackType: " + getTrackType() + " vs " + other.getTrackType() + "\n"; + + if(other.getSide() != getSide()) + diff += "side: " + getSide() + " vs " + other.getSide() + "\n"; + + if(other.getSmoothnessType() != getSmoothnessType()) + diff += "smoothness: " + getSmoothnessType() + " vs " + other.getSmoothnessType() + "\n"; + + if(other.getSurfaceType() != getSurfaceType()) + diff += "surfaceType: " + getSurfaceType() + " vs " + other.getSurfaceType() + "\n"; + + if(other.isSuitable() != isSuitable()) + diff += "suitable: " + isSuitable() + " vs " + other.isSuitable() + "\n"; + + if(other.isSurfaceQualityKnown() != isSurfaceQualityKnown()) + diff += "surfaceQualityKnown: " + isSurfaceQualityKnown() + " vs " + other.isSurfaceQualityKnown() + "\n"; + + if(other.hasAttributes != hasAttributes) + diff += "hasAttributes: " + hasAttributes + " vs " + other.hasAttributes + "\n"; + + return diff; + } } diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/core/CorePreparationHandler.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/core/CorePreparationHandler.java index 026bca2483..ed13b59b9d 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/core/CorePreparationHandler.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/core/CorePreparationHandler.java @@ -106,10 +106,6 @@ private EdgeFilter createCoreEdgeFilter(CHConfig chProfile, GraphHopperStorage g edgeFilterSequence.add(new AvoidBordersCoreEdgeFilter(gs)); } - if (routingProfileCategory == RoutingProfileCategory.WHEELCHAIR) { - edgeFilterSequence.add(new WheelchairCoreEdgeFilter(gs)); - } - /* Maximum speed & turn restrictions */ if ((routingProfileCategory & RoutingProfileCategory.DRIVING) != 0) { String[] encoders = {FlagEncoderNames.CAR_ORS, FlagEncoderNames.HEAVYVEHICLE}; diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/WheelchairEdgeFilter.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/WheelchairEdgeFilter.java index 80845dbc70..6b4cf8e640 100644 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/WheelchairEdgeFilter.java +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/WheelchairEdgeFilter.java @@ -18,32 +18,28 @@ import com.graphhopper.util.EdgeIteratorState; import org.apache.log4j.Logger; import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.heigit.ors.routing.graphhopper.extensions.storages.GraphStorageUtils; -import org.heigit.ors.routing.graphhopper.extensions.storages.WheelchairAttributesGraphStorage; +import org.heigit.ors.routing.graphhopper.extensions.util.WheelchairAttributesEncodedValues; import org.heigit.ors.routing.parameters.WheelchairParameters; public class WheelchairEdgeFilter implements EdgeFilter { private static final Logger LOGGER = Logger.getLogger(WheelchairEdgeFilter.class.getName()); - private final byte[] buffer; - private final WheelchairAttributesGraphStorage storage; - private final WheelchairAttributes attributes; + private final WheelchairAttributesEncodedValues encodedValues; + private WheelchairAttributes attributes; private WheelchairParameters params; public WheelchairEdgeFilter(WheelchairParameters params, GraphHopperStorage graphStorage) throws Exception { - storage = GraphStorageUtils.getGraphExtension(graphStorage, WheelchairAttributesGraphStorage.class); - if (storage == null) - throw new Exception("ExtendedGraphStorage for wheelchair attributes was not found."); + encodedValues = new WheelchairAttributesEncodedValues(graphStorage.getEncodingManager()); this.params = params; if (this.params == null) { this.params = new WheelchairParameters(); } attributes = new WheelchairAttributes(); - buffer = new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]; } @Override public boolean accept(EdgeIteratorState iter) { - storage.getEdgeValues(iter.getEdge(), attributes, buffer); + attributes = encodedValues.getAttributes(iter.getFlags()); + LOGGER.debug("edge: " + iter + (attributes.hasValues() ? " suitable: " + attributes.isSuitable() + " surfaceQualityKnown: " + attributes.isSurfaceQualityKnown() : " no wheelchair attributes")); return !attributes.hasValues() || !( checkSurfaceType() diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/core/WheelchairCoreEdgeFilter.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/core/WheelchairCoreEdgeFilter.java deleted file mode 100644 index 0b0c21f986..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/core/WheelchairCoreEdgeFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* This file is part of Openrouteservice. - * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . - */ -package org.heigit.ors.routing.graphhopper.extensions.edgefilters.core; - -import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.storage.GraphHopperStorage; -import com.graphhopper.util.EdgeIteratorState; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.heigit.ors.routing.graphhopper.extensions.storages.GraphStorageUtils; -import org.heigit.ors.routing.graphhopper.extensions.storages.WheelchairAttributesGraphStorage; - -public final class WheelchairCoreEdgeFilter implements EdgeFilter { - private final byte[] buffer; - private final WheelchairAttributesGraphStorage storage; - private final WheelchairAttributes attributes; - - public WheelchairCoreEdgeFilter(GraphHopperStorage graphStorage) { - buffer = new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]; - attributes = new WheelchairAttributes(); - storage = GraphStorageUtils.getGraphExtension(graphStorage, WheelchairAttributesGraphStorage.class); - } - - @Override - public boolean accept(EdgeIteratorState iter) { - - storage.getEdgeValues(iter.getEdge(), attributes, buffer); - - return !attributes.hasValues(); - - } -} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorage.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorage.java deleted file mode 100644 index 0050330d49..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorage.java +++ /dev/null @@ -1,304 +0,0 @@ -/* This file is part of Openrouteservice. - * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . - */ -package org.heigit.ors.routing.graphhopper.extensions.storages; - -import com.graphhopper.storage.DataAccess; -import com.graphhopper.storage.Directory; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.GraphExtension; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.heigit.ors.routing.graphhopper.extensions.flagencoders.EncodedValueOld; - -public class WheelchairAttributesGraphStorage implements GraphExtension { - protected static final int WIDTH_MAX_VALUE = 300; - protected static final int KERB_MAX_VALUE = 15; - protected static final int INCLINE_MAX_VALUE = 30; - protected static final int TRACK_TYPE_MAX_VALUE = 5; - protected static final int SMOOTHNESS_MAX_VALUE = 8; - protected static final int SURFACE_MAX_VALUE = 30; - - /* pointer for no entry */ - protected final int efWheelchairAttributes; - - protected DataAccess orsEdges; - protected int edgeEntryIndex = 0; - protected int edgeEntryBytes; - protected int edgesCount; // number of edges with custom values - - private final byte[] buffer; - - // bit encoders - private final EncodedValueOld surfaceEncoder; - private final EncodedValueOld smoothnessEncoder; - private final EncodedValueOld trackTypeEncoder; - private final EncodedValueOld sideFlagEncoder; - private final EncodedValueOld kerbHeightEncoder; - private final EncodedValueOld hasKerbHeightEncoder; - private final EncodedValueOld inclineEncoder; - private final EncodedValueOld hasInclineEncoder; - private final EncodedValueOld widthEncoder; - private final EncodedValueOld surfaceQualityKnownEncoder; - private final EncodedValueOld pedestrianisedEncoder; - - public static final int BYTE_COUNT = 5; - - public WheelchairAttributesGraphStorage() { - buffer = new byte[BYTE_COUNT]; - efWheelchairAttributes = 0; - - edgeEntryBytes = edgeEntryIndex + BYTE_COUNT; - edgesCount = 0; - - int shift = 1; - surfaceEncoder = new EncodedValueOld("surface", shift, 5, 1, 0, SURFACE_MAX_VALUE); - shift += surfaceEncoder.getBits(); - - smoothnessEncoder = new EncodedValueOld("smoothness", shift, 4, 1, 0, SMOOTHNESS_MAX_VALUE); - shift += smoothnessEncoder.getBits(); - - trackTypeEncoder = new EncodedValueOld("tracktype", shift, 3, 1, 0, TRACK_TYPE_MAX_VALUE); - shift += trackTypeEncoder.getBits(); - - inclineEncoder = new EncodedValueOld("incline", shift, 5, 1, 0, INCLINE_MAX_VALUE); - shift += inclineEncoder.getBits(); - - kerbHeightEncoder = new EncodedValueOld("kerbHeight", shift, 4, 1, 0, KERB_MAX_VALUE); - shift += kerbHeightEncoder.getBits(); - - widthEncoder = new EncodedValueOld("width", shift, 5, 10, 0, WIDTH_MAX_VALUE); - shift += widthEncoder.getBits(); - - sideFlagEncoder = new EncodedValueOld("side", shift, 2, 1, 0, 2); - shift += sideFlagEncoder.getBits(); - - hasKerbHeightEncoder = new EncodedValueOld("hasKerbHeight", shift, 1, 1, 0, 1); - shift += 1; - - hasInclineEncoder = new EncodedValueOld("hasIncline", shift, 1, 1, 0, 1); - shift += 1; - - surfaceQualityKnownEncoder = new EncodedValueOld("surfaceQualityKnown", shift, 1, 1, 0, 1); - shift += 1; - - pedestrianisedEncoder = new EncodedValueOld("pedestrianised", shift, 1, 1, 0, 1); - - } - - public void init(Graph graph, Directory dir) { - if (edgesCount > 0) - throw new AssertionError("The ORS storage must be initialized only once."); - - this.orsEdges = dir.create("ext_wheelchair"); - } - - public WheelchairAttributesGraphStorage create(long initBytes) { - orsEdges.create(initBytes * edgeEntryBytes); - return this; - } - - public void flush() { - orsEdges.setHeader(0, edgeEntryBytes); - orsEdges.setHeader(4, edgesCount); - orsEdges.flush(); - } - - public void close() { - orsEdges.close(); - } - - @Override - public long getCapacity() { - return orsEdges.getCapacity(); - } - - public int entries() { - return edgesCount; - } - - public boolean loadExisting() { - if (!orsEdges.loadExisting()) - throw new IllegalStateException("Unable to load storage 'ext_wheelchair'. corrupt file or directory? "); - - edgeEntryBytes = orsEdges.getHeader(0); - edgesCount = orsEdges.getHeader(4); - return true; - } - - void ensureEdgesIndex(int edgeIndex) { - orsEdges.ensureCapacity(((long) edgeIndex + 1) * edgeEntryBytes); - } - - public void setEdgeValues(int edgeId, WheelchairAttributes attrs) { - - edgesCount++; - ensureEdgesIndex(edgeId); - - long edgePointer = (long) edgeId * edgeEntryBytes; - - encodeAttributes(attrs, buffer); - - - orsEdges.setBytes(edgePointer + efWheelchairAttributes, buffer, BYTE_COUNT); - - } - - private void encodeAttributes(WheelchairAttributes attrs, byte[] buffer) { - /* - * | flag | surface | smoothness | tracktype | incline | kerbHeight | width | side | hasKerbHeight | hasIncline | surfaceQualityKnown | pedestrianised - * lsb-> | 1 bit | 5 bits | 4 bits | 3 bits | 5 bits | 4 bits | 5 bits | 2 bit | 1 bit | 1 bit | 1 bit | 1 bit | 33 bits in total which can fit into 5 bytes - * - * - */ - - if (attrs.hasValues()) { - long encodedValue = 0; - // set first bit to 1 to mark that we have wheelchair specific attributes for this edge - encodedValue |= (1L); - if (attrs.getSurfaceType() > 0) - encodedValue = surfaceEncoder.setValue(encodedValue, attrs.getSurfaceType()); - - if (attrs.getSmoothnessType() > 0) - encodedValue = smoothnessEncoder.setValue(encodedValue, attrs.getSmoothnessType()); - - if (attrs.getTrackType() > 0) - encodedValue = trackTypeEncoder.setValue(encodedValue, attrs.getTrackType()); - - if (attrs.getIncline() > -1) { - encodedValue = hasInclineEncoder.setValue(encodedValue, 1); - encodedValue = inclineEncoder.setValue(encodedValue, attrs.getIncline()); - } - - if (attrs.getSlopedKerbHeight() > 0.0) { - encodedValue = hasKerbHeightEncoder.setValue(encodedValue, 1); - encodedValue = kerbHeightEncoder.setValue(encodedValue, attrs.getSlopedKerbHeight()); - } - - if (attrs.getWidth() > 0.0) - encodedValue = widthEncoder.setValue(encodedValue, attrs.getWidth()); - - switch (attrs.getSide()) { - case LEFT -> encodedValue = sideFlagEncoder.setValue(encodedValue, 1); - case RIGHT -> encodedValue = sideFlagEncoder.setValue(encodedValue, 2); - default -> { - } - } - - if (attrs.isSurfaceQualityKnown()) { - encodedValue = surfaceQualityKnownEncoder.setValue(encodedValue, 1); - } - - if (attrs.isSuitable()) { - encodedValue = pedestrianisedEncoder.setValue(encodedValue, 1); - } - - buffer[4] = (byte) ((encodedValue >> 32) & 0xFF); - buffer[3] = (byte) ((encodedValue >> 24) & 0xFF); - buffer[2] = (byte) ((encodedValue >> 16) & 0xFF); - buffer[1] = (byte) ((encodedValue >> 8) & 0xFF); - buffer[0] = (byte) ((encodedValue) & 0xFF); - } else { - buffer[0] = 0; - buffer[1] = 0; - buffer[2] = 0; - buffer[3] = 0; - buffer[4] = 0; - } - } - - private void decodeAttributes(WheelchairAttributes attrs, byte[] buffer) { - attrs.reset(); - - if (buffer[0] == 0) - return; - - long encodedValue = (long) buffer[0] & 0xFF; - encodedValue |= (long) (buffer[1] & 0xFF) << 8; - encodedValue |= (long) (buffer[2] & 0xFF) << 16; - encodedValue |= (long) (buffer[3] & 0xFF) << 24; - encodedValue |= (long) (buffer[4] & 0xFF) << 32; - - if ((1 & encodedValue) != 0) { - long iValue = surfaceEncoder.getValue(encodedValue); - if (iValue != 0) - attrs.setSurfaceType((int) iValue); - - iValue = smoothnessEncoder.getValue(encodedValue); - if (iValue != 0) - attrs.setSmoothnessType((int) iValue); - - iValue = trackTypeEncoder.getValue(encodedValue); - if (iValue != 0) - attrs.setTrackType((int) iValue); - - long hasIncline = hasInclineEncoder.getValue(encodedValue); - if (hasIncline > 0) { - iValue = inclineEncoder.getValue(encodedValue); - attrs.setIncline((int) (iValue)); - } - - long hasKerbHeight = hasKerbHeightEncoder.getValue(encodedValue); - if (hasKerbHeight > 0) { - iValue = kerbHeightEncoder.getValue(encodedValue); - attrs.setSlopedKerbHeight((int) (iValue)); - } - - iValue = widthEncoder.getValue(encodedValue); - if (iValue != 0) - attrs.setWidth((int) (iValue)); - - iValue = sideFlagEncoder.getValue(encodedValue); - switch ((int) iValue) { - case 1 -> attrs.setSide(WheelchairAttributes.Side.LEFT); - case 2 -> attrs.setSide(WheelchairAttributes.Side.RIGHT); - default -> attrs.setSide(WheelchairAttributes.Side.UNKNOWN); - } - - iValue = surfaceQualityKnownEncoder.getValue(encodedValue); - attrs.setSurfaceQualityKnown((iValue != 0)); - - iValue = pedestrianisedEncoder.getValue(encodedValue); - attrs.setSuitable((iValue != 0)); - } - } - - public void getEdgeValues(int edgeId, WheelchairAttributes attrs, byte[] buffer) { - long edgePointer = (long) edgeId * (long) edgeEntryBytes; - orsEdges.getBytes(edgePointer + efWheelchairAttributes, buffer, BYTE_COUNT); - decodeAttributes(attrs, buffer); - } - - public boolean isRequireNodeField() { - return false; - } - - public boolean isRequireEdgeField() { - // we require the additional field in the graph to point to the first - // entry in the node table - return true; - } - - public int getDefaultNodeFieldValue() { - throw new UnsupportedOperationException("Not supported by this storage"); - } - - public int getDefaultEdgeFieldValue() { - return -1; - } - - @Override - public boolean isClosed() { - return false; - } - -} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java deleted file mode 100644 index 00157e4b4d..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java +++ /dev/null @@ -1,798 +0,0 @@ -/* This file is part of Openrouteservice. - * - * Openrouteservice is free software; you can redistribute it and/or modify it under the terms of the - * GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 - * of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public License along with this library; - * if not, see . - */ -package org.heigit.ors.routing.graphhopper.extensions.storages.builders; - -import com.graphhopper.GraphHopper; -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.storage.GraphExtension; -import com.graphhopper.util.EdgeIteratorState; -import org.heigit.ors.config.profile.ExtendedStorageProperties; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; -import org.heigit.ors.routing.graphhopper.extensions.storages.WheelchairAttributesGraphStorage; -import org.heigit.ors.util.UnitsConverter; -import org.locationtech.jts.geom.Coordinate; - -import java.util.*; - -public class WheelchairGraphStorageBuilder extends AbstractGraphStorageBuilder { - public static final String KEY_SLOPED_CURB = "sloped_curb"; - public static final String KEY_SLOPED_KERB = "sloped_kerb"; - public static final String KEY_KERB_HEIGHT = "kerb:height"; - public static final String KEY_FOOTWAY = "footway"; - public static final String SW_VAL_RIGHT = "right"; - public static final String SW_VAL_LEFT = "left"; - public static final String KEY_BOTH = "both"; - public static final String KEY_SIDEWALK_BOTH = "sidewalk:both:"; - public static final String KEY_FOOTWAY_BOTH = "footway:both:"; - public static final String KEY_CURB_HEIGHT = "curb:height"; - - public enum Side { - LEFT, - RIGHT, - NONE - } - - private WheelchairAttributesGraphStorage storage; - private final WheelchairAttributes wheelchairAttributes; - private final WheelchairAttributes wheelchairAttributesLeftSide; - private final WheelchairAttributes wheelchairAttributesRightSide; - - private Map> nodeTagsOnWay; - private Map cleanedTags; - - private boolean hasLeftSidewalk = false; - private boolean hasRightSidewalk = false; - private boolean kerbHeightOnlyOnCrossing = false; - - public WheelchairGraphStorageBuilder() { - wheelchairAttributes = new WheelchairAttributes(); - wheelchairAttributesLeftSide = new WheelchairAttributes(); - wheelchairAttributesRightSide = new WheelchairAttributes(); - nodeTagsOnWay = new HashMap<>(); - cleanedTags = new HashMap<>(); - } - - /** - * Constructor - Used for testing - * - * @param onlyAttachKerbsToCrossings Only attach kerb heights to crossings? - */ - public WheelchairGraphStorageBuilder(boolean onlyAttachKerbsToCrossings) { - this(); - kerbHeightOnlyOnCrossing = onlyAttachKerbsToCrossings; - } - - /** - * Initiate the wheelchair storage builder - * - * @param graphhopper The graphhopper instance to run against - * @return The storage that is created from the builder - * @throws Exception Thrown when the storage has already been initialized - */ - @Override - public GraphExtension init(GraphHopper graphhopper) throws Exception { - if (storage != null) - throw new Exception("GraphStorageBuilder has been already initialized."); - - ExtendedStorageProperties parameters; - parameters = this.parameters; - - if (parameters.getKerbsOnCrossings() != null) { - kerbHeightOnlyOnCrossing = parameters.getKerbsOnCrossings(); - } - storage = new WheelchairAttributesGraphStorage(); - return storage; - } - - /** - * Call the processWay method with empty coordinates and tags - * - * @param way The way to process - */ - @Override - public void processWay(ReaderWay way) { - this.processWay(way, new Coordinate[0], new HashMap<>()); - } - - /** - * Process the way - * - * @param way The way to be processed - * @param coords Coordinates of the way - * @param nodeTags Tags that have been stored on nodes of the way that should be used during processing - */ - @Override - public void processWay(ReaderWay way, Coordinate[] coords, Map> nodeTags) { - // Start by resetting storage variables after the previous way - wheelchairAttributes.reset(); - wheelchairAttributesLeftSide.reset(); - wheelchairAttributesRightSide.reset(); - hasRightSidewalk = false; - hasLeftSidewalk = false; - - this.nodeTagsOnWay = nodeTags; - - // Annoyingly, it seems often to be the case that rather than using ":" to seperate tag parts, "." is used, so - // we need to take this into account - cleanedTags = cleanTags(way.getTags()); - - // Now we need to process the way specific to whether it is a separate feature (i.e. footway) or is attached - // to a road feature (i.e. with the tag sidewalk=left) - processWayCheckForSeparateFeature(way); - - // We still need to always process the way itself even if it separate so that we can get sidewalk info (a - // separate footway can still have sidewalk tags...) - processSidewalksAttachedToWay(way); - - // the way has known suitability if it can be classified as seperate footway - wheelchairAttributes.setSuitable(isSeparateFootway(way) || way.hasTag("wheelchair_accessible", true)); - - // the sidewalks always imply known suitability - wheelchairAttributesLeftSide.setSuitable(true); - wheelchairAttributesRightSide.setSuitable(true); - - // Process the kerb tags. - processKerbTags(); - } - - /** - * Return the attributes for the sidewalk on the specified side of the road, or the general attributes when NONE is provided - * - * @param side The side of the road you want the data for - * @return The WheelchairAttributes object containing the inofrmation for the specified side - */ - public WheelchairAttributes getStoredAttributes(Side side) { - return switch (side) { - case LEFT -> wheelchairAttributesLeftSide; - case RIGHT -> wheelchairAttributesRightSide; - case NONE -> wheelchairAttributes; - default -> null; - }; - } - - /** - * Go through tags and attempt to remove any invalid keys (i.e. when compound keys have been entered using a '.' rather than ':' - * - * @param dirtyTags The OSM tag collection that needs to be cleaned - * @return A cleaned version of the tags on the way (. replaced with : in tag names) - */ - private HashMap cleanTags(Map dirtyTags) { - HashMap cleanedTagsMap = new HashMap<>(); - for (Map.Entry entry : dirtyTags.entrySet()) { - String cleanKey = entry.getKey().replace(".", ":"); - cleanedTagsMap.put(cleanKey, entry.getValue()); - } - return cleanedTagsMap; - } - - /** - * Process footways that are attached to an OSM way via the sidewalk tags. It looks for parameters important for - * wheelchair routing such as width, smoothness and kerb height and then stores these in the attributes object - * ready for use when the edge(s) are processed. It also detects which side of the base way that the sidewalks - * have been created for and stores the information appropriately. - * - * @param way The way to be processed - */ - private void processSidewalksAttachedToWay(ReaderWay way) { - - detectAndRecordSidewalkSide(way); - - // get surface type (asphalt, sand etc.) - setSidewalkAttribute(WheelchairAttributes.Attribute.SURFACE); - - // get smoothness value (good, terrible etc.) - setSidewalkAttribute(WheelchairAttributes.Attribute.SMOOTHNESS); - - // Get the track type (grade1, grade4 etc.) - setSidewalkAttribute(WheelchairAttributes.Attribute.TRACK); - - // Get the width of the way (2, 0.1 etc.) - setSidewalkAttribute(WheelchairAttributes.Attribute.WIDTH); - - // Get the incline of the way (10%, 6% etc.) - setSidewalkAttribute(WheelchairAttributes.Attribute.INCLINE); - - } - - /** - * Process a footway that has been stored in OSM as a separate feature, such as a crossing, footpath or pedestrian - * way. The same as the attached processing, it looks for the different attributes as tags that are important for - * wheelchair routing and stores them against the generic wheelchair storage object - */ - private void processWayCheckForSeparateFeature(ReaderWay way) { - boolean markSurfaceQualityKnown = isSeparateFootway(way); - setWayAttribute(WheelchairAttributes.Attribute.SURFACE, markSurfaceQualityKnown); - setWayAttribute(WheelchairAttributes.Attribute.SMOOTHNESS, markSurfaceQualityKnown); - setWayAttribute(WheelchairAttributes.Attribute.TRACK, markSurfaceQualityKnown); - setWayAttribute(WheelchairAttributes.Attribute.WIDTH, markSurfaceQualityKnown); - setWayAttribute(WheelchairAttributes.Attribute.INCLINE, markSurfaceQualityKnown); - } - - /** - * Set the specified attribute in the attribute storage object based on the information gathered from the way. This - * method ony sets the attribute in the attribute storage object for the standalone way and not sidewalks. - * - * @param attribute The attribute to process - * @param markSurfaceQualityKnown Whether or not to also set the surfaceQualityKnown flag in the WheelchairAttributes object - */ - private void setWayAttribute(WheelchairAttributes.Attribute attribute, boolean markSurfaceQualityKnown) { - if (cleanedTags.containsKey(attributeToTagName(attribute))) { - setWheelchairAttribute((String) cleanedTags.get(attributeToTagName(attribute)), attribute, markSurfaceQualityKnown); - } - } - - /** - * Set teh specified attribute in the attribute storage objects for the left and right sidewalks, - * - * @param attribute The attribute to process - */ - private void setSidewalkAttribute(WheelchairAttributes.Attribute attribute) { - String[] tagValues; - - tagValues = getSidedTagValue(attributeToTagName(attribute)); - - if (tagValues[0] != null && !tagValues[0].isEmpty()) { - setSidewalkAttributeForSide(tagValues[0], attribute, Side.LEFT); - } - if (tagValues[1] != null && !tagValues[1].isEmpty()) { - setSidewalkAttributeForSide(tagValues[1], attribute, Side.RIGHT); - } - } - - /** - * Detect if there are sidewalks stored on the way and if so, mark that these are present - * - * @param way The way to look for sidewalks on - */ - private void detectAndRecordSidewalkSide(ReaderWay way) { - if (way.hasTag("sidewalk")) { - String sw = way.getTag("sidewalk"); - switch (sw) { - case SW_VAL_LEFT -> hasLeftSidewalk = true; - case SW_VAL_RIGHT -> hasRightSidewalk = true; - case KEY_BOTH -> { - hasLeftSidewalk = true; - hasRightSidewalk = true; - } - default -> { - } - } - } - } - - /** - * Convert an attribute from the wheelchair attribute storage to a corresponding osm tag key - * - * @param attribute The attribute that the tag key is required for - * @return The OSM tag key that corresponds to the attribute - */ - private String attributeToTagName(WheelchairAttributes.Attribute attribute) { - return switch (attribute) { - case SURFACE -> "surface"; - case SMOOTHNESS -> "smoothness"; - case TRACK -> "tracktype"; - case WIDTH -> "width"; - case INCLINE -> "incline"; - case KERB -> "kerb"; - default -> ""; - }; - } - - /** - * Set the specified attribute of the specified sidewalk to be the value passed - * - * @param value The value to store - * @param attribute The attribute to store the value against - * @param side The sidewalk the attribute is for - */ - private void setSidewalkAttributeForSide(String value, WheelchairAttributes.Attribute attribute, Side side) { - switch (side) { - case LEFT -> { - hasLeftSidewalk = true; - wheelchairAttributesLeftSide.setAttribute(attribute, convertTagValueToEncodedValue(attribute, value), true); - } - case RIGHT -> { - hasRightSidewalk = true; - wheelchairAttributesRightSide.setAttribute(attribute, convertTagValueToEncodedValue(attribute, value), true); - } - default -> { - } - } - } - - /** - * Set the specified attribute on the standalone way to be the value passed - * - * @param value The value to store - * @param attribute The attribute to store the value against - * @param markSurfaceQualityKnown Whether or not to also set the surfaceQualityKnown flag in the WheelchairAttributes object - */ - private void setWheelchairAttribute(String value, WheelchairAttributes.Attribute attribute, boolean markSurfaceQualityKnown) { - wheelchairAttributes.setAttribute(attribute, convertTagValueToEncodedValue(attribute, value), markSurfaceQualityKnown); - } - - /** - * Transform (if needed) a value into an encoded value using the correct encoder. - * - * @param attribute The attribute the value is for - * @param tagValue The string value stored in the tag - * @return The correctly encoded value - */ - private int convertTagValueToEncodedValue(WheelchairAttributes.Attribute attribute, String tagValue) { - switch (attribute) { - case SMOOTHNESS: - case TRACK: - case SURFACE: - try { - return WheelchairTypesEncoder.getEncodedType(attribute, tagValue.toLowerCase()); - } catch (Exception notRecognisedEncodedTypeError) { - return -1; - } - case WIDTH: - return (int) (UnitsConverter.convertOSMDistanceTagToMeters(tagValue.toLowerCase()) * 100); - case INCLINE: - return getInclineFromTagValue(tagValue.toLowerCase()); - case KERB: - return convertKerbTagValueToCentimetres(tagValue.toLowerCase()); - default: - return -1; - } - } - - /** - * Get the kerb height value from a set of tags. The method takes into account different ways of spelling and representing the kerb height and then adds the kerb information - * to the wheelchair attribute objects - */ - private void processKerbTags() { - String[] assumedKerbTags = new String[]{ - "curb", - "kerb", - KEY_SLOPED_CURB, - KEY_SLOPED_KERB - }; - String[] explicitKerbTags = new String[]{ - KEY_KERB_HEIGHT, - KEY_CURB_HEIGHT - }; - - int height = calcSingleKerbHeightFromTagList(assumedKerbTags, -1); - // Explicit heights overwrite assumed - height = calcSingleKerbHeightFromTagList(explicitKerbTags, height); - - if (height > -1) { - wheelchairAttributes.setSlopedKerbHeight(height); - } - - // Now for if the values are attached to sides of the way - int[] heights = calcSingleKerbHeightFromSidedTagList(assumedKerbTags, new int[]{-1, -1}); - heights = calcSingleKerbHeightFromSidedTagList(explicitKerbTags, heights); - - if (heights[0] > -1) { - hasLeftSidewalk = true; - wheelchairAttributesLeftSide.setSlopedKerbHeight(heights[0]); - } - - if (heights[1] > -1) { - hasLeftSidewalk = true; - wheelchairAttributesRightSide.setSlopedKerbHeight(heights[1]); - } - } - - /** - * Calculate the kerb height from the way that should be stored on the graph bsaed on the tag keys specified - * - * @param kerbTags The tag keys that should be evaluated - * @param initialValue The initial value for the return. If no kerb height info is found, this value is returned - * @return The value to use as the kerb height derived from the specified tag keys. - */ - private int calcSingleKerbHeightFromTagList(String[] kerbTags, int initialValue) { - int height = initialValue; - for (String kerbTag : kerbTags) { - int kerbHeightValue = convertKerbTagValueToCentimetres((String) cleanedTags.get(kerbTag)); - if (kerbHeightValue != -1) { - height = kerbHeightValue; - } - } - return height; - } - - /** - * Calculate the kerb heights from the way that should be stored on the graph bsaed on the tag keys specified. - * This method looks at the tags which specify a side to the road) - * - * @param kerbTags The tag keys that should be evaluated - * @param initialValues The initial value for the return. If no kerb height info is found, this value is returned - * @return The values to use as the kerb height derived from the specified tag keys. The first item - * in the array is for the left side, and the second is the right side. - */ - private int[] calcSingleKerbHeightFromSidedTagList(String[] kerbTags, int[] initialValues) { - int[] heights = initialValues; - int height = -1; - for (String kerbTag : kerbTags) { - String[] tagValues = getSidedKerbTagValuesToApply(kerbTag); - if (tagValues[0] != null && !tagValues[0].isEmpty()) { - height = convertKerbTagValueToCentimetres(tagValues[0].toLowerCase()); - if (height > -1) { - heights[0] = height; - } - } - if (tagValues[1] != null && !tagValues[1].isEmpty()) { - height = convertKerbTagValueToCentimetres(tagValues[1].toLowerCase()); - if (height > -1) { - heights[1] = height; - } - } - } - - return heights; - } - - /** - * Look at way and try to find the correct kerb heights for it. In some cases when the kerbs are attached directly to a way they are - * marked as start and end and so we need to look through the various tags to try and find these. - * - * @param key The base key that we are investigating (e.g. "kerb", "sloped_kerb" etc.) - * @return The textual tag that should be used as the kerb height - */ - private String[] getSidedKerbTagValuesToApply(String key) { - // If we are looking at the kerbs, sometimes the start and end of a way is marked as having different kerb - // heights using the ...:start and ...:end tags. For now, we just want to get the worse of these values (the - // highest) - double leftStart = -1; - double leftEnd = -1; - double rightStart = -1; - double rightEnd = -1; - - String[] endValues = getSidedTagValue(key + ":end"); - // Convert - if (endValues[0] != null && !endValues[0].isEmpty()) { - leftEnd = convertKerbTagValueToCentimetres(endValues[0]); - } - if (endValues[1] != null && !endValues[1].isEmpty()) { - rightEnd = convertKerbTagValueToCentimetres(endValues[1]); - } - String[] startValues = getSidedTagValue(key + ":start"); - // Convert - if (startValues[0] != null && !startValues[0].isEmpty()) { - leftStart = convertKerbTagValueToCentimetres(startValues[0]); - } - if (startValues[1] != null && !startValues[1].isEmpty()) { - rightStart = convertKerbTagValueToCentimetres(startValues[1]); - } - - // Now compare to find the worst - String[] values = new String[2]; - if (leftEnd > leftStart) - values[0] = endValues[0]; - else if (leftStart > leftEnd) - values[0] = startValues[0]; - - if (rightEnd > rightStart) - values[1] = endValues[1]; - else if (rightStart > rightEnd) - values[1] = startValues[1]; - - return values; - } - - /** - * Compare the attributes gained for the given property between the sidewalks on the left and the right hand side - * of the feature and identify which is worse. This is useful if for some reason the sidewalks can not be created - * as separate edges from the feature, in which case you would avoid the whole way if an attribute was seen as - * impassible. - * - * @param attr The attribute to be assessed (surface, smoothness etc.) - * @return The value that is seen as being the worst - */ - private int getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute attr) { - switch (attr) { - case SURFACE: - return Math.max(Math.max(wheelchairAttributesLeftSide.getSurfaceType(), wheelchairAttributesRightSide.getSurfaceType()), - wheelchairAttributes.getSurfaceType()); - case SMOOTHNESS: - return Math.max(Math.max(wheelchairAttributesLeftSide.getSmoothnessType(), wheelchairAttributesRightSide.getSmoothnessType()), - wheelchairAttributes.getSmoothnessType()); - case KERB: - return Math.max(Math.max(wheelchairAttributesLeftSide.getSlopedKerbHeight(), wheelchairAttributesRightSide.getSlopedKerbHeight()), - wheelchairAttributes.getSlopedKerbHeight()); - case WIDTH: - // default value is 0, but this will always be returned so we need to do a check - int l = wheelchairAttributesLeftSide.getWidth(); - int r = wheelchairAttributesRightSide.getWidth(); - int w = wheelchairAttributes.getWidth(); - if (l <= 0) l = Integer.MAX_VALUE; - if (r <= 0) r = Integer.MAX_VALUE; - if (w <= 0) w = Integer.MAX_VALUE; - - int ret = Math.min(Math.min(l, r), w); - if (ret == Integer.MAX_VALUE) ret = 0; - - return ret; - case TRACK: - return Math.max(Math.max(wheelchairAttributesLeftSide.getTrackType(), wheelchairAttributesRightSide.getTrackType()), - wheelchairAttributes.getTrackType()); - case INCLINE: - return Math.max(Math.max(wheelchairAttributesLeftSide.getIncline(), wheelchairAttributesRightSide.getIncline()), - wheelchairAttributes.getIncline()); - - default: - return 0; - } - } - - /** - * Process an individual edge which has been derived from the way and then store it in the storage. - * - * @param way The parent way feature - * @param edge The specific edge to be processed - */ - @Override - public void processEdge(ReaderWay way, EdgeIteratorState edge) { - // We want to copy so that we don't overwrite original values as this edge is only part of the way - WheelchairAttributes at = wheelchairAttributes.copy(); - - // Get the kerb heights for the individual edge as this may overwrite the original - int kerbHeight = getKerbHeightForEdge(way); - if (kerbHeight > -1) { - at.setSlopedKerbHeight(kerbHeight); - } - - // Check for if we have specified which side the processing is for - if (way.hasTag("ors-sidewalk-side")) { - String side = way.getTag("ors-sidewalk-side"); - if (side.equals(SW_VAL_LEFT)) { - // Only get the attributes for the left side - at = getAttributes(SW_VAL_LEFT); - at.setSide(WheelchairAttributes.Side.LEFT); - } - if (side.equals(SW_VAL_RIGHT)) { - at = getAttributes(SW_VAL_RIGHT); - at.setSide(WheelchairAttributes.Side.RIGHT); - } - } else { - // if we have sidewalks attached, then we should also look at those. We should only hit this point if - // the preprocessing hasn't detected that there are sidewalks even though there are... - if (hasRightSidewalk || hasLeftSidewalk) { - at = combineAttributesOfWayWhenBothSidesPresent(at); - } - } - - storage.setEdgeValues(edge.getEdge(), at); - - } - - /** - * Get an overriding kerb height if needed from the nodes that are on the way rather than the data stored on the way itself. - * This should be the case if we are specifying to only store kerb heights on crossings as these features do not normally - * have kerb heights attached to them - * - * @param way The way that is being investigated - * @return A kerb height from the tags of the nodes on the way, or -1 if no kerb heights are found/required - */ - int getKerbHeightForEdge(ReaderWay way) { - int kerbHeight = -1; - - if (!kerbHeightOnlyOnCrossing || (way.hasTag(KEY_FOOTWAY) && way.getTag(KEY_FOOTWAY).equals("crossing"))) { - // Look for kerb information - kerbHeight = getKerbHeightFromNodeTags(); - } - - return kerbHeight; - } - - /** - * Look at the information stored against the nodes of the way and extract the kerb height to use for the whole way - * from those data. - * - * @return The derived kerb height in centimetres from teh nodes that are on the way - */ - int getKerbHeightFromNodeTags() { - // Assumed kerb heights are those obtained from a tag without the explicit :height attribute - List assumedKerbHeights = new ArrayList<>(); - // Explicit heights are those provided by the :height tag - these should take precidence - List explicitKerbHeights = new ArrayList<>(); - - for (Map.Entry> entry : nodeTagsOnWay.entrySet()) { - Map tags = entry.getValue(); - for (Map.Entry tag : tags.entrySet()) { - switch (tag.getKey()) { - case KEY_SLOPED_CURB, "curb", "kerb", KEY_SLOPED_KERB -> - assumedKerbHeights.add(convertKerbTagValueToCentimetres(tag.getValue())); - case KEY_KERB_HEIGHT -> explicitKerbHeights.add(convertKerbTagValueToCentimetres(tag.getValue())); - default -> { - } - } - } - } - if (!explicitKerbHeights.isEmpty()) { - return Collections.max(explicitKerbHeights); - } else if (!assumedKerbHeights.isEmpty()) { - // If we have multiple kerb heights, we need to apply the largest to the edge as this is the worst - return Collections.max(assumedKerbHeights); - } else { - return -1; - } - } - - /** - * When sidewalks are tagged on both sides for a way and we do not want to process them as separate items then we need - * to get the "worst" for each attribute and use that. - * - * @param attributes The attributes storage object that needs to be merged - * @return A resultant combined object - */ - public WheelchairAttributes combineAttributesOfWayWhenBothSidesPresent(WheelchairAttributes attributes) { - WheelchairAttributes at = attributes; - - int tr = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.TRACK); - if (tr > 0) at.setTrackType(tr); - - int su = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.SURFACE); - if (su > 0) at.setSurfaceType(su); - - int sm = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.SMOOTHNESS); - if (sm > 0) at.setSmoothnessType(sm); - - int sl = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.KERB); - if (sl > 0) at.setSlopedKerbHeight(sl); - - int wi = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.WIDTH); - if (wi > 0) at.setWidth(wi); - - int in = getWorseAttributeValueFromSeparateItems(WheelchairAttributes.Attribute.INCLINE); - if (in > 0) at.setIncline(in); - - at.setSurfaceQualityKnown( - wheelchairAttributesLeftSide.isSurfaceQualityKnown() - && wheelchairAttributesRightSide.isSurfaceQualityKnown() - && attributes.isSurfaceQualityKnown() - ); - - at.setSuitable( - wheelchairAttributesLeftSide.isSuitable() - && wheelchairAttributesRightSide.isSuitable() - && attributes.isSuitable() - ); - - return at; - } - - /** - * Get the attributes of a sidewalk on the specified side of the road - * - * @param side The side you want the attributes for - * @return A WheelchairAttributes object for the side requested. If there are no attributes for the specified - * side, then the overall attributes for the way are returned - */ - private WheelchairAttributes getAttributes(String side) { - WheelchairAttributes at = wheelchairAttributes.copy(); - - // Now get the specific items - switch (side) { - case SW_VAL_LEFT -> at = at.merge(wheelchairAttributesLeftSide); - case SW_VAL_RIGHT -> at = at.merge(wheelchairAttributesRightSide); - default -> { - } - } - return at; - } - - /** - * Converts a kerb height value to a numerical height (in centimetres). A kerb could be stored as an explicit height or - * as an indicator as to whether the kerb is lowered or not. - * - * @param value The value of the tag - * @return The presumed height of the kerb in metres - */ - private int convertKerbTagValueToCentimetres(String value) { - int centimetreHeight = -1; - - if (value == null) { - return -1; - } - switch (value) { - case "yes", KEY_BOTH, "low", "lowered", "dropped", "sloped" -> centimetreHeight = 3; - case "no", "none", "one", "rolled", "regular" -> centimetreHeight = 15; - case "at_grade", "flush" -> centimetreHeight = 0; - default -> { - double metresHeight = UnitsConverter.convertOSMDistanceTagToMeters(value); - // If no unit was given in the tag, the value might be in meters or centimeters; we can only guess - // depending on the value - if (metresHeight < 0.15) { - centimetreHeight = (int) (metresHeight * 100); - } else { - centimetreHeight = (int) metresHeight; - } - } - } - - return centimetreHeight; - } - - /** - * Get the values obtained from a way for a specific sidewalk property. For example, providing the property - * "surface" would check the way for the surface tag stored against attached sidewalks using the keys - * sidewalk:left:surface, sidewalk:right:surface, and sidewalk:both:surface. The obtained values are then returned - * in an array. - * - * @param property The property to be extracted - * @return A String array containing two values - the first is the property for the left sidewalk and - * the second is the property value for the right sidewalk. - */ - private String[] getSidedTagValue(String property) { - String[] values = new String[2]; - // Left side - if (cleanedTags.containsKey("sidewalk:left:" + property)) - values[0] = (String) cleanedTags.get("sidewalk:left:" + property); - else if (cleanedTags.containsKey("footway:left:" + property)) - values[0] = (String) cleanedTags.get("footway:left:" + property); - // Right side - if (cleanedTags.containsKey("sidewalk:right:" + property)) - values[1] = (String) cleanedTags.get("sidewalk:right:" + property); - else if (cleanedTags.containsKey("footway:right:" + property)) - values[1] = (String) cleanedTags.get("footway:right:" + property); - - // Both - if (cleanedTags.containsKey(KEY_SIDEWALK_BOTH + property)) { - values[0] = (String) cleanedTags.get(KEY_SIDEWALK_BOTH + property); - values[1] = (String) cleanedTags.get(KEY_SIDEWALK_BOTH + property); - } else if (cleanedTags.containsKey(KEY_FOOTWAY_BOTH + property)) { - values[0] = (String) cleanedTags.get(KEY_FOOTWAY_BOTH + property); - values[1] = (String) cleanedTags.get(KEY_FOOTWAY_BOTH + property); - } - return values; - } - - private int getInclineFromTagValue(String inclineValue) { - double decimalIncline = UnitsConverter.convertOSMInclineValueToPercentage(inclineValue, true); - decimalIncline = Math.min(decimalIncline, 15.0); - return (int) Math.round(decimalIncline); - } - - - /** - * Determine if the way is a separate footway object or a road feature. - * - * @param way The OSM way object to be assessed - * @return Whether the way is seen as a separately drawn footway (true) or a road (false) - */ - private boolean isSeparateFootway(ReaderWay way) { - String type = way.getTag("highway", ""); - - String[] pedestrianWayTypes = { - "living_street", - "pedestrian", - KEY_FOOTWAY, - "path", - "crossing", - "track" - }; - - // Check if it is a footpath or pedestrian - if (!type.isEmpty()) { - // We are looking at a separate footpath - // we are looking at a road feature so any footway would be attached to it as a tag - return Arrays.asList(pedestrianWayTypes).contains(type); - } - - return true; - } - - @Override - public String getName() { - return "Wheelchair"; - } - -} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/WheelchairAttributesEncodedValues.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/WheelchairAttributesEncodedValues.java new file mode 100644 index 0000000000..19ccb7079e --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/WheelchairAttributesEncodedValues.java @@ -0,0 +1,87 @@ +package org.heigit.ors.routing.graphhopper.extensions.util; + +import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; + +public class WheelchairAttributesEncodedValues { + IntEncodedValue surfaceEncoder; + IntEncodedValue smoothnessEncoder; + IntEncodedValue trackTypeEncoder; + IntEncodedValue inclineEncoder; + IntEncodedValue widthEncoder; + IntEncodedValue kerbEncoder; + BooleanEncodedValue suitableEncoder; + EnumEncodedValue sideEncoder; + BooleanEncodedValue surfaceQualityKnownEncoder; + public IntEncodedValue osmWayIdEnc; + + public WheelchairAttributesEncodedValues(EncodingManager encodingManager) { + if(encodingManager.hasEncodedValue(WheelchairSurface.KEY)) + surfaceEncoder = encodingManager.getIntEncodedValue(WheelchairSurface.KEY); + + if(encodingManager.hasEncodedValue(WheelchairSmoothness.KEY)) + smoothnessEncoder = encodingManager.getIntEncodedValue(WheelchairSmoothness.KEY); + + if(encodingManager.hasEncodedValue(WheelchairTrackType.KEY)) + trackTypeEncoder = encodingManager.getIntEncodedValue(WheelchairTrackType.KEY); + + if(encodingManager.hasEncodedValue(WheelchairIncline.KEY)) + inclineEncoder = encodingManager.getIntEncodedValue(WheelchairIncline.KEY); + + if(encodingManager.hasEncodedValue(WheelchairWidth.KEY)) + widthEncoder = encodingManager.getIntEncodedValue(WheelchairWidth.KEY); + + if(encodingManager.hasEncodedValue(WheelchairKerb.KEY)) + kerbEncoder = encodingManager.getIntEncodedValue(WheelchairKerb.KEY); + + if(encodingManager.hasEncodedValue(WheelchairSuitable.KEY)) + suitableEncoder = encodingManager.getBooleanEncodedValue(WheelchairSuitable.KEY); + + if(encodingManager.hasEncodedValue(WheelchairSide.KEY)) + sideEncoder = encodingManager.getEnumEncodedValue(WheelchairSide.KEY, WheelchairAttributes.Side.class); + + if(encodingManager.hasEncodedValue(WheelchairSurfaceQualityKnown.KEY)) + surfaceQualityKnownEncoder = encodingManager.getBooleanEncodedValue(WheelchairSurfaceQualityKnown.KEY); + + if(encodingManager.hasEncodedValue(OsmWayId.KEY)) + osmWayIdEnc = encodingManager.getIntEncodedValue(OsmWayId.KEY); + } + + public WheelchairAttributes getAttributes(IntsRef edgeFlags) { + WheelchairAttributes attrs = new WheelchairAttributes(); + int surface = surfaceEncoder.getInt(false, edgeFlags) - 1; + if(surface > -1) + attrs.setSurfaceType(surface); + + int smoothness = smoothnessEncoder.getInt(false, edgeFlags) - 1; + if(smoothness > -1) + attrs.setSmoothnessType(smoothness); + + int trackType = trackTypeEncoder.getInt(false, edgeFlags) - 1; + if(trackType > -1) + attrs.setTrackType(trackType); + + attrs.setSuitable(suitableEncoder.getBool(false, edgeFlags)); + attrs.setSide(sideEncoder.getEnum(false, edgeFlags)); + attrs.setSurfaceQualityKnown(surfaceQualityKnownEncoder.getBool(false, edgeFlags)); + + int incline = inclineEncoder.getInt(false, edgeFlags) - 1; // we store unsigned integers, however -1 is used to indicate unknown + if (incline > -1) { + attrs.setIncline(incline); // incline is converts to int in WheelchairAttributes, weird that the method takes a double... + } + + int width = widthEncoder.getInt(false, edgeFlags) - 1; + if (width > 0) { // no idea why we also exclude 0 here, but this was already the previous behavior and changing it to -1 breaks it + attrs.setWidth(width); + } + + int kerb = kerbEncoder.getInt(false, edgeFlags) - 1; + if (kerb > -1) { + attrs.setSlopedKerbHeight(kerb); + } + + return attrs; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairBaseParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairBaseParser.java new file mode 100644 index 0000000000..117d39a59b --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairBaseParser.java @@ -0,0 +1,370 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.EncodedValue; +import com.graphhopper.routing.ev.EncodedValueLookup; +import com.graphhopper.routing.ev.IntEncodedValue; +import com.graphhopper.routing.util.parsers.TagParser; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.util.UnitsConverter; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.IntBinaryOperator; +import java.util.function.ToIntFunction; + +import static org.heigit.ors.routing.graphhopper.extensions.reader.osmfeatureprocessors.OSMAttachedSidewalkProcessor.KEY_ORS_SIDEWALK_SIDE; + +public abstract class WheelchairBaseParser implements TagParser { + public static final String SW_VAL_RIGHT = "right"; + public static final String SW_VAL_LEFT = "left"; + public static final String KEY_BOTH = "both"; + public static final String KEY_SIDEWALK = "sidewalk"; + public static final String KEY_SIDEWALK_BOTH = "sidewalk:both:"; + public static final String KEY_FOOTWAY_BOTH = "footway:both:"; + + protected EncodedValue encoder; + + protected boolean hasLeftSidewalk = false; + protected boolean hasRightSidewalk = false; + + protected Map cleanedTags; + + protected static String[] assumedKerbTags = new String[]{ + "curb", + "kerb", + "sloped_curb", + "sloped_kerb" + }; + + protected static String[] explicitKerbTags = new String[]{ + "kerb:height", + "curb:height" + }; + + @Override + public void createEncodedValues(EncodedValueLookup encodedValueLookup, List list) { + list.add(encoder); + } + + protected void beforeHandleWayTags(ReaderWay way) { + hasLeftSidewalk = false; + hasRightSidewalk = false; + + cleanedTags = cleanTags(way.getTags()); + detectAndRecordSidewalkSide(way); + detectAndRecordSidewalkSideWithKerb(); + } + + protected void setInt(IntsRef edgeFlags, int value) { + ((IntEncodedValue) encoder).setInt(false, edgeFlags, value + 1); + } + + /** + * Detect if there are sidewalks stored on the way and if so, mark that these are present + * + * @param way The way to look for sidewalks on + */ + private void detectAndRecordSidewalkSide(ReaderWay way) { + if (way.hasTag(KEY_SIDEWALK)) { + String sw = way.getTag(KEY_SIDEWALK); + switch (sw) { + case SW_VAL_LEFT -> hasLeftSidewalk = true; + case SW_VAL_RIGHT -> hasRightSidewalk = true; + case KEY_BOTH -> { + hasLeftSidewalk = true; + hasRightSidewalk = true; + } + default -> { + // leave as is + } + } + } + } + + private void detectAndRecordSidewalkSideWithKerb() { + // Now for if the values are attached to sides of the way + int[] heights = calcSingleKerbHeightFromSidedTagList(assumedKerbTags, new int[]{-1, -1}); + heights = calcSingleKerbHeightFromSidedTagList(explicitKerbTags, heights); + + if (heights[0] > -1) { + hasLeftSidewalk = true; + } + + if (heights[1] > -1) { + hasRightSidewalk = true; + } + } + + /** + * Calculate the kerb height from the way that should be stored on the graph bsaed on the tag keys specified + * + * @param kerbTags The tag keys that should be evaluated + * @param initialValue The initial value for the return. If no kerb height info is found, this value is returned + * @return The value to use as the kerb height derived from the specified tag keys. + */ + protected int calcSingleKerbHeightFromTagList(String[] kerbTags, int initialValue) { + int height = initialValue; + for (String kerbTag : kerbTags) { + int kerbHeightValue = convertKerbTagValueToCentimetres((String) cleanedTags.get(kerbTag)); + if (kerbHeightValue != -1) { + height = kerbHeightValue; + } + } + return height; + } + + /** + * Calculate the kerb heights from the way that should be stored on the graph bsaed on the tag keys specified. + * This method looks at the tags which specify a side to the road) + * + * @param kerbTags The tag keys that should be evaluated + * @param initialValues The initial value for the return. If no kerb height info is found, this value is returned + * @return The values to use as the kerb height derived from the specified tag keys. The first item + * in the array is for the left side, and the second is the right side. + */ + protected int[] calcSingleKerbHeightFromSidedTagList(String[] kerbTags, int[] initialValues) { + int[] heights = initialValues; + int height = -1; + for (String kerbTag : kerbTags) { + String[] tagValues = getSidedKerbTagValuesToApply(kerbTag); + if (tagValues[0] != null && !tagValues[0].isEmpty()) { + height = convertKerbTagValueToCentimetres(tagValues[0].toLowerCase()); + if (height > -1) { + heights[0] = height; + } + } + if (tagValues[1] != null && !tagValues[1].isEmpty()) { + height = convertKerbTagValueToCentimetres(tagValues[1].toLowerCase()); + if (height > -1) { + heights[1] = height; + } + } + } + + return heights; + } + + /** + * Go through tags and attempt to remove any invalid keys (i.e. when compound keys have been entered using a '.' rather than ':' + * + * @param dirtyTags The OSM tag collection that needs to be cleaned + * @return A cleaned version of the tags on the way (. replaced with : in tag names) + */ + protected HashMap cleanTags(Map dirtyTags) { + HashMap cleanedTagsMap = new HashMap<>(); + for (Map.Entry entry : dirtyTags.entrySet()) { + String cleanKey = entry.getKey().replace(".", ":"); + cleanedTagsMap.put(cleanKey, entry.getValue()); + } + return cleanedTagsMap; + } + + protected String[] getSidedTagValue(String property) { + String[] values = new String[2]; + // Left side + if (cleanedTags.containsKey("sidewalk:left:" + property)) + values[0] = (String) cleanedTags.get("sidewalk:left:" + property); + else if (cleanedTags.containsKey("footway:left:" + property)) + values[0] = (String) cleanedTags.get("footway:left:" + property); + // Right side + if (cleanedTags.containsKey("sidewalk:right:" + property)) + values[1] = (String) cleanedTags.get("sidewalk:right:" + property); + else if (cleanedTags.containsKey("footway:right:" + property)) + values[1] = (String) cleanedTags.get("footway:right:" + property); + + // Both + if (cleanedTags.containsKey(KEY_SIDEWALK_BOTH + property)) { + values[0] = (String) cleanedTags.get(KEY_SIDEWALK_BOTH + property); + values[1] = (String) cleanedTags.get(KEY_SIDEWALK_BOTH + property); + } else if (cleanedTags.containsKey(KEY_FOOTWAY_BOTH + property)) { + values[0] = (String) cleanedTags.get(KEY_FOOTWAY_BOTH + property); + values[1] = (String) cleanedTags.get(KEY_FOOTWAY_BOTH + property); + } + return values; + } + + /** Select the worst value (maximum) for the sidewalk side based on the presence of the ors-sidewalk-side tag and the presence of sidewalk tags on the sides of the way */ + protected int selectIntValueForSidewalkSide(ReaderWay way, int center, int left, int right) { + return selectIntValueForSidewalkSide(way, center, left, right, false); + } + + /** Select the worst value (maximum or minimum depending on the value of moreIsBetter) for the sidewalk side based on the presence of the ors-sidewalk-side tag and the presence of sidewalk tags on the sides of the way */ + protected int selectIntValueForSidewalkSide(ReaderWay way, int center, int left, int right, boolean moreIsBetter) { + IntBinaryOperator op = moreIsBetter ? Math::min : Math::max; + + IntBinaryOperator combine = (a, b) -> { + if (a == -1) return b; + if (b == -1) return a; + return op.applyAsInt(a, b); + }; + + if (way.hasTag(KEY_ORS_SIDEWALK_SIDE)) { + String side = way.getTag(KEY_ORS_SIDEWALK_SIDE); + if (side.equals(SW_VAL_LEFT)) { + return left > -1 ? left : center; + } else if (side.equals(SW_VAL_RIGHT)) { + return right > -1 ? right : center; + } + } else if (hasLeftSidewalk || hasRightSidewalk) { + return combine.applyAsInt(center, combine.applyAsInt(left, right)); + } + return center; + } + + /** + * Converts a kerb height value to a numerical height (in centimetres). A kerb could be stored as an explicit height or + * as an indicator as to whether the kerb is lowered or not. + * + * @param value The value of the tag + * @return The presumed height of the kerb in metres + */ + protected int convertKerbTagValueToCentimetres(String value) { + int centimetreHeight = -1; + + if (value == null) { + return -1; + } + switch (value) { + case "yes", KEY_BOTH, "low", "lowered", "dropped", "sloped" -> centimetreHeight = 3; + case "no", "none", "one", "rolled", "regular" -> centimetreHeight = 15; + case "at_grade", "flush" -> centimetreHeight = 0; + default -> { + double metresHeight = UnitsConverter.convertOSMDistanceTagToMeters(value); + // If no unit was given in the tag, the value might be in meters or centimeters; we can only guess + // depending on the value + if (metresHeight < 0.15) { + centimetreHeight = (int) (metresHeight * 100); + } else { + centimetreHeight = (int) metresHeight; + } + } + } + + return centimetreHeight; + } + + /** + * Look at way and try to find the correct kerb heights for it. In some cases when the kerbs are attached directly to a way they are + * marked as start and end and so we need to look through the various tags to try and find these. + * + * @param key The base key that we are investigating (e.g. "kerb", "sloped_kerb" etc.) + * @return The textual tag that should be used as the kerb height + */ + protected String[] getSidedKerbTagValuesToApply(String key) { + // If we are looking at the kerbs, sometimes the start and end of a way is marked as having different kerb + // heights using the ...:start and ...:end tags. For now, we just want to get the worse of these values (the + // highest) + double leftStart = -1; + double leftEnd = -1; + double rightStart = -1; + double rightEnd = -1; + + String[] endValues = getSidedTagValue(key + ":end"); + // Convert + if (endValues[0] != null && !endValues[0].isEmpty()) { + leftEnd = convertKerbTagValueToCentimetres(endValues[0]); + } + if (endValues[1] != null && !endValues[1].isEmpty()) { + rightEnd = convertKerbTagValueToCentimetres(endValues[1]); + } + String[] startValues = getSidedTagValue(key + ":start"); + // Convert + if (startValues[0] != null && !startValues[0].isEmpty()) { + leftStart = convertKerbTagValueToCentimetres(startValues[0]); + } + if (startValues[1] != null && !startValues[1].isEmpty()) { + rightStart = convertKerbTagValueToCentimetres(startValues[1]); + } + + // Now compare to find the worst + String[] values = new String[2]; + if (leftEnd > leftStart) + values[0] = endValues[0]; + else if (leftStart > leftEnd) + values[0] = startValues[0]; + + if (rightEnd > rightStart) + values[1] = endValues[1]; + else if (rightStart > rightEnd) + values[1] = startValues[1]; + + return values; + } + + protected boolean selectBooleanValueForSidewalkSide(ReaderWay way, boolean center, boolean left, boolean right) { + if (way.hasTag(KEY_ORS_SIDEWALK_SIDE)) { + String side = way.getTag(KEY_ORS_SIDEWALK_SIDE); + if (side.equals(SW_VAL_LEFT)) { + return center || left; + } else if (side.equals(SW_VAL_RIGHT)) { + return center || right; + } + } else if (hasLeftSidewalk || hasRightSidewalk) { + return center && left && right; + } + return center; + } + + + protected IntsRef defaultHandleWayTags(IntsRef edgeFlags, ReaderWay way, String tagName, ToIntFunction tagValueToEncodedValueFunction){ + return defaultHandleWayTags(edgeFlags, way, tagName, tagValueToEncodedValueFunction, false); + } + + protected IntsRef defaultHandleWayTags(IntsRef edgeFlags, ReaderWay way, String tagName, ToIntFunction tagValueToEncodedValueFunction, boolean moreIsBetter) { + beforeHandleWayTags(way); + + int center = -1; + int left = -1; + int right = -1; + + // Read center + if (cleanedTags.containsKey(tagName)) { + center = tagValueToEncodedValueFunction.applyAsInt(((String) cleanedTags.get(tagName)).toLowerCase()); + } + + // Read sides + String[] tagValues = getSidedTagValue(tagName); + if (tagValues[0] != null && !tagValues[0].isEmpty()) + left = tagValueToEncodedValueFunction.applyAsInt(tagValues[0].toLowerCase()); + if (tagValues[1] != null && !tagValues[1].isEmpty()) + right = tagValueToEncodedValueFunction.applyAsInt(tagValues[1].toLowerCase()); + + // Select based on artificial ors-sidewalk-side tag + center = selectIntValueForSidewalkSide(way, center, left, right, moreIsBetter); + + setInt(edgeFlags, center); + + return edgeFlags; + } + + /** + * Determine if the way is a separate footway object or a road feature. + * + * @param way The OSM way object to be assessed + * @return Whether the way is seen as a separately drawn footway (true) or a road (false) + */ + protected boolean isSeparateFootway(ReaderWay way) { + String type = way.getTag("highway", ""); + + String[] pedestrianWayTypes = { + "living_street", + "pedestrian", + "footway", + "path", + "crossing", + "track" + }; + + // Check if it is a footpath or pedestrian + if (!type.isEmpty()) { + // We are looking at a separate footpath + // we are looking at a road feature so any footway would be attached to it as a tag + return Arrays.asList(pedestrianWayTypes).contains(type); + } + + return true; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairInclineParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairInclineParser.java new file mode 100644 index 0000000000..633cbf524d --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairInclineParser.java @@ -0,0 +1,25 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairIncline; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.util.UnitsConverter; + +public class WheelchairInclineParser extends WheelchairBaseParser { + public static final String TAG_NAME = "incline"; + + public WheelchairInclineParser(){ + this.encoder = WheelchairIncline.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + return defaultHandleWayTags(edgeFlags, way, TAG_NAME, this::getInclineFromTagValue, false); + } + + private int getInclineFromTagValue(String inclineValue) { + double decimalIncline = UnitsConverter.convertOSMInclineValueToPercentage(inclineValue, true); + decimalIncline = Math.min(decimalIncline, 15.0); + return (int) Math.round(decimalIncline); + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairKerbHeightParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairKerbHeightParser.java new file mode 100644 index 0000000000..b68465381f --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairKerbHeightParser.java @@ -0,0 +1,119 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.coll.GHLongObjectHashMap; +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairKerb; +import com.graphhopper.storage.IntsRef; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WheelchairKerbHeightParser extends WheelchairBaseParser { + public static final String TAG_NAME = "kerb_height"; + public static boolean kerbHeightOnlyOnCrossing = true; // TODO: expose this as a configuration option + + + public WheelchairKerbHeightParser() { + this.encoder = WheelchairKerb.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + beforeHandleWayTags(way); + + GHLongObjectHashMap> nodeTags = way.getTag("ors:node_tags", new GHLongObjectHashMap<>()); + + int heightC = -1; + int heightL = -1; + int heightR = -1; + + int height = calcSingleKerbHeightFromTagList(assumedKerbTags, -1); + height = calcSingleKerbHeightFromTagList(explicitKerbTags, height); + + if (height > -1) { + heightC = height; + } + + // Now for if the values are attached to sides of the way + int[] heights = calcSingleKerbHeightFromSidedTagList(assumedKerbTags, new int[]{-1, -1}); + heights = calcSingleKerbHeightFromSidedTagList(explicitKerbTags, heights); + + if (heights[0] > -1) { + heightL = heights[0]; + } + + if (heights[1] > -1) { + heightR = heights[1]; + } + + + int kerbHeight = getKerbHeightForEdge(way, nodeTags); + if (kerbHeight > -1) { + heightC = kerbHeight; + } + + int finalHeight = selectIntValueForSidewalkSide(way, heightC, heightL, heightR); + + setInt(edgeFlags, finalHeight); + + return edgeFlags; + } + + + /** + * Get an overriding kerb height if needed from the nodes that are on the way rather than the data stored on the way itself. + * This should be the case if we are specifying to only store kerb heights on crossings as these features do not normally + * have kerb heights attached to them + * + * @param way The way that is being investigated + * @return A kerb height from the tags of the nodes on the way, or -1 if no kerb heights are found/required + */ + int getKerbHeightForEdge(ReaderWay way, GHLongObjectHashMap> nodeTags) { + int kerbHeight = -1; + + if (!kerbHeightOnlyOnCrossing || (way.hasTag("footway") && way.getTag("footway").equals("crossing"))) { + // Look for kerb information + kerbHeight = getKerbHeightFromNodeTags(nodeTags); + } + + return kerbHeight; + } + + /** + * Look at the information stored against the nodes of the way and extract the kerb height to use for the whole way + * from those data. + * + * @return The derived kerb height in centimetres from teh nodes that are on the way + */ + int getKerbHeightFromNodeTags(GHLongObjectHashMap> nodeTags) { + // Assumed kerb heights are those obtained from a tag without the explicit :height attribute + List assumedKerbHeights = new ArrayList<>(); + // Explicit heights are those provided by the :height tag - these should take precidence + List explicitKerbHeights = new ArrayList<>(); + + for (var entry : nodeTags.values()) { + Map tags = entry.value; + for (Map.Entry tag : tags.entrySet()) { + switch (tag.getKey()) { + case "sloped_curb", "curb", "kerb", "sloped_kerb" -> + assumedKerbHeights.add(convertKerbTagValueToCentimetres(tag.getValue())); + case "kerb:height" -> explicitKerbHeights.add(convertKerbTagValueToCentimetres(tag.getValue())); + default -> { + } + } + } + } + if (!explicitKerbHeights.isEmpty()) { + return Collections.max(explicitKerbHeights); + } else if (!assumedKerbHeights.isEmpty()) { + // If we have multiple kerb heights, we need to apply the largest to the edge as this is the worst + return Collections.max(assumedKerbHeights); + } else { + return -1; + } + } +} + + diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSideParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSideParser.java new file mode 100644 index 0000000000..1aea58930b --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSideParser.java @@ -0,0 +1,32 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.EnumEncodedValue; +import com.graphhopper.routing.ev.WheelchairSide; +import com.graphhopper.storage.IntsRef; + +import static org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes.*; +import static org.heigit.ors.routing.graphhopper.extensions.reader.osmfeatureprocessors.OSMAttachedSidewalkProcessor.KEY_ORS_SIDEWALK_SIDE; + +public class WheelchairSideParser extends WheelchairBaseParser { + public WheelchairSideParser(){ + this.encoder = WheelchairSide.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + Side sideProp = Side.UNKNOWN; + + if (way.hasTag(KEY_ORS_SIDEWALK_SIDE)) { + String side = way.getTag(KEY_ORS_SIDEWALK_SIDE); + if (side.equals(SW_VAL_LEFT)) { + sideProp = Side.LEFT; + } else if (side.equals(SW_VAL_RIGHT)) { + sideProp = Side.RIGHT; + } + } + + ((EnumEncodedValue) encoder).setEnum(false, edgeFlags, sideProp); + return edgeFlags; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSmoothnessParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSmoothnessParser.java new file mode 100644 index 0000000000..a000515fbc --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSmoothnessParser.java @@ -0,0 +1,19 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairSmoothness; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; + +public class WheelchairSmoothnessParser extends WheelchairBaseParser { + public static final String TAG_NAME = "smoothness"; + + public WheelchairSmoothnessParser(){ + this.encoder = WheelchairSmoothness.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + return defaultHandleWayTags(edgeFlags, way, TAG_NAME, WheelchairTypesEncoder::getSmoothnessType); + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSuitableParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSuitableParser.java new file mode 100644 index 0000000000..02f3bd184f --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSuitableParser.java @@ -0,0 +1,26 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.WheelchairSuitable; +import com.graphhopper.storage.IntsRef; + +public class WheelchairSuitableParser extends WheelchairBaseParser { + public WheelchairSuitableParser() { + this.encoder = WheelchairSuitable.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + boolean suited = isSeparateFootway(way) || way.hasTag("wheelchair_accessible", true); + + // the sidewalks always imply known suitability + boolean suitedLeft = true; + boolean suitedRight = true; + + suited = selectBooleanValueForSidewalkSide(way, suited, suitedLeft, suitedRight); + + ((BooleanEncodedValue) encoder).setBool(false, edgeFlags, suited); + return edgeFlags; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceParser.java new file mode 100644 index 0000000000..2e7de094cb --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceParser.java @@ -0,0 +1,19 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairSurface; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; + +public class WheelchairSurfaceParser extends WheelchairBaseParser { + public static final String TAG_NAME = "surface"; + + public WheelchairSurfaceParser(){ + this.encoder = WheelchairSurface.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + return defaultHandleWayTags(edgeFlags, way, TAG_NAME, WheelchairTypesEncoder::getSurfaceType); + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceQualityKnownParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceQualityKnownParser.java new file mode 100644 index 0000000000..12d00382d0 --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceQualityKnownParser.java @@ -0,0 +1,46 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; +import com.graphhopper.routing.ev.WheelchairSurfaceQualityKnown; +import com.graphhopper.storage.IntsRef; + +import java.util.Arrays; +import java.util.List; + +public class WheelchairSurfaceQualityKnownParser extends WheelchairBaseParser { + public WheelchairSurfaceQualityKnownParser(){ + this.encoder = WheelchairSurfaceQualityKnown.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + beforeHandleWayTags(way); + + boolean markSurfaceQualityKnown = isSeparateFootway(way); + final List keys = Arrays.asList("surface", "smoothness", "tracktype"); + + boolean center = false; + boolean left = false; + boolean right = false; + + for(String key : keys) { + // Read center + if (cleanedTags.containsKey(key)) { + center = markSurfaceQualityKnown; + } + + // Read sides + String[] tagValues = getSidedTagValue(key); + if (tagValues[0] != null && !tagValues[0].isEmpty()) + left = true; + if (tagValues[1] != null && !tagValues[1].isEmpty()) + right = true; + } + + center = selectBooleanValueForSidewalkSide(way, center, left, right); + + ((SimpleBooleanEncodedValue) encoder).setBool(false, edgeFlags, center); + return edgeFlags; + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairTrackTypeParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairTrackTypeParser.java new file mode 100644 index 0000000000..c25d8ae664 --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairTrackTypeParser.java @@ -0,0 +1,19 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairTrackType; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.routing.graphhopper.extensions.WheelchairTypesEncoder; + +public class WheelchairTrackTypeParser extends WheelchairBaseParser { + public static final String TAG_NAME = "tracktype"; + + public WheelchairTrackTypeParser(){ + this.encoder = WheelchairTrackType.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + return defaultHandleWayTags(edgeFlags, way, TAG_NAME, WheelchairTypesEncoder::getTrackType); + } +} diff --git a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairWidthParser.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairWidthParser.java new file mode 100644 index 0000000000..c98ed6648e --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairWidthParser.java @@ -0,0 +1,29 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.WheelchairWidth; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.util.UnitsConverter; + +public class WheelchairWidthParser extends WheelchairBaseParser { + public static final String TAG_NAME = "width"; + + public WheelchairWidthParser(){ + this.encoder = WheelchairWidth.create(); + } + + @Override + public IntsRef handleWayTags(IntsRef edgeFlags, ReaderWay way, boolean ferry, IntsRef relationFlags) { + return defaultHandleWayTags( + edgeFlags, + way, + TAG_NAME, + this::widthFromTag, + true // a wider way is better for wheelchair users, therefore the worst value is the smallest one + ); + } + + private int widthFromTag(String value) { + return (int) (UnitsConverter.convertOSMDistanceTagToMeters(value.toLowerCase()) * 100); + } +} diff --git a/ors-engine/src/main/resources/META-INF/services/org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder b/ors-engine/src/main/resources/META-INF/services/org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder index 3abd707211..59ce0954dc 100644 --- a/ors-engine/src/main/resources/META-INF/services/org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder +++ b/ors-engine/src/main/resources/META-INF/services/org.heigit.ors.routing.graphhopper.extensions.storages.builders.GraphStorageBuilder @@ -1,5 +1,4 @@ org.heigit.ors.routing.graphhopper.extensions.storages.builders.HereTrafficGraphStorageBuilder -org.heigit.ors.routing.graphhopper.extensions.storages.builders.WheelchairGraphStorageBuilder org.heigit.ors.routing.graphhopper.extensions.storages.builders.GreenIndexGraphStorageBuilder org.heigit.ors.routing.graphhopper.extensions.storages.builders.NoiseIndexGraphStorageBuilder org.heigit.ors.routing.graphhopper.extensions.storages.builders.CsvGraphStorageBuilder diff --git a/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorageTest.java b/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorageTest.java deleted file mode 100644 index 3854c75e99..0000000000 --- a/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorageTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.heigit.ors.routing.graphhopper.extensions.storages; - -import com.graphhopper.storage.RAMDirectory; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class WheelchairAttributesGraphStorageTest { - private WheelchairAttributesGraphStorage storage; - - - public WheelchairAttributesGraphStorageTest() { - } - - @Test - void WheelchairEdgeCreationTest() { - storage = new WheelchairAttributesGraphStorage(); - storage.init(null, new RAMDirectory("")); - storage.create(1); - - WheelchairAttributes attrs = new WheelchairAttributes(); - attrs.setAttribute(WheelchairAttributes.Attribute.WIDTH, 180, false); - attrs.setAttribute(WheelchairAttributes.Attribute.KERB, 3, false); - attrs.setAttribute(WheelchairAttributes.Attribute.INCLINE, 0, false); - attrs.setAttribute(WheelchairAttributes.Attribute.SURFACE, 1, true); - attrs.setAttribute(WheelchairAttributes.Attribute.SMOOTHNESS, 1, true); - attrs.setSuitable(true); - - - storage.setEdgeValues(1, attrs); - - WheelchairAttributes attrsRet = new WheelchairAttributes(); - - storage.getEdgeValues(1, attrsRet, new byte[WheelchairAttributesGraphStorage.BYTE_COUNT]); - - assertEquals(180, attrsRet.getWidth(), 0.0); - assertEquals(3, attrsRet.getSlopedKerbHeight(), 0.0); - assertTrue(attrsRet.isSurfaceQualityKnown()); - assertTrue(attrsRet.isSuitable()); - - } - -} diff --git a/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilderTest.java b/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilderTest.java deleted file mode 100644 index c2bb981ca2..0000000000 --- a/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilderTest.java +++ /dev/null @@ -1,215 +0,0 @@ -package org.heigit.ors.routing.graphhopper.extensions.storages.builders; - -import com.graphhopper.reader.ReaderWay; -import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.locationtech.jts.geom.Coordinate; - -import java.util.HashMap; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.*; - -class WheelchairGraphStorageBuilderTest { - private WheelchairGraphStorageBuilder builder; - - public WheelchairGraphStorageBuilderTest() { - builder = new WheelchairGraphStorageBuilder(); - } - - @BeforeEach - void reset() { - builder = new WheelchairGraphStorageBuilder(); - } - - @Test - void TestProcessSeparateWay() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "residential"); - way.setTag("width", "0.5"); - way.setTag("incline", "2"); - way.setTag("kerb:height", "0.03"); - way.setTag("smoothness", "good"); - way.setTag("surface", "asphalt"); - - - builder.processWay(way); - - WheelchairAttributes attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.NONE); - assertEquals(50, attrs.getWidth()); - assertEquals(2, attrs.getIncline()); - assertEquals(3, attrs.getSlopedKerbHeight()); - assertEquals(2, attrs.getSmoothnessType()); - assertEquals(2, attrs.getSurfaceType()); - assertFalse(attrs.isSurfaceQualityKnown()); - assertFalse(attrs.isSuitable()); - } - - - @Test - void TestPedestrianisedWay() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "track"); - way.setTag("width", "0.5"); - way.setTag("incline", "2"); - way.setTag("tracktype", "grade1"); - way.setTag("surface", "asphalt"); - - - builder.processWay(way); - - WheelchairAttributes attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.NONE); - assertEquals(50, attrs.getWidth()); - assertEquals(2, attrs.getIncline()); - assertEquals(2, attrs.getSurfaceType()); - assertEquals(1, attrs.getTrackType()); - assertTrue(attrs.isSurfaceQualityKnown()); - assertTrue(attrs.isSuitable()); - } - - @Test - void TestProcessWayWithLeftSidewalkAttached() { - ReaderWay way = constructSidedWay("left"); - builder.processWay(way); - WheelchairAttributes attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.LEFT); - assertAttributeValues(attrs); - } - - @Test - void TestProcessWayWithRightSidewalkAttached() { - ReaderWay way = constructSidedWay("right"); - builder.processWay(way); - WheelchairAttributes attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.RIGHT); - assertAttributeValues(attrs); - } - - @Test - void TestProcessWayWithBothSidewalksAttached() { - ReaderWay way = constructSidedWay("both"); - builder.processWay(way); - WheelchairAttributes attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.RIGHT); - assertAttributeValues(attrs); - attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.LEFT); - assertAttributeValues(attrs); - } - - @Test - void TestProcessWayWithMultipleValues() { - ReaderWay way = new ReaderWay(1); - - way.setTag("sidewalk:left:width", "0.6"); - way.setTag("sidewalk:right:width", "0.5"); - way.setTag("sidewalk:left:kerb:height:start", "0.01"); - way.setTag("sidewalk:right:kerb:height:start", "0.03"); - way.setTag("sidewalk:left:incline", "5"); - way.setTag("sidewalk:right:incline", "2"); - way.setTag("sidewalk:left:tracktype", "grade"); - way.setTag("sidewalk:right:tracktype", "grade2"); - way.setTag("sidewalk:left:smoothness", "intermediate"); - way.setTag("sidewalk:right:smoothness", "excellent"); - way.setTag("sidewalk:left:surface", "asphalt"); - way.setTag("sidewalk:right:surface", "paving_stones"); - - WheelchairAttributes correctWheelchairAttributes = new WheelchairAttributes(); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.KERB, 3, false); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.WIDTH, 50, false); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.INCLINE, 5, false); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.TRACK, 2, false); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.SMOOTHNESS, 3, false); - correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.SURFACE, 4, false); - assertFalse(correctWheelchairAttributes.isSurfaceQualityKnown()); - assertFalse(correctWheelchairAttributes.isSuitable()); - - builder.processWay(way); - WheelchairAttributes left_attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.LEFT); - assertTrue(left_attrs.isSurfaceQualityKnown()); - assertTrue(left_attrs.isSuitable()); - - WheelchairAttributes right_attrs = builder.getStoredAttributes(WheelchairGraphStorageBuilder.Side.RIGHT); - assertTrue(right_attrs.isSurfaceQualityKnown()); - assertTrue(right_attrs.isSuitable()); - - WheelchairAttributes attrs = builder.combineAttributesOfWayWhenBothSidesPresent(new WheelchairAttributes()); - assertFalse(attrs.isSurfaceQualityKnown()); - assertFalse(attrs.isSuitable()); - - assertEquals(wheelchairAttributesAsString(correctWheelchairAttributes), wheelchairAttributesAsString(attrs)); - } - - @Test - void TestKerbHeightFromNode() { - ReaderWay way = new ReaderWay(1); - - way.setTag("highway", "crossing"); - - Map> nodeTags = new HashMap<>(); - Map tags = new HashMap<>(); - tags.put("kerb:height", "0.03"); - nodeTags.put(1, tags); - - builder.processWay(way, new Coordinate[0], nodeTags); - - assertEquals(3, builder.getKerbHeightFromNodeTags()); - } - - @Test - void TestAttachKerbHeightToCrossing() { - builder = new WheelchairGraphStorageBuilder(true); - - ReaderWay way = new ReaderWay(1); - - way.setTag("footway", "crossing"); - - Map> nodeTags = new HashMap<>(); - Map tags = new HashMap<>(); - tags.put("kerb:height", "0.03"); - nodeTags.put(1, tags); - - builder.processWay(way, new Coordinate[0], nodeTags); - - assertEquals(3, builder.getKerbHeightForEdge(way)); - - way = new ReaderWay(2); - - way.setTag("highway", "footway"); - - builder.processWay(way, new Coordinate[0], nodeTags); - - assertEquals(-1, builder.getKerbHeightForEdge(way)); - } - - private ReaderWay constructSidedWay(String side) { - ReaderWay way = new ReaderWay(1); - way.setTag("sidewalk", side); - way.setTag("sidewalk:" + side + ":width", "0.5"); - way.setTag("sidewalk:" + side + ":incline", "2"); - way.setTag("sidewalk:" + side + ":kerb:height:start", "0.03"); - way.setTag("sidewalk:" + side + ":kerb:height:end", "0.01"); - way.setTag("sidewalk:" + side + ":smoothness", "good"); - way.setTag("sidewalk:" + side + ":surface", "asphalt"); - way.setTag("sidewalk:" + side + ":tracktype", "grade4"); - - return way; - } - - private void assertAttributeValues(WheelchairAttributes attrs) { - assertEquals(50, attrs.getWidth()); - assertEquals(2, attrs.getIncline()); - assertEquals(3, attrs.getSlopedKerbHeight()); - assertEquals(2, attrs.getSmoothnessType()); - assertEquals(2, attrs.getSurfaceType()); - assertEquals(4, attrs.getTrackType()); - assertTrue(attrs.isSuitable()); - assertTrue(attrs.isSurfaceQualityKnown()); - } - - private String wheelchairAttributesAsString(WheelchairAttributes attrs) { - return attrs.getIncline() + "," - + attrs.getSlopedKerbHeight() + "," - + attrs.getSmoothnessType() + "," - + attrs.getSurfaceType() + "," - + attrs.getTrackType() + "," - + attrs.getWidth(); - } -} diff --git a/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/WheelchairParserTest.java b/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/WheelchairParserTest.java new file mode 100644 index 0000000000..d17ee3dab5 --- /dev/null +++ b/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/WheelchairParserTest.java @@ -0,0 +1,327 @@ +package org.heigit.ors.routing.graphhopper.extensions.util.parsers; + +import com.graphhopper.coll.GHLongObjectHashMap; +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.storage.IntsRef; +import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; +import org.heigit.ors.routing.graphhopper.extensions.util.WheelchairAttributesEncodedValues; +import org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +class WheelchairParserTest { + private EncodingManager em; + private IntsRef intsRef, relFlags; + + private WheelchairSurfaceParser parserSurface; + private WheelchairSmoothnessParser parserSmoothness; + private WheelchairTrackTypeParser parserTrackType; + private WheelchairInclineParser parserIncline; + private WheelchairKerbHeightParser parserKerbHeight; + private WheelchairWidthParser parserWidth; + private WheelchairSuitableParser parserSuitable; + private WheelchairSurfaceQualityKnownParser parserSurfaceQualityKnown; + private WheelchairSideParser parserSide; + + private final GHLongObjectHashMap> nodeTags = new GHLongObjectHashMap<>(); + + public WheelchairParserTest() { + setUp(); + } + + @BeforeEach + void setUp() { + parserSurface = new WheelchairSurfaceParser(); + parserSmoothness = new WheelchairSmoothnessParser(); + parserTrackType = new WheelchairTrackTypeParser(); + parserIncline = new WheelchairInclineParser(); + parserKerbHeight = new WheelchairKerbHeightParser(); + parserWidth = new WheelchairWidthParser(); + parserSuitable = new WheelchairSuitableParser(); + parserSurfaceQualityKnown = new WheelchairSurfaceQualityKnownParser(); + parserSide = new WheelchairSideParser(); + nodeTags.clear(); + + em = new EncodingManager.Builder() + .add(parserSurface) + .add(parserSmoothness) + .add(parserTrackType) + .add(parserIncline) + .add(parserKerbHeight) + .add(parserWidth) + .add(parserSuitable) + .add(parserSurfaceQualityKnown) + .add(parserSide).build(); + + relFlags = em.createRelationFlags(); + intsRef = em.createEdgeFlags(); + } + + void executeParsers(ReaderWay way) { + parserSurface.handleWayTags(intsRef, way, false, relFlags); + parserSmoothness.handleWayTags(intsRef, way, false, relFlags); + parserTrackType.handleWayTags(intsRef, way, false, relFlags); + parserIncline.handleWayTags(intsRef, way, false, relFlags); + parserKerbHeight.handleWayTags(intsRef, way, false, relFlags); + parserWidth.handleWayTags(intsRef, way, false, relFlags); + parserSuitable.handleWayTags(intsRef, way, false, relFlags); + parserSurfaceQualityKnown.handleWayTags(intsRef, way, false, relFlags); + parserSide.handleWayTags(intsRef, way, false, relFlags); + } + + void addNodeTag(ReaderWay way, String key, String value, int node) { + GHLongObjectHashMap> tags = way.getTag("ors:node_tags", new GHLongObjectHashMap<>()); + + if(!tags.containsKey(node)){ + tags.put(node, new HashMap<>()); + } + + tags.get(node).put(key, value); + way.setTag("ors:node_tags", tags); + } + + @Test + void TestProcessSeparateWay() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "residential"); + way.setTag("width", "0.5"); + way.setTag("incline", "2"); + way.setTag("kerb:height", "0.03"); + way.setTag("smoothness", "good"); + way.setTag("surface", "asphalt"); + + executeParsers(way); + + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(50, attrs.getWidth()); + assertEquals(2, attrs.getIncline()); + assertEquals(3, attrs.getSlopedKerbHeight()); + assertEquals(2, attrs.getSmoothnessType()); + assertEquals(2, attrs.getSurfaceType()); + assertFalse(attrs.isSurfaceQualityKnown()); + assertFalse(attrs.isSuitable()); + } + + + @Test + void TestPedestrianisedWay() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "track"); + way.setTag("width", "0.5"); + way.setTag("incline", "2"); + way.setTag("tracktype", "grade1"); + way.setTag("surface", "asphalt"); + + executeParsers(way); + + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(50, attrs.getWidth()); + assertEquals(2, attrs.getIncline()); + assertEquals(2, attrs.getSurfaceType()); + assertEquals(1, attrs.getTrackType()); + assertTrue(attrs.isSurfaceQualityKnown()); + assertTrue(attrs.isSuitable()); + } + + @Test + void TestProcessWayWithLeftSidewalkAttached() { + ReaderWay way = constructSidedWay("left"); + executeParsers(way); + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + assertAttributeValues(attrs); + } + + @Test + void TestProcessWayWithRightSidewalkAttached() { + ReaderWay way = constructSidedWay("right"); + executeParsers(way); + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + assertAttributeValues(attrs); + } + + @Test + void TestProcessWayWithBothSidewalksAttached() { + ReaderWay way = constructSidedWay("both"); + WheelchairAttributes attrs = parseForSide(way, "right"); + assertAttributeValues(attrs); + + attrs = parseForSide(way, "left"); + assertAttributeValues(attrs); + } + + @Test + void TestProcessWayWithMultipleValues() { + ReaderWay way = new ReaderWay(1); + + way.setTag("sidewalk:left:width", "0.6"); + way.setTag("sidewalk:right:width", "0.5"); + way.setTag("sidewalk:left:kerb:height:start", "0.01"); + way.setTag("sidewalk:right:kerb:height:start", "0.03"); + way.setTag("sidewalk:left:incline", "5"); + way.setTag("sidewalk:right:incline", "2"); + way.setTag("sidewalk:left:tracktype", "grade"); + way.setTag("sidewalk:right:tracktype", "grade2"); + way.setTag("sidewalk:left:smoothness", "intermediate"); + way.setTag("sidewalk:right:smoothness", "excellent"); + way.setTag("sidewalk:left:surface", "asphalt"); + way.setTag("sidewalk:right:surface", "paving_stones"); + + WheelchairAttributes correctWheelchairAttributes = new WheelchairAttributes(); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.KERB, 3, false); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.WIDTH, 50, false); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.INCLINE, 5, false); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.TRACK, 2, false); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.SMOOTHNESS, 3, false); + correctWheelchairAttributes.setAttribute(WheelchairAttributes.Attribute.SURFACE, 4, false); + assertFalse(correctWheelchairAttributes.isSurfaceQualityKnown()); + assertFalse(correctWheelchairAttributes.isSuitable()); + + WheelchairAttributes leftAttrs = parseForSide(way, "left"); + assertTrue(leftAttrs.isSurfaceQualityKnown()); + assertTrue(leftAttrs.isSuitable()); + assertEquals(60, leftAttrs.getWidth()); + assertEquals(5, leftAttrs.getIncline()); + assertEquals(1, leftAttrs.getSlopedKerbHeight()); + + WheelchairAttributes rightAttrs = parseForSide(way, "right"); + assertTrue(rightAttrs.isSurfaceQualityKnown()); + assertTrue(rightAttrs.isSuitable()); + assertEquals(50, rightAttrs.getWidth()); + assertEquals(2, rightAttrs.getIncline()); + assertEquals(3, rightAttrs.getSlopedKerbHeight()); + + WheelchairAttributes attrs = parseForSide(way, "both"); + assertFalse(attrs.isSurfaceQualityKnown()); + + assertEquals(wheelchairAttributesAsString(correctWheelchairAttributes), wheelchairAttributesAsString(attrs)); + } + + @Test + void TestKerbHeightFromNode() { + WheelchairKerbHeightParser.kerbHeightOnlyOnCrossing = false; + ReaderWay way = new ReaderWay(1); + + way.setTag("highway", "crossing"); + addNodeTag(way, "kerb:height", "0.03", 1); + + executeParsers(way); + + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(3, attrs.getSlopedKerbHeight()); + } + + @Test + void TestAttachKerbHeightToCrossing() { + WheelchairKerbHeightParser.kerbHeightOnlyOnCrossing = true; + + ReaderWay way = new ReaderWay(1); + + way.setTag("footway", "crossing"); + addNodeTag(way, "kerb:height", "0.03", 1); + + executeParsers(way); + + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(3, attrs.getSlopedKerbHeight()); + } + + @Test + void TestAttachKerbHeightOnlyToCrossing() { + WheelchairKerbHeightParser.kerbHeightOnlyOnCrossing = true; + + ReaderWay way = new ReaderWay(1); + + way.setTag("highway", "footway"); + addNodeTag(way, "kerb:height", "0.03", 1); + + executeParsers(way); + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(-1, attrs.getSlopedKerbHeight()); + } + + @Test + void TestUseWorstKerbHeightTag() { + /* This test is new and fails, because an already previously existing issue + We will tackle this after the refactoring. + + ReaderWay way = new ReaderWay(1); + + way.setTag("footway", "crossing"); + way.setTag("kerb:height", "0.03"); + addNodeTag(way, "curb", "0.2 m", 1); + + executeParsers(way); + + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + WheelchairAttributes attrs = encValues.getAttributes(intsRef); + + assertEquals(20, attrs.getSlopedKerbHeight()); */ + } + + + private WheelchairAttributes parseForSide(ReaderWay way, String side){ + assert(side.equals("left") || side.equals("right") || side.equals("both")); + if(side.equals("both")) { + way.removeTag("ors-sidewalk-side"); + } else { + way.setTag("ors-sidewalk-side", side); + } + + executeParsers(way); + WheelchairAttributesEncodedValues encValues = new WheelchairAttributesEncodedValues(em); + return encValues.getAttributes(intsRef); + } + + private ReaderWay constructSidedWay(String side) { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk", side); + way.setTag("sidewalk:" + side + ":width", "0.5"); + way.setTag("sidewalk:" + side + ":incline", "2"); + way.setTag("sidewalk:" + side + ":kerb:height:start", "0.03"); + way.setTag("sidewalk:" + side + ":kerb:height:end", "0.01"); + way.setTag("sidewalk:" + side + ":smoothness", "good"); + way.setTag("sidewalk:" + side + ":surface", "asphalt"); + way.setTag("sidewalk:" + side + ":tracktype", "grade4"); + way.setTag("ors-sidewalk-side", side); + + return way; + } + + private void assertAttributeValues(WheelchairAttributes attrs) { + assertEquals(50, attrs.getWidth()); + assertEquals(2, attrs.getIncline()); + assertEquals(3, attrs.getSlopedKerbHeight()); + assertEquals(2, attrs.getSmoothnessType()); + assertEquals(2, attrs.getSurfaceType()); + assertEquals(4, attrs.getTrackType()); + assertTrue(attrs.isSuitable()); + assertTrue(attrs.isSurfaceQualityKnown()); + } + + private String wheelchairAttributesAsString(WheelchairAttributes attrs) { + return attrs.getIncline() + "," + + attrs.getSlopedKerbHeight() + "," + + attrs.getSmoothnessType() + "," + + attrs.getSurfaceType() + "," + + attrs.getTrackType() + "," + + attrs.getWidth(); + } +}