From 3ec6f968acebbdeb882e64e075f5ea76730a1a0c Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sat, 8 Feb 2025 15:53:37 +0900 Subject: [PATCH 1/6] Display boundaries and borders --- .../data/type/MultiPolygonDataType.java | 6 ++++ basemap/layers/boundary/line.js | 19 ++++++++++++- basemap/layers/boundary/tileset.js | 28 ++++++++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java b/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java index b7a881852..eae348be9 100644 --- a/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java +++ b/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java @@ -20,6 +20,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import org.locationtech.jts.geom.*; +import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; /** * A data type for {@link GeometryCollection} objects. @@ -96,4 +97,9 @@ public MultiPolygon read(final ByteBuffer buffer, final int position) { } return geometryFactory.createMultiPolygon(polygons.toArray(Polygon[]::new)); } + + // Simplifies a MultiPolygon using the Douglas-Peucker algorithm with the given tolerance + public MultiPolygon simplify(MultiPolygon multiPolygon, double tolerance) { + return (MultiPolygon) DouglasPeuckerSimplifier.simplify(multiPolygon, tolerance); + } } diff --git a/basemap/layers/boundary/line.js b/basemap/layers/boundary/line.js index 4d2572c04..f2cb1fa8d 100644 --- a/basemap/layers/boundary/line.js +++ b/basemap/layers/boundary/line.js @@ -55,7 +55,24 @@ export default asLayerObject(withSortKeys(directives), { visibility: 'visible', }, paint: { - 'line-dasharray': [4, 1, 1, 1], + 'line-width': [ + 'interpolate', + ['exponential', 1], + ['zoom'], + 0, 0.5, + 10, [ + 'case', + ['==', ['get', 'maritime'], 'yes'], + 2, // If maritime is 'yes' + [ + 'case', + ['==', ['get', 'admin_level'], '4'], + 1, // If admin_level is '4' + 3 // For all other cases + ] + ], + 20, 5 + ] }, filter: ['==', ["geometry-type"], 'LineString'], }); diff --git a/basemap/layers/boundary/tileset.js b/basemap/layers/boundary/tileset.js index 66a990ca2..adf9aea9f 100644 --- a/basemap/layers/boundary/tileset.js +++ b/basemap/layers/boundary/tileset.js @@ -18,10 +18,36 @@ export default { id: 'boundary', queries: [ { - minzoom: 13, + minzoom: 7, maxzoom: 20, sql: "SELECT id, tags, geom FROM osm_boundary", }, + { + minzoom: 0, + maxzoom: 20, + sql: + "SELECT id, tags, geom " + + "FROM osm_boundary " + + "WHERE tags ->> 'admin_level' = '2' " + + "AND COALESCE(tags ->> 'maritime', '') != 'yes';", + }, + { + minzoom: 3.5, + maxzoom: 20, + sql: + "SELECT id, tags, geom " + + "FROM osm_boundary " + + "WHERE tags ->> 'admin_level' = '2' " + + "AND tags ->> 'maritime' = 'yes';", + }, + { + minzoom: 3.5, + maxzoom: 20, + sql: + "SELECT id, tags, geom " + + "FROM osm_boundary " + + "WHERE tags ->> 'admin_level' = '4';" + } ], } From b0a5e9125557af959ba23d8c3bfff71ad20442c9 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 16 Feb 2025 19:45:09 +0900 Subject: [PATCH 2/6] Add ST_SimplifyPreserveTopology in tileset.js --- basemap/layers/boundary/tileset.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/basemap/layers/boundary/tileset.js b/basemap/layers/boundary/tileset.js index adf9aea9f..d885e4486 100644 --- a/basemap/layers/boundary/tileset.js +++ b/basemap/layers/boundary/tileset.js @@ -21,13 +21,13 @@ export default { minzoom: 7, maxzoom: 20, sql: - "SELECT id, tags, geom FROM osm_boundary", + "SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom FROM osm_boundary", }, { minzoom: 0, maxzoom: 20, sql: - "SELECT id, tags, geom " + + "SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " + "FROM osm_boundary " + "WHERE tags ->> 'admin_level' = '2' " + "AND COALESCE(tags ->> 'maritime', '') != 'yes';", @@ -36,7 +36,7 @@ export default { minzoom: 3.5, maxzoom: 20, sql: - "SELECT id, tags, geom " + + "SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " + "FROM osm_boundary " + "WHERE tags ->> 'admin_level' = '2' " + "AND tags ->> 'maritime' = 'yes';", @@ -45,9 +45,9 @@ export default { minzoom: 3.5, maxzoom: 20, sql: - "SELECT id, tags, geom " + + "SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " + "FROM osm_boundary " + "WHERE tags ->> 'admin_level' = '4';" } ], -} +} \ No newline at end of file From d17166d12a2867fb778ef841955bddc2fdcdf3dc Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Mon, 17 Feb 2025 23:25:43 +0900 Subject: [PATCH 3/6] Create RelationBoundaryBuilder class --- .../data/type/MultiPolygonDataType.java | 6 -- .../function/RelationBoundaryBuilder.java | 77 +++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java diff --git a/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java b/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java index eae348be9..b7a881852 100644 --- a/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java +++ b/baremaps-data/src/main/java/org/apache/baremaps/data/type/MultiPolygonDataType.java @@ -20,7 +20,6 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import org.locationtech.jts.geom.*; -import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; /** * A data type for {@link GeometryCollection} objects. @@ -97,9 +96,4 @@ public MultiPolygon read(final ByteBuffer buffer, final int position) { } return geometryFactory.createMultiPolygon(polygons.toArray(Polygon[]::new)); } - - // Simplifies a MultiPolygon using the Douglas-Peucker algorithm with the given tolerance - public MultiPolygon simplify(MultiPolygon multiPolygon, double tolerance) { - return (MultiPolygon) DouglasPeuckerSimplifier.simplify(multiPolygon, tolerance); - } } diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java new file mode 100644 index 000000000..58d072d1a --- /dev/null +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java @@ -0,0 +1,77 @@ +package org.apache.baremaps.openstreetmap.function; + +import org.apache.baremaps.openstreetmap.model.Entity; +import org.apache.baremaps.openstreetmap.model.Node; +import org.apache.baremaps.openstreetmap.model.Relation; +import org.apache.baremaps.openstreetmap.model.Way; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.geom.util.GeometryCombiner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class RelationBoundaryBuilder implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(RelationBoundaryBuilder.class); + + private final Map nodes; + private final Map ways; + private final Map relations; + private final GeometryFactory geometryFactory; + + public RelationBoundaryBuilder( + Map nodes, + Map ways, + Map relations, + GeometryFactory geometryFactory + ) { + this.nodes = nodes; + this.ways = ways; + this.relations = relations; + this.geometryFactory = geometryFactory; + } + + @Override + public void accept(final Entity entity) { + if (entity instanceof Relation relation) { + try { + var start = System.currentTimeMillis(); + + buildBoundary(relation); + + var end = System.currentTimeMillis(); + var duration = end - start; + if (duration > 60 * 1000) { + logger.debug("Relation #{} processed in {} ms", relation.getId(), duration); + } + } catch (Exception e) { + logger.error("Error processing relation #" + relation.getId(), e); + } + } + } + + public Geometry buildBoundary(Relation relation) { + List geometries = relation.getMembers().stream() + .map(member -> switch (member.type()) { + case NODE -> nodes.get(member.ref()).getGeometry(); + case WAY -> ways.get(member.ref()).getGeometry(); + case RELATION -> buildBoundary(relations.get(member.ref())); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + GeometryCombiner combiner = new GeometryCombiner(geometries); + Geometry combinedGeometry = combiner.combine(); + + if (combinedGeometry instanceof MultiPolygon || combinedGeometry instanceof MultiLineString) { + return combinedGeometry; + } else { + return geometryFactory.createGeometryCollection(new Geometry[]{combinedGeometry}); + } + } +} \ No newline at end of file From fe62174150d837aabe1938e2b140848766c6e560 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Tue, 18 Feb 2025 23:28:16 +0900 Subject: [PATCH 4/6] Apply spotless --- .../function/RelationBoundaryBuilder.java | 127 ++++++++++-------- .../openstreetmap/model/Relation.java | 2 - 2 files changed, 71 insertions(+), 58 deletions(-) diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java index 58d072d1a..5197e4f39 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java @@ -1,5 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.baremaps.openstreetmap.function; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; import org.apache.baremaps.openstreetmap.model.Entity; import org.apache.baremaps.openstreetmap.model.Node; import org.apache.baremaps.openstreetmap.model.Relation; @@ -9,69 +31,62 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; -import java.util.stream.Collectors; - public class RelationBoundaryBuilder implements Consumer { - private static final Logger logger = LoggerFactory.getLogger(RelationBoundaryBuilder.class); + private static final Logger logger = LoggerFactory.getLogger(RelationBoundaryBuilder.class); - private final Map nodes; - private final Map ways; - private final Map relations; - private final GeometryFactory geometryFactory; + private final Map nodes; + private final Map ways; + private final Map relations; + private final GeometryFactory geometryFactory; - public RelationBoundaryBuilder( - Map nodes, - Map ways, - Map relations, - GeometryFactory geometryFactory - ) { - this.nodes = nodes; - this.ways = ways; - this.relations = relations; - this.geometryFactory = geometryFactory; - } + public RelationBoundaryBuilder( + Map nodes, + Map ways, + Map relations, + GeometryFactory geometryFactory) { + this.nodes = nodes; + this.ways = ways; + this.relations = relations; + this.geometryFactory = geometryFactory; + } - @Override - public void accept(final Entity entity) { - if (entity instanceof Relation relation) { - try { - var start = System.currentTimeMillis(); + @Override + public void accept(final Entity entity) { + if (entity instanceof Relation relation) { + try { + var start = System.currentTimeMillis(); - buildBoundary(relation); + buildBoundary(relation); - var end = System.currentTimeMillis(); - var duration = end - start; - if (duration > 60 * 1000) { - logger.debug("Relation #{} processed in {} ms", relation.getId(), duration); - } - } catch (Exception e) { - logger.error("Error processing relation #" + relation.getId(), e); - } - } - } + var end = System.currentTimeMillis(); + var duration = end - start; + if (duration > 60 * 1000) { + logger.debug("Relation #{} processed in {} ms", relation.getId(), duration); + } + } catch (Exception e) { + logger.error("Error processing relation #" + relation.getId(), e); + } + } + } - public Geometry buildBoundary(Relation relation) { - List geometries = relation.getMembers().stream() - .map(member -> switch (member.type()) { - case NODE -> nodes.get(member.ref()).getGeometry(); - case WAY -> ways.get(member.ref()).getGeometry(); - case RELATION -> buildBoundary(relations.get(member.ref())); - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + public Geometry buildBoundary(Relation relation) { + List geometries = relation.getMembers().stream() + .map(member -> switch (member.type()) { + case NODE -> nodes.get(member.ref()).getGeometry(); + case WAY -> ways.get(member.ref()).getGeometry(); + case RELATION -> buildBoundary(relations.get(member.ref())); + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); - GeometryCombiner combiner = new GeometryCombiner(geometries); - Geometry combinedGeometry = combiner.combine(); + GeometryCombiner combiner = new GeometryCombiner(geometries); + Geometry combinedGeometry = combiner.combine(); - if (combinedGeometry instanceof MultiPolygon || combinedGeometry instanceof MultiLineString) { - return combinedGeometry; - } else { - return geometryFactory.createGeometryCollection(new Geometry[]{combinedGeometry}); - } - } -} \ No newline at end of file + if (combinedGeometry instanceof MultiPolygon || combinedGeometry instanceof MultiLineString) { + return combinedGeometry; + } else { + return geometryFactory.createGeometryCollection(new Geometry[] {combinedGeometry}); + } + } +} diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java index 03496cc31..5b94914e8 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/model/Relation.java @@ -17,8 +17,6 @@ package org.apache.baremaps.openstreetmap.model; - - import java.util.List; import java.util.Map; import java.util.Objects; From d128e7b9b12b7697c7d39a746452d385cefbb5ec Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Tue, 18 Feb 2025 23:40:34 +0900 Subject: [PATCH 5/6] Optimize relation processing with early returns --- .../openstreetmap/function/RelationBoundaryBuilder.java | 3 +++ .../openstreetmap/function/RelationMultiPolygonBuilder.java | 3 +++ 2 files changed, 6 insertions(+) diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java index 5197e4f39..491bd11fe 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java @@ -54,6 +54,9 @@ public RelationBoundaryBuilder( @Override public void accept(final Entity entity) { if (entity instanceof Relation relation) { + if (!relation.getTags().containsKey("boundary")) { + return; + } try { var start = System.currentTimeMillis(); diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationMultiPolygonBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationMultiPolygonBuilder.java index e406d5924..c0f08d518 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationMultiPolygonBuilder.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationMultiPolygonBuilder.java @@ -62,6 +62,9 @@ public RelationMultiPolygonBuilder( @Override public void accept(Entity entity) { if (entity instanceof Relation relation) { + if (relation.getTags().containsKey("boundary")) { + return; + } try { var start = System.currentTimeMillis(); From aff9c198724dfbe16bea2cfe8bf3087113668218 Mon Sep 17 00:00:00 2001 From: yongjunhong Date: Sun, 23 Feb 2025 00:07:56 +0900 Subject: [PATCH 6/6] Improve boundary handling logic in `RelationBoundaryBuilder` --- .../baremaps/tasks/UpdateOsmDatabase.java | 10 +- .../function/EntityGeometryBuilder.java | 4 +- .../function/RelationBoundaryBuilder.java | 111 +++++++++++------- 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/baremaps-core/src/main/java/org/apache/baremaps/tasks/UpdateOsmDatabase.java b/baremaps-core/src/main/java/org/apache/baremaps/tasks/UpdateOsmDatabase.java index 51d125a02..5ab92f0b7 100644 --- a/baremaps-core/src/main/java/org/apache/baremaps/tasks/UpdateOsmDatabase.java +++ b/baremaps-core/src/main/java/org/apache/baremaps/tasks/UpdateOsmDatabase.java @@ -158,12 +158,20 @@ static void execute( new ChangeEntitiesHandler(buildRelationGeometry.andThen(reprojectRelationGeometry)); var importRelations = new ChangeElementsImporter<>(Relation.class, relationRepository); + var buildBoundaryGeometry = new RelationBoundaryBuilder(coordinateMap, referenceMap); + var reprojectBoundaryGeometry = new EntityProjectionTransformer(4326, databaseSrid); + var prepareBoundaryGeometry = + new ChangeEntitiesHandler(buildBoundaryGeometry.andThen(reprojectBoundaryGeometry)); + var importBoundaries = new ChangeElementsImporter<>(Relation.class, relationRepository); + var entityProcessor = prepareNodeGeometry .andThen(importNodes) .andThen(prepareWayGeometry) .andThen(importWays) .andThen(prepareRelationGeometry) - .andThen(importRelations); + .andThen(importRelations) + .andThen(prepareBoundaryGeometry) + .andThen(importBoundaries); try (var changeInputStream = new GZIPInputStream(new BufferedInputStream(changeUrl.openStream()))) { diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java index 7c1080d58..423dc5d5b 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/EntityGeometryBuilder.java @@ -34,6 +34,7 @@ public class EntityGeometryBuilder implements Consumer { private final Consumer nodeGeometryBuilder; private final Consumer wayGeometryBuilder; private final Consumer relationMultiPolygonBuilder; + private final Consumer relationBoundaryBuilder; /** * Constructs a consumer that uses the provided caches to create and set geometries. @@ -47,6 +48,7 @@ public EntityGeometryBuilder( this.nodeGeometryBuilder = new NodeGeometryBuilder(); this.wayGeometryBuilder = new WayGeometryBuilder(coordinateMap); this.relationMultiPolygonBuilder = new RelationMultiPolygonBuilder(coordinateMap, referenceMap); + this.relationBoundaryBuilder = new RelationBoundaryBuilder(coordinateMap, referenceMap); } /** @@ -71,7 +73,7 @@ public void accept(Entity entity) { } else if (entity instanceof Way way) { wayGeometryBuilder.accept(way); } else if (entity instanceof Relation relation && isMultiPolygon(relation)) { - relationMultiPolygonBuilder.accept(relation); + relationMultiPolygonBuilder.andThen(relationBoundaryBuilder).accept(relation); } } diff --git a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java index 491bd11fe..47bcdd8a4 100644 --- a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java +++ b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/function/RelationBoundaryBuilder.java @@ -23,9 +23,8 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.baremaps.openstreetmap.model.Entity; -import org.apache.baremaps.openstreetmap.model.Node; import org.apache.baremaps.openstreetmap.model.Relation; -import org.apache.baremaps.openstreetmap.model.Way; +import org.apache.baremaps.openstreetmap.utils.GeometryUtils; import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.util.GeometryCombiner; import org.slf4j.Logger; @@ -35,61 +34,91 @@ public class RelationBoundaryBuilder implements Consumer { private static final Logger logger = LoggerFactory.getLogger(RelationBoundaryBuilder.class); - private final Map nodes; - private final Map ways; - private final Map relations; - private final GeometryFactory geometryFactory; + private final Map coordinateMap; + private final Map> referenceMap; - public RelationBoundaryBuilder( - Map nodes, - Map ways, - Map relations, - GeometryFactory geometryFactory) { - this.nodes = nodes; - this.ways = ways; - this.relations = relations; - this.geometryFactory = geometryFactory; + public RelationBoundaryBuilder(Map coordinateMap, + Map> referenceMap) { + this.coordinateMap = coordinateMap; + this.referenceMap = referenceMap; } @Override public void accept(final Entity entity) { - if (entity instanceof Relation relation) { - if (!relation.getTags().containsKey("boundary")) { - return; - } - try { - var start = System.currentTimeMillis(); - - buildBoundary(relation); - - var end = System.currentTimeMillis(); - var duration = end - start; - if (duration > 60 * 1000) { - logger.debug("Relation #{} processed in {} ms", relation.getId(), duration); - } - } catch (Exception e) { - logger.error("Error processing relation #" + relation.getId(), e); + if (!(entity instanceof Relation relation)) { + return; + } + if (!relation.getTags().containsKey("boundary")) { + return; + } + try { + long start = System.currentTimeMillis(); + buildBoundary(relation); + long end = System.currentTimeMillis(); + long duration = end - start; + if (duration > 60 * 1000) { + logger.debug("Relation #{} processed in {} ms", relation.getId(), duration); } + } catch (Exception e) { + logger.error("Error processing relation #" + relation.getId(), e); } } - public Geometry buildBoundary(Relation relation) { + public void buildBoundary(Relation relation) { List geometries = relation.getMembers().stream() .map(member -> switch (member.type()) { - case NODE -> nodes.get(member.ref()).getGeometry(); - case WAY -> ways.get(member.ref()).getGeometry(); - case RELATION -> buildBoundary(relations.get(member.ref())); + case NODE -> { + Coordinate coord = coordinateMap.get(member.ref()); + yield (coord != null) ? GeometryUtils.GEOMETRY_FACTORY_WGS84.createPoint(coord) : null; + } + case WAY -> { + List nodeIds = referenceMap.get(member.ref()); + if (nodeIds == null || nodeIds.isEmpty()) { + yield null; + } + Coordinate[] coords = nodeIds.stream() + .map(coordinateMap::get) + .filter(Objects::nonNull) + .toArray(Coordinate[]::new); + if (coords.length < 2) { + yield null; + } + if (coords[0].equals2D(coords[coords.length - 1]) && coords.length >= 4) { + LinearRing ring = GeometryUtils.GEOMETRY_FACTORY_WGS84.createLinearRing(coords); + yield GeometryUtils.GEOMETRY_FACTORY_WGS84.createPolygon(ring); + } else { + yield GeometryUtils.GEOMETRY_FACTORY_WGS84.createLineString(coords); + } + } + case RELATION -> null; }) .filter(Objects::nonNull) .collect(Collectors.toList()); - GeometryCombiner combiner = new GeometryCombiner(geometries); - Geometry combinedGeometry = combiner.combine(); - - if (combinedGeometry instanceof MultiPolygon || combinedGeometry instanceof MultiLineString) { - return combinedGeometry; + Geometry finalGeometry; + if (geometries.isEmpty()) { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createGeometryCollection(new Geometry[]{}); } else { - return geometryFactory.createGeometryCollection(new Geometry[] {combinedGeometry}); + Geometry combinedGeometry = GeometryCombiner.combine(geometries); + if (combinedGeometry instanceof Polygon) { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiPolygon(new Polygon[]{(Polygon) combinedGeometry}); + } else if (combinedGeometry instanceof LineString) { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiLineString(new LineString[]{(LineString) combinedGeometry}); + } else if (combinedGeometry instanceof GeometryCollection && combinedGeometry.getNumGeometries() == 1) { + Geometry single = combinedGeometry.getGeometryN(0); + if (single instanceof Polygon) { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiPolygon(new Polygon[]{(Polygon) single}); + } else if (single instanceof LineString) { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createMultiLineString(new LineString[]{(LineString) single}); + } else { + finalGeometry = combinedGeometry; + } + } else if (combinedGeometry instanceof MultiPolygon || combinedGeometry instanceof MultiLineString) { + finalGeometry = combinedGeometry; + } else { + finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createGeometryCollection(new Geometry[]{combinedGeometry}); + } } + relation.setGeometry(finalGeometry); } }