kopia lustrzana https://github.com/onthegomap/planetiler
relation info
rodzic
df1d6afa54
commit
fd3267ac85
|
@ -2,6 +2,9 @@ package com.onthegomap.flatmap;
|
|||
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.read.OpenStreetMapReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.Lineal;
|
||||
|
@ -11,11 +14,14 @@ public abstract class SourceFeature {
|
|||
private final Map<String, Object> properties;
|
||||
private final String source;
|
||||
private final String sourceLayer;
|
||||
private final List<OpenStreetMapReader.RelationInfo> relationInfos;
|
||||
|
||||
protected SourceFeature(Map<String, Object> properties, String source, String sourceLayer) {
|
||||
protected SourceFeature(Map<String, Object> properties, String source, String sourceLayer,
|
||||
List<OpenStreetMapReader.RelationInfo> relationInfos) {
|
||||
this.properties = properties;
|
||||
this.source = source;
|
||||
this.sourceLayer = sourceLayer;
|
||||
this.relationInfos = relationInfos;
|
||||
}
|
||||
|
||||
public abstract Geometry latLonGeometry() throws GeometryException;
|
||||
|
@ -147,4 +153,19 @@ public abstract class SourceFeature {
|
|||
public String getSourceLayer() {
|
||||
return sourceLayer;
|
||||
}
|
||||
|
||||
public <T extends OpenStreetMapReader.RelationInfo> List<T> relationInfo(Class<T> relationInfoClass) {
|
||||
List<T> result = null;
|
||||
if (relationInfos != null) {
|
||||
for (OpenStreetMapReader.RelationInfo info : relationInfos) {
|
||||
if (relationInfoClass.isInstance(info)) {
|
||||
if (result == null) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
result.add(relationInfoClass.cast(info));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result == null ? List.of() : result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,7 +209,18 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
}
|
||||
boolean closed = nodes.size() > 1 && nodes.get(0) == nodes.get(nodes.size() - 1);
|
||||
String area = way.getTag("area");
|
||||
return new WaySourceFeature(way, closed, area, nodeCache);
|
||||
LongArrayList relationIds = wayToRelations.get(way.getId());
|
||||
List<RelationInfo> rels = null;
|
||||
if (!relationIds.isEmpty()) {
|
||||
rels = new ArrayList<>(relationIds.size());
|
||||
for (int r = 0; r < relationIds.size(); r++) {
|
||||
RelationInfo rel = relationInfo.get(relationIds.get(r));
|
||||
if (rel != null) {
|
||||
rels.add(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new WaySourceFeature(way, closed, area, nodeCache, rels);
|
||||
}
|
||||
|
||||
SourceFeature processNodePass2(ReaderNode node) {
|
||||
|
@ -237,10 +248,10 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
nodeDb.close();
|
||||
}
|
||||
|
||||
public static class RelationInfo implements MemoryEstimator.HasEstimate {
|
||||
public interface RelationInfo extends MemoryEstimator.HasEstimate {
|
||||
|
||||
@Override
|
||||
public long estimateMemoryUsageBytes() {
|
||||
default long estimateMemoryUsageBytes() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -252,8 +263,9 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
final boolean point;
|
||||
final long osmId;
|
||||
|
||||
public ProxyFeature(ReaderElement elem, boolean point, boolean line, boolean polygon) {
|
||||
super(ReaderElementUtils.getProperties(elem), name, null);
|
||||
public ProxyFeature(ReaderElement elem, boolean point, boolean line, boolean polygon,
|
||||
List<RelationInfo> relationInfo) {
|
||||
super(ReaderElementUtils.getProperties(elem), name, null, relationInfo);
|
||||
this.point = point;
|
||||
this.line = line;
|
||||
this.polygon = polygon;
|
||||
|
@ -298,7 +310,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
private final double lat;
|
||||
|
||||
NodeSourceFeature(ReaderNode node) {
|
||||
super(node, true, false, false);
|
||||
super(node, true, false, false, null);
|
||||
this.lon = node.getLon();
|
||||
this.lat = node.getLat();
|
||||
}
|
||||
|
@ -327,10 +339,12 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
private final NodeLocationProvider nodeCache;
|
||||
private final LongArrayList nodeIds;
|
||||
|
||||
public WaySourceFeature(ReaderWay way, boolean closed, String area, NodeLocationProvider nodeCache) {
|
||||
public WaySourceFeature(ReaderWay way, boolean closed, String area, NodeLocationProvider nodeCache,
|
||||
List<RelationInfo> relationInfo) {
|
||||
super(way, false,
|
||||
(!closed || !"yes".equals(area)) && way.getNodes().size() >= 2,
|
||||
(closed && !"no".equals(area)) && way.getNodes().size() >= 4
|
||||
(closed && !"no".equals(area)) && way.getNodes().size() >= 4,
|
||||
relationInfo
|
||||
);
|
||||
this.nodeIds = way.getNodes();
|
||||
this.nodeCache = nodeCache;
|
||||
|
@ -373,7 +387,7 @@ public class OpenStreetMapReader implements Closeable, MemoryEstimator.HasEstima
|
|||
private final NodeLocationProvider nodeCache;
|
||||
|
||||
public MultipolygonSourceFeature(ReaderRelation relation, NodeLocationProvider nodeCache) {
|
||||
super(relation, false, false, true);
|
||||
super(relation, false, false, true, null);
|
||||
this.relation = relation;
|
||||
this.nodeCache = nodeCache;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ public class ReaderFeature extends SourceFeature {
|
|||
}
|
||||
|
||||
public ReaderFeature(Geometry latLonGeometry, Map<String, Object> properties, String source, String sourceLayer) {
|
||||
super(properties, source, sourceLayer);
|
||||
super(properties, source, sourceLayer, null);
|
||||
this.latLonGeometry = latLonGeometry;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,11 @@ public class FlatMapTest {
|
|||
private static final int Z4_TILES = 1 << 4;
|
||||
private final Stats stats = new Stats.InMemory();
|
||||
|
||||
private static <T extends ReaderElement> T with(T elem, Consumer<T> fn) {
|
||||
fn.accept(elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
private void processReaderFeatures(FeatureGroup featureGroup, Profile profile, CommonParams config,
|
||||
List<? extends SourceFeature> features) {
|
||||
new Reader(profile, stats, "test") {
|
||||
|
@ -97,15 +102,10 @@ public class FlatMapTest {
|
|||
}
|
||||
}
|
||||
|
||||
private interface Runner {
|
||||
|
||||
void run(FeatureGroup featureGroup, Profile profile, CommonParams config) throws Exception;
|
||||
}
|
||||
|
||||
private FlatMapResults run(
|
||||
Map<String, String> args,
|
||||
Runner runner,
|
||||
BiConsumer<SourceFeature, FeatureCollector> profileFunction
|
||||
Profile profile
|
||||
) throws Exception {
|
||||
CommonParams config = CommonParams.from(Arguments.of(args));
|
||||
var translations = Translations.defaultProvider(List.of());
|
||||
|
@ -113,7 +113,6 @@ public class FlatMapTest {
|
|||
LongLongMap nodeLocations = LongLongMap.newInMemorySortedTable();
|
||||
FeatureSort featureDb = FeatureSort.newInMemory();
|
||||
FeatureGroup featureGroup = new FeatureGroup(featureDb, profile1, stats);
|
||||
var profile = TestProfile.processSourceFeatures(profileFunction);
|
||||
runner.run(featureGroup, profile, config);
|
||||
featureGroup.sorter().sort();
|
||||
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
||||
|
@ -134,7 +133,7 @@ public class FlatMapTest {
|
|||
return run(
|
||||
args,
|
||||
(featureGroup, profile, config) -> processReaderFeatures(featureGroup, profile, config, features),
|
||||
profileFunction
|
||||
TestProfile.processSourceFeatures(profileFunction)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -146,7 +145,20 @@ public class FlatMapTest {
|
|||
return run(
|
||||
args,
|
||||
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
||||
profileFunction
|
||||
TestProfile.processSourceFeatures(profileFunction)
|
||||
);
|
||||
}
|
||||
|
||||
private FlatMapResults runWithOsmElements(
|
||||
Map<String, String> args,
|
||||
List<ReaderElement> features,
|
||||
Function<ReaderRelation, List<OpenStreetMapReader.RelationInfo>> preprocessOsmRelation,
|
||||
BiConsumer<SourceFeature, FeatureCollector> profileFunction
|
||||
) throws Exception {
|
||||
return run(
|
||||
args,
|
||||
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
||||
new TestProfile(profileFunction, preprocessOsmRelation, (a, b, c) -> null)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -654,11 +666,6 @@ public class FlatMapTest {
|
|||
), results.tiles);
|
||||
}
|
||||
|
||||
private static <T extends ReaderElement> T with(T elem, Consumer<T> fn) {
|
||||
fn.accept(elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmLine() throws Exception {
|
||||
var results = runWithOsmElements(
|
||||
|
@ -741,15 +748,6 @@ public class FlatMapTest {
|
|||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
||||
private <K extends Comparable<? super K>, V extends List<?>> Map<K, ?> sortListValues(Map<K, V> input) {
|
||||
Map<K, List<?>> result = new TreeMap<>();
|
||||
for (var entry : input.entrySet()) {
|
||||
List<?> sorted = entry.getValue().stream().sorted(Comparator.comparing(Object::toString)).toList();
|
||||
result.put(entry.getKey(), sorted);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmMultipolygon() throws Exception {
|
||||
var results = runWithOsmElements(
|
||||
|
@ -812,11 +810,80 @@ public class FlatMapTest {
|
|||
), results.tiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOsmLineInRelation() throws Exception {
|
||||
record TestRelationInfo(String name) implements OpenStreetMapReader.RelationInfo {}
|
||||
var results = runWithOsmElements(
|
||||
Map.of("threads", "1"),
|
||||
List.of(
|
||||
new ReaderNode(1, 0, 0),
|
||||
new ReaderNode(2, GeoUtils.getWorldLat(0.375), 0),
|
||||
new ReaderNode(3, GeoUtils.getWorldLat(0.25), 0),
|
||||
new ReaderNode(4, GeoUtils.getWorldLat(0.125), 0),
|
||||
with(new ReaderWay(5), way -> {
|
||||
way.setTag("attr", "value1");
|
||||
way.getNodes().add(1, 2);
|
||||
}),
|
||||
with(new ReaderWay(6), way -> {
|
||||
way.setTag("attr", "value2");
|
||||
way.getNodes().add(3, 4);
|
||||
}),
|
||||
with(new ReaderRelation(6), rel -> {
|
||||
rel.setTag("name", "relation name");
|
||||
rel.add(new ReaderRelation.Member(ReaderRelation.WAY, 6, "role"));
|
||||
})
|
||||
),
|
||||
(relation) -> {
|
||||
if (relation.hasTag("name", "relation name")) {
|
||||
return List.of(new TestRelationInfo(relation.getTag("name")));
|
||||
}
|
||||
return null;
|
||||
}, (in, features) -> {
|
||||
List<TestRelationInfo> relationInfos = in.relationInfo(TestRelationInfo.class);
|
||||
if (in.canBeLine()) {
|
||||
features.line("layer")
|
||||
.setZoomRange(0, 0)
|
||||
.setAttr("relname", relationInfos.stream()
|
||||
.findFirst().map(TestRelationInfo::name)
|
||||
.orElse(null))
|
||||
.inheritFromSource("attr");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
assertSubmap(sortListValues(Map.of(
|
||||
TileCoord.ofXYZ(0, 0, 0), List.of(
|
||||
feature(newLineString(128, 128, 128, 0.375 * 256), Map.of(
|
||||
"attr", "value1"
|
||||
)),
|
||||
feature(newLineString(128, 0.25 * 256, 128, 0.125 * 256), Map.of(
|
||||
"attr", "value2",
|
||||
"relname", "relation name"
|
||||
))
|
||||
)
|
||||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
||||
|
||||
private <K extends Comparable<? super K>, V extends List<?>> Map<K, ?> sortListValues(Map<K, V> input) {
|
||||
Map<K, List<?>> result = new TreeMap<>();
|
||||
for (var entry : input.entrySet()) {
|
||||
List<?> sorted = entry.getValue().stream().sorted(Comparator.comparing(Object::toString)).toList();
|
||||
result.put(entry.getKey(), sorted);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map.Entry<TileCoord, List<TestUtils.ComparableFeature>> newTileEntry(int x, int y, int z,
|
||||
List<TestUtils.ComparableFeature> features) {
|
||||
return Map.entry(TileCoord.ofXYZ(x, y, z), features);
|
||||
}
|
||||
|
||||
private interface Runner {
|
||||
|
||||
void run(FeatureGroup featureGroup, Profile profile, CommonParams config) throws Exception;
|
||||
}
|
||||
|
||||
private interface LayerPostprocessFunction {
|
||||
|
||||
List<VectorTileEncoder.Feature> process(String layer, int zoom, List<VectorTileEncoder.Feature> items);
|
||||
|
|
|
@ -643,6 +643,47 @@ public class OpenStreetMapReaderTest {
|
|||
assertThrows(GeometryException.class, feature::validatedPolygon);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWayInRelation() {
|
||||
record OtherRelInfo() implements OpenStreetMapReader.RelationInfo {}
|
||||
record TestRelInfo(String name) implements OpenStreetMapReader.RelationInfo {
|
||||
|
||||
@Override
|
||||
public long estimateMemoryUsageBytes() {
|
||||
return 10 + name.length();
|
||||
}
|
||||
}
|
||||
OpenStreetMapReader reader = new OpenStreetMapReader(
|
||||
osmSource,
|
||||
longLongMap,
|
||||
new Profile.NullProfile() {
|
||||
@Override
|
||||
public List<OpenStreetMapReader.RelationInfo> preprocessOsmRelation(ReaderRelation relation) {
|
||||
return List.of(new TestRelInfo("name"));
|
||||
}
|
||||
},
|
||||
stats
|
||||
);
|
||||
var nodeCache = reader.newNodeGeometryCache();
|
||||
var node1 = new ReaderNode(1, 0, 0);
|
||||
var node2 = node(2, 0.75, 0.75);
|
||||
var way = new ReaderWay(3);
|
||||
way.getNodes().add(node1.getId(), node2.getId());
|
||||
way.setTag("key", "value");
|
||||
var relation = new ReaderRelation(4);
|
||||
relation.add(new ReaderRelation.Member(ReaderRelation.Member.WAY, 3, ""));
|
||||
|
||||
reader.processPass1(node1);
|
||||
reader.processPass1(node2);
|
||||
reader.processPass1(way);
|
||||
reader.processPass1(relation);
|
||||
|
||||
SourceFeature feature = reader.processWayPass2(nodeCache, way);
|
||||
|
||||
assertEquals(List.of(), feature.relationInfo(OtherRelInfo.class));
|
||||
assertEquals(List.of(new TestRelInfo("name")), feature.relationInfo(TestRelInfo.class));
|
||||
}
|
||||
|
||||
private OpenStreetMapReader newOsmReader() {
|
||||
return new OpenStreetMapReader(
|
||||
osmSource,
|
||||
|
|
Ładowanie…
Reference in New Issue