diff --git a/src/main/java/org/goplanit/osm/converter/OsmBoundary.java b/src/main/java/org/goplanit/osm/converter/OsmBoundary.java index f0ee841..864e771 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 957cc12..d838615 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 13c31a4..bda59e1 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 eb671ba..64b3588 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 af37b00..fd003e0 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 bc5b517..ef625c0 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 bd5f3c4..d8a1c7d 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 6deb553..9d4d323 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;