From a16c76258c0146df108d9e0c09c7b477ffdac411 Mon Sep 17 00:00:00 2001 From: MarkRaadsen <52301193+MarkRaadsen@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:59:59 +1000 Subject: [PATCH] #56, implementation and constructing of OsmBoundary on networkData done (instead of using settings). However, this is whole support is also need for zoning reader. See comment on issue on how to avoid duplicate code and make this a more general and contained piece of code. NOT DONE. after refactoring, replace use of setting boundingboundary with the one on the data objects passes around. --- .../goplanit/osm/converter/OsmBoundary.java | 16 +- .../network/OsmNetworkBaseHandler.java | 16 +- .../OsmNetworkMainProcessingHandler.java | 19 +- .../OsmNetworkPreProcessingHandler.java | 182 +++++++++++++----- .../converter/network/OsmNetworkReader.java | 12 +- .../network/OsmNetworkReaderData.java | 113 ++++++++++- .../goplanit/osm/util/OsmRelationUtils.java | 16 ++ .../org/goplanit/osm/util/OsmWayUtils.java | 11 +- 8 files changed, 295 insertions(+), 90 deletions(-) diff --git a/src/main/java/org/goplanit/osm/converter/OsmBoundary.java b/src/main/java/org/goplanit/osm/converter/OsmBoundary.java index f0ee8412..864e7719 100644 --- a/src/main/java/org/goplanit/osm/converter/OsmBoundary.java +++ b/src/main/java/org/goplanit/osm/converter/OsmBoundary.java @@ -93,8 +93,20 @@ public static OsmBoundary of(String boundaryName, String boundaryType) { } /** - * Create a polygon based on name of administrative type for a given admin level (if provided) - * to restrict parsing to. + * Create full OsM boundary with all members populated + * + * @param boundaryName to restrict to + * @param boundaryType type of boundary (optional) + * @param adminLevel admin level of the boundary (optional) + * @param boundingPolygon to restrict to + * @return OsmBoundary to use in setting + */ + public static OsmBoundary of(String boundaryName, String boundaryType, String adminLevel, Polygon boundingPolygon) { + return new OsmBoundary(boundaryName, boundaryType, adminLevel, boundingPolygon); + } + + /** + * Create OSM boundary of administrative type * * @param boundaryName to restrict to * @param adminLevel admin level of the boundary (optional) diff --git a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkBaseHandler.java b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkBaseHandler.java index 957cc127..d8386152 100644 --- a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkBaseHandler.java +++ b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkBaseHandler.java @@ -45,22 +45,8 @@ protected OsmNetworkBaseHandler(final PlanitOsmNetwork networkToPopulate, final this.networkToPopulate = networkToPopulate; this.settings = settings; this.networkData = networkData; - initialiseUserDefinedBoundary(); } - /** - * If the user defined a bounding area in some way shape or form, we must construct a bounding polygon from - * this information to apply during parsing. This is what happens here - */ - protected void initialiseUserDefinedBoundary() { - if(getSettings().hasBoundingBoundary()){ - if(getSettings().getBoundingArea().hasBoundaryName()){ - - } - } - } - - /** verify if tags represent an highway or railway that is specifically aimed at road based or rail based infrastructure, e.g., * asphalt or tracks and NOT an area, platform, stops, etc. and is also activated for parsing based on the settings * @@ -87,7 +73,7 @@ protected boolean isActivatedRoadRailOrWaterwayBasedInfrastructure(Map> osmWayConsumer) { + protected void wrapHandleInfrastructureOsmWay(OsmWay osmWay, BiConsumer> osmWayConsumer) { if(!settings.isOsmWayExcluded(osmWay.getId())) { diff --git a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkMainProcessingHandler.java b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkMainProcessingHandler.java index 13c31a41..bda59e16 100644 --- a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkMainProcessingHandler.java +++ b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkMainProcessingHandler.java @@ -10,7 +10,6 @@ import org.goplanit.osm.physical.network.macroscopic.PlanitOsmNetwork; import org.goplanit.osm.tags.*; import org.goplanit.osm.util.*; -import org.goplanit.utils.exceptions.PlanItException; import org.goplanit.utils.exceptions.PlanItRunTimeException; import org.goplanit.utils.misc.Pair; import org.goplanit.utils.network.layer.NetworkLayer; @@ -53,20 +52,20 @@ private boolean hasNetworkLayersWithActiveOsmNode(long osmNodeId){ * @return true when eligible, false otherwise */ private boolean isNodeSpatiallyEligible(final OsmNode osmNode) { - var settings = getSettings(); - return getNetworkData().getOsmNodeData().containsPreregisteredOsmNode(osmNode.getId()) + var networkData = getNetworkData(); + return networkData.getOsmNodeData().containsPreregisteredOsmNode(osmNode.getId()) && - ( !settings.hasBoundingBoundary() || - !settings.getBoundingArea().hasBoundingPolygon() || - settings.isKeepOsmNodeOutsideBoundingPolygon(osmNode.getId())|| - OsmNodeUtils.createPoint(osmNode).within(settings.getBoundingArea().getBoundingPolygon()) + ( !networkData.hasBoundingBoundary() || + !networkData.getBoundingArea().hasBoundingPolygon() || + getSettings().isKeepOsmNodeOutsideBoundingPolygon(osmNode.getId())|| + OsmNodeUtils.createPoint(osmNode).within(networkData.getBoundingArea().getBoundingPolygon()) ); } /** - * now parse the remaining circular osmWays, which by default are converted into multiple links/linksegments for each part of - * the circular way in between connecting in and outgoing links/linksegments that were parsed during the regular parsing phase + * now parse the remaining circular osmWays, which by default are converted into multiple links/link segments for each part of + * the circular way in between connecting in and outgoing links/link segments that were parsed during the regular parsing phase * * @param circularOsmWay the circular osm way to parse */ @@ -453,7 +452,7 @@ public void handle(OsmNode osmNode) { @Override public void handle(OsmWay osmWay) throws IOException { - wrapHandleOsmWay(osmWay, this::handleOsmWay); + wrapHandleInfrastructureOsmWay(osmWay, this::handleOsmWay); } diff --git a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkPreProcessingHandler.java b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkPreProcessingHandler.java index eb671ba0..64b3588c 100644 --- a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkPreProcessingHandler.java +++ b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkPreProcessingHandler.java @@ -1,18 +1,29 @@ package org.goplanit.osm.converter.network; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.atomic.LongAdder; -import java.util.logging.Logger; - +import de.topobyte.osm4j.core.model.iface.EntityType; import de.topobyte.osm4j.core.model.iface.OsmNode; import de.topobyte.osm4j.core.model.iface.OsmRelation; import de.topobyte.osm4j.core.model.iface.OsmWay; import de.topobyte.osm4j.core.model.util.OsmModelUtil; +import org.goplanit.osm.converter.OsmBoundary; import org.goplanit.osm.physical.network.macroscopic.PlanitOsmNetwork; import org.goplanit.osm.tags.OsmBoundaryTags; +import org.goplanit.osm.tags.OsmMultiPolygonTags; import org.goplanit.osm.tags.OsmTags; +import org.goplanit.osm.util.OsmRelationUtils; import org.goplanit.osm.util.OsmTagUtils; +import org.goplanit.osm.util.OsmWayUtils; +import org.goplanit.utils.geo.PlanitJtsUtils; +import org.locationtech.jts.geom.Coordinate; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.LongAdder; +import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Preprocessing Handler that has two stages: @@ -53,6 +64,95 @@ public enum Stage { private final LongAdder nodeCounter; + /** + * Verify if relation defines a boundary area that was user configured to be the bounding area. If so + * register its members reflecting the outer boundary for processing in the regular pre-processing stage + * + * @param osmRelation to check + */ + private void identifyAndRegisterBoundingAreaRelationMembers(OsmRelation osmRelation) { + /* only keep going when boundary is active and based on name */ + if(!getSettings().hasBoundingBoundary() || !getSettings().getBoundingArea().hasBoundaryName()){ + return; + } + var boundarySettings = getSettings().getBoundingArea(); + + /* check for boundary tags on relation */ + var tags = OsmModelUtil.getTagsAsMap(osmRelation); + if(!tags.containsKey(OsmBoundaryTags.getBoundaryKeyTag())){ + return; + } + + if(!OsmTagUtils.matchesAnyValueTag( + tags.get(OsmBoundaryTags.getBoundaryKeyTag()), OsmBoundaryTags.getBoundaryValues())){ + return; + } + + /* boundary compatible relation - now check against settings */ + if(OsmTagUtils.keyMatchesAnyValueTag(tags, OsmTags.NAME, boundarySettings.getBoundaryName())){ + + // found, no see if more specific checks are required based on type and/or admin_level. Below flags switch to + // true if the item is not used or it is used AND it is matched + boolean boundaryTypeMatch = + boundarySettings.hasBoundaryType() && OsmBoundaryTags.hasBoundaryValueTag(tags, boundarySettings.getBoundaryType()); + boolean adminLevelMatch = boundarySettings.hasBoundaryAdminLevel() && + OsmTagUtils.keyMatchesAnyValueTag(tags, OsmBoundaryTags.ADMIN_LEVEL, boundarySettings.getBoundaryAdminLevel()); + boolean boundaryAdministrativeMatch = !boundarySettings.getBoundaryType().equals(OsmBoundaryTags.ADMINISTRATIVE) || + boundarySettings.hasBoundaryAdminLevel() && + OsmTagUtils.keyMatchesAnyValueTag(tags, OsmBoundaryTags.ADMIN_LEVEL, boundarySettings.getBoundaryAdminLevel()); + + if(boundaryTypeMatch && adminLevelMatch && boundaryAdministrativeMatch){ + + // full match found -> register all members with correct roles to extract bounding area polygon from in next stage + for(int memberIndex = 0; memberIndex < osmRelation.getNumberOfMembers(); ++memberIndex){ + var currMember = osmRelation.getMember(memberIndex); + if(OsmRelationUtils.isMemberOfTypeAndRole(currMember, EntityType.Way, OsmMultiPolygonTags.OUTER_ROLE)){ + getNetworkData().registerBoundaryOsmWayOuterRoleSection(currMember.getId()); + } + } + } + } + } + + /** + * Construct bounding boundary based on registered OSM ways. It is assumed these OSM ways are contiguous based on the + * numbering assigned to them. It is assumed this forms a complete polygon (will be checked) and it is assumed this + * adheres to the bounding boundary configuration (without a polygon) configured by the user in the network settings. + * The result is a new OsmBoundingBoundary that will be used in the actual processing + */ + private void completeConstructionBoundingBoundaryFromOsmWays() { + List boundaryOsmWays = getNetworkData().getRegisteredBoundaryOsmWaysInOrder(); + List contiguousBoundaryCoords = new ArrayList<>(); + for(var osmWay : boundaryOsmWays){ + var coordArray = OsmWayUtils.createCoordinateArrayNoThrow(osmWay,getNetworkData().getOsmNodeData().getRegisteredOsmNodes()); + if(!contiguousBoundaryCoords.isEmpty() && + !contiguousBoundaryCoords.get(contiguousBoundaryCoords.size()-1).equals2D(coordArray[0])){ + LOGGER.severe("Bounding boundary outer role OSM ways supposed to be contiguous, but this was not found to be the case, this shouldn't happen, ignore"); + return; + } + contiguousBoundaryCoords.addAll(Arrays.stream(coordArray).collect(Collectors.toList())); + } + + // now convert to polygon + var boundingBoundaryPolygon = PlanitJtsUtils.createPolygon( + contiguousBoundaryCoords.toArray(new Coordinate[0])); + if(boundingBoundaryPolygon == null){ + LOGGER.severe("Unable to construct bounding boundary polygon from OSM way coordinates listed under bounding boundary name"); + return; + } + + // construct final OsmBoundary copying original + adding in polygon + var originalBoundary = getSettings().getBoundingArea(); + getNetworkData().setBoundingAreaWithPolygon( + OsmBoundary.of( + originalBoundary.getBoundaryName(), + originalBoundary.getBoundaryType(), + originalBoundary.getBoundaryAdminLevel(), + boundingBoundaryPolygon)); + + //todo implement situation no name/registered ways exist and we should just copy the one from the user settings instead! + } + /** Mark all nodes of eligible OSM ways (e.g., road, rail, etc.) to be parsed during the main processing phase * * @param osmWay to handle @@ -78,7 +178,7 @@ protected void handleEligibleOsmWay(OsmWay osmWay, Map tags) { } } - /* mark all nodes as potentially eligible for keeping, since they reside on an OSM way that is deemed eligible (road, rail) */ + /* mark all nodes as potentially eligible for keeping, since they reside on an OSM way that is deemed eligible (road, rail, or boundary) */ for(int index=0;index register as the bounding area polygon to use - - //TODO below requires first implementing the two phases (enum created but not yet enforced) - // stage 1: do nothing by register the name and not create the polygon. In stage 2 extract the ways with outer roles - // and in complete() create the overall polygon from the line strings --> then put it on the network data there - // and it can be used in main processing (also to be implemented because it is not yet used - // then remove the initialise bit from the basehandler I think that is still there but is not needed. - // then feed this through all the way - //getNetworkData().setBoundingAreaWithPolygon(); - } - } + /* identify relations that might carry bounding area information */ + identifyAndRegisterBoundingAreaRelationMembers(osmRelation); } - + /** Log total number of parsed nodes and percentage retained */ @Override public void complete() throws IOException { super.complete(); + + // finalise bounding boundary construction + if(getNetworkData().hasRegisteredBoundaryOsmWay()){ + completeConstructionBoundingBoundaryFromOsmWays(); + } + + // regular pre-registration for networks int totalOsmNodes = (int) nodeCounter.sum(); int preRegisteredOsmNodes = getNetworkData().getOsmNodeData().getRegisteredOsmNodes().size(); LOGGER.info(String.format("Total OSM nodes in source: %d",totalOsmNodes)); LOGGER.info(String.format("Total OSM nodes identified as part of network: %d (%.2f%%)",preRegisteredOsmNodes, preRegisteredOsmNodes/(double)totalOsmNodes)); } - /** * reset the contents, mainly to free up unused resources */ diff --git a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReader.java b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReader.java index af37b00c..fd003e0c 100644 --- a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReader.java +++ b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReader.java @@ -46,7 +46,6 @@ public class OsmNetworkReader implements NetworkReader { /** * Call this BEFORE we parse the OSM network to initialise the handler(s) properly - * */ public void initialiseBeforeParsing() { PlanItRunTimeException.throwIf(getOsmNetworkToPopulate().getTransportLayers() != null && getOsmNetworkToPopulate().getTransportLayers().size()>0, @@ -78,8 +77,9 @@ public void initialiseBeforeParsing() { settings.excludeOsmWayTypesWithoutActivatedModes(); settings.logUnsupportedOsmWayTypes(); - /* initialise layer specific parsers */ + /* initialise layer specific parsers and bounding area*/ networkData.initialiseLayerParsers(getOsmNetworkToPopulate(), settings, geoUtils); + networkData.initialiseBoundingArea(settings); } /** Read based on reader and handler where the reader performs a callback to the handler provided @@ -116,9 +116,10 @@ private void doPreprocessing(){ OsmNetworkPreProcessingHandler osmHandler; - /* identify OSM relation by name if bounding area is specified by name rather than an explicit bounding box */ + /* STAGE 1 - BOUNDARY IDENTIFICATION + * identify OSM relation by name if bounding area is specified by name rather than an explicit bounding box */ if(settings.hasBoundingBoundary() && !settings.getBoundingArea().hasBoundingPolygon()) { - LOGGER.info("Pre-processing: Identifying relations representing public transport platforms"); + LOGGER.info(String.format("Pre-processing: Identifying bounding boundary for %s", settings.getBoundingArea().getBoundaryName())); /* reader to parse the actual file or source location */ OsmReader osmReader = Osm4JUtils.createOsm4jReader(settings.getInputSource()); @@ -128,9 +129,10 @@ private void doPreprocessing(){ } osmHandler = new OsmNetworkPreProcessingHandler( OsmNetworkPreProcessingHandler.Stage.IDENTIFY_BOUNDARY_BY_NAME, getOsmNetworkToPopulate(), networkData, settings); + read(osmReader, osmHandler); } - /* regular preprocessing */ + /* STAGE 2 - REGULAR PREPROCESSING */ LOGGER.info("Preprocessing: reducing memory footprint, identifying required OSM nodes"); /* reader to parse the actual file or source location */ diff --git a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReaderData.java b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReaderData.java index bc5b517d..ef625c0f 100644 --- a/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReaderData.java +++ b/src/main/java/org/goplanit/osm/converter/network/OsmNetworkReaderData.java @@ -2,6 +2,7 @@ import java.util.*; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.goplanit.network.layer.macroscopic.MacroscopicNetworkLayerImpl; import org.goplanit.osm.converter.OsmBoundary; @@ -10,6 +11,7 @@ import org.goplanit.osm.util.OsmNodeUtils; import org.goplanit.utils.exceptions.PlanItRunTimeException; import org.goplanit.utils.geo.PlanitJtsCrsUtils; +import org.goplanit.utils.misc.Pair; import org.goplanit.utils.network.layer.MacroscopicNetworkLayer; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; @@ -35,11 +37,18 @@ public class OsmNetworkReaderData { private Envelope networkBoundingBox; /** the osmBoundary derived from the user configuration, which in case the user configuration was based on a name only - * will have been expanded with the polygon identified during network pre-processing. Use this polygon for main processing, instead - * of the one from user settings. + * will have been expanded with the polygon identified during network pre-processing. This polygon will then be used to + * create this new bounding area that is used for main processing, instead of the one from user settings. If a polygon was + * directly set, we create a copy instead */ private OsmBoundary osmBoundingArea = null; + /** + * OSMWay ids that make up the outer polygon of the to be constructed osmBoundingArea (if any), contiguous nature of + * these can be reconstructed by ordering them by their integer value which is incrementally created upon adding entries. + */ + private Map> osmBoundaryOsmWayTracker = new HashMap<>(); + /** * Track OSM nodes to retain in memory during network parsing, which might or might not end up being used to construct links * or other entities @@ -72,7 +81,22 @@ protected void initialiseLayerParsers(PlanitOsmNetwork network, OsmNetworkReader OsmNetworkLayerParser layerHandler = new OsmNetworkLayerParser(macroNetworkLayer, this, settings, geoUtils); osmLayerParsers.put(macroNetworkLayer, layerHandler); } - } + } + + /** + * Initialise bounding area based on original bounding area set by user in settings. In case it has no polygon + * but just a name, then we do nothing here, but instead await the pre-processing which will update the bounding area + * at a later stage (with a polygon) + * + * @param settings to collect current bounding area information from + */ + protected void initialiseBoundingArea(OsmNetworkReaderSettings settings) { + boolean thisNoBoundaryNorPolygon = (this.osmBoundingArea==null || !getBoundingAreaWithPolygon().hasBoundingPolygon()); + boolean settingsHasBoundaryAndPolygon = settings.hasBoundingBoundary() && settings.getBoundingArea().hasBoundingPolygon(); + if(thisNoBoundaryNorPolygon && settingsHasBoundaryAndPolygon) { + setBoundingAreaWithPolygon(settings.getBoundingArea().deepClone()); + } + } /** * reset @@ -86,7 +110,8 @@ public void reset() { osmLayerParsers.clear(); } - /** update bounding box to include osm node + /** update bounding box to include OSM node + * * @param osmNode to expand so that bounding box includes it */ public void updateSpanningBoundingBox(OsmNode osmNode) { @@ -196,5 +221,83 @@ public final Map getLayerParsers return this.osmLayerParsers; } - + /** + * Register portions of the named OSM boundary to construct a single coherent polygon during regular pre-processing + * stage but before main processing (as main processing relies on the boundary to be finalised to do the actual parsing) + * + * @param osmWayId to register as part of the future OSM boundary + */ + public void registerBoundaryOsmWayOuterRoleSection(long osmWayId) { + osmBoundaryOsmWayTracker.put(osmWayId,Pair.of(osmBoundaryOsmWayTracker.size(), null /* to be added in next stage */)); + } + + /** + * Register actual OsmWay portion of the named OSM boundary to construct a single coherent polygon during regular pre-processing + * stage but before main processing (as main processing relies on the boundary to be finalised to do the actual parsing). + *

Requires the osmWayId to have been registered already in stage 1 of preprocessing

+ * + * @param osmWay to update registered entry with as part of the future OSM boundary + */ + public void updateBoundaryRegistrationWithOsmWay(OsmWay osmWay) { + if(!isRegisteredBoundaryOsmWay(osmWay.getId())){ + LOGGER.severe("Should not update boundary with OSM way when it has not been identified as being part of boundary during " + + "initial stage of preprocessing, ignored"); + } + var valueWithoutOsmWay = osmBoundaryOsmWayTracker.get(osmWay.getId()); + osmBoundaryOsmWayTracker.put(osmWay.getId(),Pair.of(valueWithoutOsmWay.first(), osmWay /* update now available */)); + } + + /** + * Extract an ordered list of the registered OSM ways for the bounding boundary in the order they were registered in + * + * @return ordered list of OSM ways + */ + public List getRegisteredBoundaryOsmWaysInOrder() { + if(!hasRegisteredBoundaryOsmWay()){ + LOGGER.severe("Unable to constructed sorted list of OSM ways as none have been registered"); + return List.of(); + } + if(osmBoundaryOsmWayTracker.values().iterator().next().second() == null){ + LOGGER.severe("Registered OSM ways do not have an OSM way object attached to them, unable to constructed sorted list of OSM ways"); + return List.of(); + } + // sort by integer index and then collect the OSM ways as list + return osmBoundaryOsmWayTracker.entrySet().stream().sorted( + Comparator.comparingInt( e -> e.getValue().first())).map( e -> e.getValue().second()).collect(Collectors.toList()); + } + + /** + * Verify if OSM way is part of bounding area boundary + * + * @param osmWayId to verify + */ + public boolean isRegisteredBoundaryOsmWay(long osmWayId) { + return osmBoundaryOsmWayTracker.containsKey(osmWayId); + } + + /** + * Check if any OSM ways have been registered for constructing the OSM bounding boundary polygon from + * + * @return true if present, false otherwise + */ + public boolean hasRegisteredBoundaryOsmWay() { + return !osmBoundaryOsmWayTracker.isEmpty(); + } + + /** check if bounding area is available + * + * @return true if present, false otherwise + */ + public boolean hasBoundingBoundary(){ + return osmBoundingArea != null; + } + + /** get the bounding area + * + * @return bounding area + */ + public OsmBoundary getBoundingArea(){ + return osmBoundingArea; + } + } diff --git a/src/main/java/org/goplanit/osm/util/OsmRelationUtils.java b/src/main/java/org/goplanit/osm/util/OsmRelationUtils.java index bd5f3c4b..d8a1c7da 100644 --- a/src/main/java/org/goplanit/osm/util/OsmRelationUtils.java +++ b/src/main/java/org/goplanit/osm/util/OsmRelationUtils.java @@ -3,6 +3,8 @@ import java.util.Map; import java.util.logging.Logger; +import com.sun.istack.NotNull; +import de.topobyte.osm4j.core.model.iface.EntityType; import de.topobyte.osm4j.core.model.iface.OsmRelation; import de.topobyte.osm4j.core.model.iface.OsmRelationMember; import org.goplanit.utils.functionalinterface.TriConsumer; @@ -54,6 +56,20 @@ public static void applyToOsmRelationMemberWithRole( } } } + + /** + * Check if a member conforms to provided type and role + * @param member to check + * @param typeToCheck type + * @param role role + * @return true if match, false otherwise + */ + public static boolean isMemberOfTypeAndRole(@NotNull OsmRelationMember member, @NotNull EntityType typeToCheck, @NotNull String role){ + assert member != null; + assert typeToCheck != null; + assert role != null; + return typeToCheck.equals(member.getType()) && role.equals(member.getRole()); + } diff --git a/src/main/java/org/goplanit/osm/util/OsmWayUtils.java b/src/main/java/org/goplanit/osm/util/OsmWayUtils.java index 6deb5532..9d4d323b 100644 --- a/src/main/java/org/goplanit/osm/util/OsmWayUtils.java +++ b/src/main/java/org/goplanit/osm/util/OsmWayUtils.java @@ -181,7 +181,12 @@ public static boolean isCircularWayDirectionClosed(Map tags, boo * @return coordinate array found, empty when no nodes were found available * @throws PlanItException thrown if error */ - public static Coordinate[] createCoordinateArray(OsmWay osmWay, Map osmNodes, int startNodeIndex, int endNodeIndex, PlanitExceptionConsumer> missingNodeConsumer) throws PlanItException{ + public static Coordinate[] createCoordinateArray( + OsmWay osmWay, + Map osmNodes, + int startNodeIndex, + int endNodeIndex, + PlanitExceptionConsumer> missingNodeConsumer) throws PlanItException{ Set missingNodes = null; /* in the special case the end node index is smaller than start node index (circular way) we "loop around" to accommodate this */ @@ -198,7 +203,7 @@ public static Coordinate[] createCoordinateArray(OsmWay osmWay, Map(); + missingNodes = new HashSet<>(); } missingNodes.add(osmWay.getNodeId(index)); continue; @@ -213,7 +218,7 @@ public static Coordinate[] createCoordinateArray(OsmWay osmWay, Map(); + missingNodes = new HashSet<>(); } missingNodes.add(osmWay.getNodeId(index)); continue;