Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()))) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class EntityGeometryBuilder implements Consumer<Entity> {
private final Consumer<Entity> nodeGeometryBuilder;
private final Consumer<Entity> wayGeometryBuilder;
private final Consumer<Entity> relationMultiPolygonBuilder;
private final Consumer<Entity> relationBoundaryBuilder;

/**
* Constructs a consumer that uses the provided caches to create and set geometries.
Expand All @@ -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);
}

/**
Expand All @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* 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.Relation;
import org.apache.baremaps.openstreetmap.utils.GeometryUtils;
import org.locationtech.jts.geom.*;
import org.locationtech.jts.geom.util.GeometryCombiner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RelationBoundaryBuilder implements Consumer<Entity> {

private static final Logger logger = LoggerFactory.getLogger(RelationBoundaryBuilder.class);

private final Map<Long, Coordinate> coordinateMap;
private final Map<Long, List<Long>> referenceMap;

public RelationBoundaryBuilder(Map<Long, Coordinate> coordinateMap,
Map<Long, List<Long>> referenceMap) {
this.coordinateMap = coordinateMap;
this.referenceMap = referenceMap;
}

@Override
public void accept(final Entity entity) {
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 void buildBoundary(Relation relation) {
List<Geometry> geometries = relation.getMembers().stream()
.map(member -> switch (member.type()) {
case NODE -> {
Coordinate coord = coordinateMap.get(member.ref());
yield (coord != null) ? GeometryUtils.GEOMETRY_FACTORY_WGS84.createPoint(coord) : null;
}
case WAY -> {
List<Long> 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());

Geometry finalGeometry;
if (geometries.isEmpty()) {
finalGeometry = GeometryUtils.GEOMETRY_FACTORY_WGS84.createGeometryCollection(new Geometry[]{});
} else {
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@

package org.apache.baremaps.openstreetmap.model;



import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down
19 changes: 18 additions & 1 deletion basemap/layers/boundary/line.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
});
32 changes: 29 additions & 3 deletions basemap/layers/boundary/tileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,36 @@ export default {
id: 'boundary',
queries: [
{
minzoom: 13,
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, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " +
"FROM osm_boundary " +
"WHERE tags ->> 'admin_level' = '2' " +
"AND COALESCE(tags ->> 'maritime', '') != 'yes';",
},
{
minzoom: 3.5,
maxzoom: 20,
sql:
"SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " +
"FROM osm_boundary " +
"WHERE tags ->> 'admin_level' = '2' " +
"AND tags ->> 'maritime' = 'yes';",
},
{
minzoom: 3.5,
maxzoom: 20,
sql:
"SELECT id, tags, ST_SimplifyPreserveTopology(geom, 0.01) AS geom " +
"FROM osm_boundary " +
"WHERE tags ->> 'admin_level' = '4';"
}
],
}
}
Loading