From 5b822c699cdef3a71274933cb119b297e6f773d7 Mon Sep 17 00:00:00 2001 From: jarinox <45308098+jarinox@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:44:53 +0100 Subject: [PATCH 1/5] feat: store WheelchairAttributes in EncodedValues WheelchairAttributes are stored in EncodedValues instead of GraphStorage: - implement parsers for all attributes - read WheelchairValues from EncodedValues --- .../routing/ev/WheelchairIncline.java | 19 + .../routing/ev/WheelchairKerb.java | 19 + .../routing/ev/WheelchairSide.java | 16 + .../routing/ev/WheelchairSmoothness.java | 13 + .../routing/ev/WheelchairSuitable.java | 17 + .../routing/ev/WheelchairSurface.java | 13 + .../ev/WheelchairSurfaceQualityKnown.java | 13 + .../routing/ev/WheelchairTrackType.java | 13 + .../routing/ev/WheelchairWidth.java | 19 + .../ors/config/profile/BuildProperties.java | 36 ++ .../profile/EncodedValuesProperties.java | 38 ++ .../org/heigit/ors/export/ExportRequest.java | 16 +- .../graphhopper/extensions/ORSOSMReader.java | 31 +- .../extensions/OrsTagParserFactory.java | 10 + .../edgefilters/WheelchairEdgeFilter.java | 22 +- .../WheelchairGraphStorageBuilder.java | 1 + .../WheelchairAttributesEncodedValues.java | 83 ++++ .../wheelchair/WheelchairBaseParser.java | 369 ++++++++++++++++++ .../wheelchair/WheelchairInclineParser.java | 25 ++ .../WheelchairKerbHeightParser.java | 124 ++++++ .../wheelchair/WheelchairSideParser.java | 32 ++ .../WheelchairSmoothnessParser.java | 19 + .../wheelchair/WheelchairSuitableParser.java | 26 ++ .../wheelchair/WheelchairSurfaceParser.java | 19 + .../WheelchairSurfaceQualityKnownParser.java | 46 +++ .../wheelchair/WheelchairTrackTypeParser.java | 19 + .../wheelchair/WheelchairWidthParser.java | 29 ++ 27 files changed, 1055 insertions(+), 32 deletions(-) create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairIncline.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairKerb.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSide.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSmoothness.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSuitable.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurface.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurfaceQualityKnown.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairTrackType.java create mode 100644 ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairWidth.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/WheelchairAttributesEncodedValues.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairBaseParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairInclineParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairKerbHeightParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSideParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSmoothnessParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSuitableParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairSurfaceQualityKnownParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairTrackTypeParser.java create mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairWidthParser.java 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..cec5dfb769 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSide.java @@ -0,0 +1,16 @@ +package com.graphhopper.routing.ev; + +import org.heigit.ors.routing.graphhopper.extensions.WheelchairAttributes; + +// Side of the sidewalk, if attached. +public class WheelchairSide { + private WheelchairSide() { + // do not instantiate + } + + 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..104c87bff3 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSmoothness.java @@ -0,0 +1,13 @@ +package com.graphhopper.routing.ev; + +public class WheelchairSmoothness { + private WheelchairSmoothness() { + // do not instantiate + } + + 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..2071ff2d81 --- /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 { + private WheelchairSuitable() { + // do not instantiate + } + + public static final String KEY = "wheelchair_suitable"; + + 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..19c1bd5390 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairSurface.java @@ -0,0 +1,13 @@ +package com.graphhopper.routing.ev; + +public class WheelchairSurface { + private WheelchairSurface() { + // do not instantiate + } + + 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..1d87c4d920 --- /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 { + private WheelchairSurfaceQualityKnown() { + // do not instantiate + } + + public static final String KEY = "wheelchair_surface_quality_known"; + + 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..2bf7d0faa4 --- /dev/null +++ b/ors-engine/src/main/java/com/graphhopper/routing/ev/WheelchairTrackType.java @@ -0,0 +1,13 @@ +package com.graphhopper.routing.ev; + +public class WheelchairTrackType { + private WheelchairTrackType() { + // do not instantiate + } + + 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..781d611de8 --- /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 { + private WheelchairWidth() { + // do not instantiate + } + + public static final String KEY = "wheelchair_width"; + + 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..3b0b990d82 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 @@ -5,10 +5,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; +import org.heigit.ors.routing.graphhopper.extensions.util.parsers.wheelchair.WheelchairKerbHeightParser; import java.nio.file.Path; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Objects; import static java.util.Optional.ofNullable; @@ -95,6 +97,7 @@ public void initExtStorages() { if (extStorages == null) { return; } + for (Map.Entry entry : extStorages.entrySet()) { String key = entry.getKey(); ExtendedStorageProperties storage = entry.getValue(); @@ -111,6 +114,7 @@ public void initExtStorages() { case TOLLWAYS -> handleTollways(); case HILL_INDEX -> handleHillIndex(); case TRAIL_DIFFICULTY -> handleTrailDifficulty(); + case WHEELCHAIR -> handleWheelchair(storage); default -> { storage.initialize(extendedStorageName); this.extStorages.put(key, storage); @@ -226,6 +230,38 @@ private void handleTrailDifficulty() { } } + private void handleWheelchair(ExtendedStorageProperties storage) { + WheelchairKerbHeightParser.setKerbHeightOnlyOnCrossing(Objects.requireNonNullElse(storage.getKerbsOnCrossings(), true)); + + 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..9a27f93466 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 @@ -19,8 +19,7 @@ import org.heigit.ors.routing.RoutingProfileType; 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 +48,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 +88,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 +172,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 5e909f284c..64baf6a1c6 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 @@ -84,6 +84,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) { @@ -96,19 +109,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()) { @@ -287,6 +287,10 @@ private void onProcessWay(ReaderWay way) { tags.put(internalId, tagsForNode); } } + + if (!tags.isEmpty()) { + way.setTag("ors:node_tags", nodeTags); + } } if (processGeom || processSimpleGeom) { @@ -391,6 +395,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/edgefilters/WheelchairEdgeFilter.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/WheelchairEdgeFilter.java index 80845dbc70..915bca4690 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 @@ -13,37 +13,37 @@ */ package org.heigit.ors.routing.graphhopper.extensions.edgefilters; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EdgeFilter; +import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.storage.GraphHopperStorage; 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; +import java.util.List; + 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."); + public WheelchairEdgeFilter(WheelchairParameters params, GraphHopperStorage graphStorage) { + 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/storages/builders/WheelchairGraphStorageBuilder.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java index 00157e4b4d..5c38ecf67e 100644 --- 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 @@ -26,6 +26,7 @@ import java.util.*; +@Deprecated(since = "9.7.0") public class WheelchairGraphStorageBuilder extends AbstractGraphStorageBuilder { public static final String KEY_SLOPED_CURB = "sloped_curb"; public static final String KEY_SLOPED_KERB = "sloped_kerb"; 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..f109ce534f --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/WheelchairAttributesEncodedValues.java @@ -0,0 +1,83 @@ +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 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); + } + + 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..84440b5078 --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairBaseParser.java @@ -0,0 +1,369 @@ +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.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 of two integer values */ + protected int combineValues(int a, int b, boolean moreIsBetter) { + if(a == -1) return b; + if(b == -1) return a; + + return moreIsBetter ? Math.min(a, b) : Math.max(a, b); + } + + /** 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) { + 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 combineValues(center, combineValues(left, right, moreIsBetter), moreIsBetter); + } + 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..aab6ee9da7 --- /dev/null +++ b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/wheelchair/WheelchairKerbHeightParser.java @@ -0,0 +1,124 @@ +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 lombok.Getter; +import lombok.Setter; + +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"; + + @Getter + @Setter + private static boolean kerbHeightOnlyOnCrossing = true; + + 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 -> { + // tag irrelevant for kerb height + } + } + } + } + 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); + } +} From 5eb907b3b36e233fbcc2bd57e0e68316efdc1d44 Mon Sep 17 00:00:00 2001 From: jarinox <45308098+jarinox@users.noreply.github.com> Date: Tue, 13 Jan 2026 10:44:53 +0100 Subject: [PATCH 2/5] test: migrate WheelchairGraphStorageBuilderTest to WheelchairParserTest --- .../WheelchairGraphStorageBuilderTest.java | 215 ------------ .../util/parsers/WheelchairParserTest.java | 326 ++++++++++++++++++ 2 files changed, 326 insertions(+), 215 deletions(-) delete mode 100644 ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilderTest.java create mode 100644 ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/WheelchairParserTest.java 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..48c93cf731 --- /dev/null +++ b/ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/util/parsers/WheelchairParserTest.java @@ -0,0 +1,326 @@ +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.Disabled; +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.setKerbHeightOnlyOnCrossing(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.setKerbHeightOnlyOnCrossing(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.setKerbHeightOnlyOnCrossing(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()); + } + + @Disabled("This test is new and fails, because an already previously existing issue. We will tackle this after the refactoring.") + @Test + void TestUseWorstKerbHeightTag() { + 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(); + } +} From d1b4ca1a9c90fd13bb6a410f8f43465d1b838902 Mon Sep 17 00:00:00 2001 From: jarinox <45308098+jarinox@users.noreply.github.com> Date: Thu, 16 Apr 2026 12:23:11 +0200 Subject: [PATCH 3/5] test: use separate tests for duration and distance --- .../heigit/ors/apitests/routing/ResultTest.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 38f49fbf69..b32ebae1e9 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 @@ -2336,7 +2336,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(); @@ -2357,6 +2356,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); } From 48bbb0d6b526a6fe0dbd8b55c91e68733e61d511 Mon Sep 17 00:00:00 2001 From: jarinox <45308098+jarinox@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:11:06 +0200 Subject: [PATCH 4/5] chore: clean up (remove deprecated WheelchairGraphStorageBuilder) --- .../ors/config/profile/BuildProperties.java | 1 + .../core/CorePreparationHandler.java | 4 - .../edgefilters/WheelchairEdgeFilter.java | 4 - .../core/WheelchairCoreEdgeFilter.java | 42 - .../flagencoders/EncodedValueOld.java | 96 --- .../WheelchairAttributesGraphStorage.java | 304 ------- .../WheelchairGraphStorageBuilder.java | 799 ------------------ ...ions.storages.builders.GraphStorageBuilder | 1 - .../WheelchairAttributesGraphStorageTest.java | 45 - 9 files changed, 1 insertion(+), 1295 deletions(-) delete mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/edgefilters/core/WheelchairCoreEdgeFilter.java delete mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/flagencoders/EncodedValueOld.java delete mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorage.java delete mode 100644 ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java delete mode 100644 ors-engine/src/test/java/org/heigit/ors/routing/graphhopper/extensions/storages/WheelchairAttributesGraphStorageTest.java 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 3b0b990d82..a6d6caccf4 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 @@ -105,6 +105,7 @@ public void initExtStorages() { continue; } ExtendedStorageName extendedStorageName = ExtendedStorageName.getEnum(key); + switch (extendedStorageName) { case HEAVY_VEHICLE -> handleHeavyVehicle(storage); case OSM_ID -> handleOsmId(); 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 915bca4690..62c2917db1 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 @@ -13,9 +13,7 @@ */ package org.heigit.ors.routing.graphhopper.extensions.edgefilters; -import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.storage.GraphHopperStorage; import com.graphhopper.util.EdgeIteratorState; import org.apache.log4j.Logger; @@ -23,8 +21,6 @@ import org.heigit.ors.routing.graphhopper.extensions.util.WheelchairAttributesEncodedValues; import org.heigit.ors.routing.parameters.WheelchairParameters; -import java.util.List; - public class WheelchairEdgeFilter implements EdgeFilter { private static final Logger LOGGER = Logger.getLogger(WheelchairEdgeFilter.class.getName()); private final WheelchairAttributesEncodedValues encodedValues; 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/flagencoders/EncodedValueOld.java b/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/flagencoders/EncodedValueOld.java deleted file mode 100644 index 50343184e7..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/flagencoders/EncodedValueOld.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.heigit.ors.routing.graphhopper.extensions.flagencoders; - -/** - * @deprecated replace all occurences of EncodedValueOld by regular EncodedValues - */ -@Deprecated -public class EncodedValueOld { - protected final long shift; - protected final long mask; - protected final double factor; - protected final long defaultValue; - private final String name; - private final long maxValue; - private final boolean allowZero; - private final int bits; - - /** - * Define a bit-encoded value - *

- * - * @param name Description for debugging - * @param shift bit index of this value - * @param bits number of bits reserved - * @param factor scaling factor for stored values - * @param defaultValue default value - * @param maxValue default maximum value - */ - public EncodedValueOld(String name, int shift, int bits, double factor, long defaultValue, int maxValue) { - this(name, shift, bits, factor, defaultValue, maxValue, true); - } - - public EncodedValueOld(String name, int shift, int bits, double factor, long defaultValue, int maxValue, boolean allowZero) { - this.name = name; - this.shift = shift; - this.factor = factor; - this.defaultValue = defaultValue; - this.bits = bits; - long tmpMask = (1L << bits) - 1; - this.maxValue = Math.min(maxValue, Math.round(tmpMask * factor)); - if (maxValue > this.maxValue) - throw new IllegalStateException(name + " -> maxValue " + maxValue + " is too large for " + bits + " bits"); - - double factorDivision = maxValue / factor; - if (factorDivision != (int) factorDivision) { - throw new IllegalStateException("MaxValue needs to be divisible by factor without remainder"); - } - - mask = tmpMask << shift; - this.allowZero = allowZero; - } - - protected void checkValue(long value) { - if (value > maxValue) - throw new IllegalArgumentException(name + " value too large for encoding: " + value + ", maxValue:" + maxValue); - if (value < 0) - throw new IllegalArgumentException("negative " + name + " value not allowed! " + value); - if (!allowZero && value == 0) - throw new IllegalArgumentException("zero " + name + " value not allowed! " + value); - } - - public long setValue(long flags, long value) { - // scale value - value = Math.round(value / factor); - checkValue((long) (value * factor)); - value <<= shift; - - // clear value bits - flags &= ~mask; - - // set value - return flags | value; - } - - public String getName() { - return name; - } - - public long getValue(long flags) { - // find value - flags &= mask; - flags >>>= shift; - return Math.round(flags * factor); - } - - public int getBits() { - return bits; - } - - public double getFactor() { - return factor; - } - - public long setDefaultValue(long flags) { - return setValue(flags, defaultValue); - } -} 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 5c38ecf67e..0000000000 --- a/ors-engine/src/main/java/org/heigit/ors/routing/graphhopper/extensions/storages/builders/WheelchairGraphStorageBuilder.java +++ /dev/null @@ -1,799 +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.*; - -@Deprecated(since = "9.7.0") -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/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()); - - } - -} From 5389f0a2ab7315a08e43d7571deba4a12719ec9c Mon Sep 17 00:00:00 2001 From: jarinox <45308098+jarinox@users.noreply.github.com> Date: Tue, 16 Jun 2026 10:51:30 +0200 Subject: [PATCH 5/5] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d8929aec..a98b970bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Releasing is documented in RELEASE.md - migrate trail difficulty storage to `sac_scale`, `mtb_scale`, and `mtb_scale_uphill` encoded values ([#2277](https://github.com/GIScience/openrouteservice/pull/2277)) - migrate road access restrictions storage to a dedicated encoded value ([#2270](https://github.com/GIScience/openrouteservice/pull/2270)) - update references to deprecated URL https://api.openrouteservice.org, update default attribution strings ([#2279](https://github.com/GIScience/openrouteservice/pull/2279)) +- migrate wheelchair attributes storage to encoded values ([#2284](https://github.com/GIScience/openrouteservice/pull/2284)) ### Deprecated