Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ target/
.settings
bin/

# cursor
.cursor/

TODO

data/
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ java -Xmx1g -jar planetiler.jar --download --area=monaco
Or using Docker:

```bash
docker run -e JAVA_TOOL_OPTIONS="-Xmx1g" -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:latest --download --area=monaco
docker run --rm -e JAVA_TOOL_OPTIONS="-Xmx1g" -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:latest --download --area=monaco
```

:warning: This starts off by downloading about 1GB of [data sources](NOTICE.md#data) required by the OpenMapTiles
Expand All @@ -73,7 +73,7 @@ java -Xmx1g -jar planetiler.jar --download --area=monaco \
Docker:

```bash
docker run -e JAVA_TOOL_OPTIONS="-Xmx1g" -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:latest --download --area=monaco \
docker run --rm -e JAVA_TOOL_OPTIONS="-Xmx1g" -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:latest --download --area=monaco \
--water-polygons-url=https://github.com/onthegomap/planetiler/raw/main/planetiler-core/src/test/resources/water-polygons-split-3857.zip \
--natural-earth-url=https://github.com/onthegomap/planetiler/raw/main/planetiler-core/src/test/resources/natural_earth_vector.sqlite.zip
```
Expand Down
19 changes: 19 additions & 0 deletions planetiler-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,25 @@
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<id>add-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/protobuf/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate {
private final Object waysInMultipolygonLock = new Object();
// ~7GB
private LongLongMultimap.Replaceable multipolygonWayGeometries;
// for relation_members: track relations that need member processing
private Roaring64Bitmap relationsForMemberProcessing = new Roaring64Bitmap();
// for relation_members: track node-to-relation membership
private LongLongMultimap.Appendable nodesToRelations = LongLongMultimap.newAppendableMultimap();
// for relation_members: track ways-to-relation membership
private Roaring64Bitmap waysInRelationMembers = new Roaring64Bitmap();
// for relation_members: store way geometries (node IDs) for member ways
private LongLongMultimap.Replaceable relationMembersWayGeometries;
// for relation_members: store way tags for member ways
private LongObjectHashMap<Map<String, Object>> relationMembersWayTags = Hppc.newLongObjectHashMap();
private final Object relationMembersWayTagsLock = new Object();
// for relation_members: store node tags for member nodes
private LongObjectHashMap<Map<String, Object>> relationMembersNodeTags = Hppc.newLongObjectHashMap();
private final Object relationMembersNodeTagsLock = new Object();
// keep track of data needed to encode/decode role strings into a long
private final ObjectIntHashMap<String> roleIds = new ObjectIntHashMap<>();
private final IntObjectHashMap<String> roleIdsReverse = new IntObjectHashMap<>();
Expand Down Expand Up @@ -133,6 +147,8 @@ public OsmReader(String name, Supplier<OsmBlockSource> osmSourceProvider, LongLo
"relations", pass1Phaser::relations
));
this.multipolygonWayGeometries = multipolygonGeometries;
// Initialize relation members way geometries storage (similar to multipolygons)
this.relationMembersWayGeometries = LongLongMultimap.newInMemoryReplaceableMultimap();
}

/**
Expand Down Expand Up @@ -277,19 +293,28 @@ void processPass1Blocks(Iterable<? extends Iterable<? extends OsmElement>> block
try {
List<OsmRelationInfo> infos = profile.preprocessOsmRelation(relation);
if (infos != null && !infos.isEmpty()) {
boolean needsMemberProcessing = infos.stream()
.anyMatch(info -> info.getClass().getName().contains("RelationMembersInfo"));
synchronized (wayToRelationsLock) {
if (needsMemberProcessing) {
relationsForMemberProcessing.add(relation.id());
}
for (OsmRelationInfo info : infos) {
relationInfo.put(relation.id(), info);
relationInfoSizes.addAndGet(info.estimateMemoryUsageBytes());
}
for (var member : relation.members()) {
var type = member.type();
// TODO handle nodes in relations
if (type == OsmElement.Type.WAY) {
if (type == OsmElement.Type.NODE) {
nodesToRelations.put(member.ref(), encodeRelationMembership(member.role(), relation.id()));
} else if (type == OsmElement.Type.WAY) {
wayToRelations.put(member.ref(), encodeRelationMembership(member.role(), relation.id()));
if (needsMemberProcessing) {
waysInRelationMembers.add(member.ref());
}
} else if (type == OsmElement.Type.RELATION) {
relationToParentRelations.put(member.ref(),
encodeRelationMembership(member.role(), relation.id()));
encodeRelationMembership(member.role(), relation.id()));
}
}
}
Expand Down Expand Up @@ -359,9 +384,18 @@ public void pass2(FeatureGroup writer, PlanetilerConfig config) {
try (var renderer = createFeatureRenderer(writer, config, next)) {
var phaser = pass2Phaser.forWorker();
var relationHandler = relationDistributor.forThread(relation -> {
var feature = processRelationPass2(relation, nodeLocations);
if (feature != null) {
render(featureCollectors, renderer, relation, feature);
// Process as multipolygon if applicable (independent check)
if (isMultipolygon(relation)) {
List<RelationMember<OsmRelationInfo>> parentRelations = getRelationMembershipForWay(relation.id());
SourceFeature multipolygonFeature = new MultipolygonSourceFeature(relation, nodeLocations, parentRelations);
render(featureCollectors, renderer, relation, multipolygonFeature);
}
// Process as relation_members if applicable (independent check - can be both)
if (relationsForMemberProcessing.contains(relation.id())) {
List<RelationMember<OsmRelationInfo>> parentRelations = getRelationMembershipForWay(relation.id());
RelationMemberDataProvider dataProvider = createRelationMemberDataProvider();
SourceFeature relationMembersFeature = new RelationSourceFeature(relation, parentRelations, dataProvider);
render(featureCollectors, renderer, relation, relationMembersFeature);
}
rels.inc();
});
Expand Down Expand Up @@ -530,9 +564,48 @@ private FeatureRenderer createFeatureRenderer(FeatureGroup writer, PlanetilerCon
);
}

private RelationMemberDataProvider createRelationMemberDataProvider() {
return new RelationMemberDataProvider() {
@Override
public LongArrayList getWayGeometry(long wayId) {
return relationMembersWayGeometries != null ? relationMembersWayGeometries.get(wayId) : null;
}

@Override
public Map<String, Object> getWayTags(long wayId) {
return relationMembersWayTags != null ? relationMembersWayTags.get(wayId) : null;
}

@Override
public Map<String, Object> getNodeTags(long nodeId) {
return relationMembersNodeTags != null ? relationMembersNodeTags.get(nodeId) : null;
}

@Override
public org.locationtech.jts.geom.Coordinate getNodeCoordinate(long nodeId) {
long encoded = nodeLocationDb.get(nodeId);
if (encoded == LongLongMap.MISSING_VALUE) {
return null;
}
return new org.locationtech.jts.geom.CoordinateXY(
GeoUtils.decodeWorldX(encoded),
GeoUtils.decodeWorldY(encoded)
);
}
};
}

SourceFeature processNodePass2(OsmElement.Node node) {
// nodes are simple because they already contain their location
return new NodeSourceFeature(node);
// Store node tags if this node is a member of a relation_members relation
//
if (!nodesToRelations.get(node.id()).isEmpty()) {
synchronized (relationMembersNodeTagsLock) {
relationMembersNodeTags.put(node.id(), node.tags());
}
}
List<RelationMember<OsmRelationInfo>> rels = getRelationMembershipForNode(node.id());
return new NodeSourceFeature(node, rels);
}

WaySourceFeature processWayPass2(OsmElement.Way way, NodeLocationProvider nodeLocations) {
Expand All @@ -546,6 +619,15 @@ WaySourceFeature processWayPass2(OsmElement.Way way, NodeLocationProvider nodeLo
multipolygonWayGeometries.replaceValues(way.id(), nodes);
}
}
// Store way geometry and tags if this way is a member of a relation_members relation
if (waysInRelationMembers.contains(way.id())) {
synchronized (this) {
relationMembersWayGeometries.replaceValues(way.id(), nodes);
}
synchronized (relationMembersWayTagsLock) {
relationMembersWayTags.put(way.id(), way.tags());
}
}
boolean closed = nodes.size() > 1 && nodes.get(0) == nodes.get(nodes.size() - 1);
// area tag used to differentiate between whether a closed way should be treated as a polygon or linestring
String area = way.getString("area");
Expand All @@ -559,13 +641,19 @@ SourceFeature processRelationPass2(OsmElement.Relation rel, NodeLocationProvider
if (isMultipolygon(rel)) {
List<RelationMember<OsmRelationInfo>> parentRelations = getRelationMembershipForWay(rel.id());
return new MultipolygonSourceFeature(rel, nodeLocations, parentRelations);
} else {
return null;
}
if (relationsForMemberProcessing.contains(rel.id())) {
// This relation needs member processing (relation_members geometry)
List<RelationMember<OsmRelationInfo>> parentRelations = getRelationMembershipForWay(rel.id());
RelationMemberDataProvider dataProvider = createRelationMemberDataProvider();
return new RelationSourceFeature(rel, parentRelations, dataProvider);
}
return null;
}

private List<RelationMember<OsmRelationInfo>> getRelationMembershipForWay(long wayId) {
LongArrayList relationIds = wayToRelations.get(wayId);
private List<RelationMember<OsmRelationInfo>> getRelationMembership(
LongLongMultimap.Appendable relationshipMap, long elementId) {
LongArrayList relationIds = relationshipMap.get(elementId);
List<RelationMember<OsmRelationInfo>> rels = null;
if (!relationIds.isEmpty()) {
rels = new ArrayList<>(relationIds.size());
Expand All @@ -591,6 +679,14 @@ private List<RelationMember<OsmRelationInfo>> getRelationMembershipForWay(long w
return rels;
}

private List<RelationMember<OsmRelationInfo>> getRelationMembershipForWay(long wayId) {
return getRelationMembership(wayToRelations, wayId);
}

private List<RelationMember<OsmRelationInfo>> getRelationMembershipForNode(long nodeId) {
return getRelationMembership(nodesToRelations, nodeId);
}

private List<RelationMember<OsmRelationInfo>> getRelationInfosForRelationId(long relationIdAndRole,
HashSet<Long> visited, List<Long> parentRelationPath) {
var parsed = decodeRelationMembership(relationIdAndRole);
Expand All @@ -615,7 +711,11 @@ public long estimateMemoryUsageBytes() {
long size = 0;
size += waysInMultipolygon == null ? 0 : waysInMultipolygon.serializedSizeInBytes();
// multipolygonWayGeometries is reported separately
size += waysInRelationMembers == null ? 0 : waysInRelationMembers.serializedSizeInBytes();
size += estimateSize(relationMembersWayTags);
size += estimateSize(relationMembersNodeTags);
size += estimateSize(wayToRelations);
size += estimateSize(nodesToRelations);
size += estimateSize(relationToParentRelations);
size += estimateSize(relationInfo);
size += estimateSize(roleIdsReverse);
Expand All @@ -632,9 +732,17 @@ public void close() throws IOException {
multipolygonWayGeometries.close();
multipolygonWayGeometries = null;
}
if (relationMembersWayGeometries != null) {
relationMembersWayGeometries.close();
relationMembersWayGeometries = null;
}
wayToRelations = null;
nodesToRelations = null;
relationToParentRelations = null;
waysInMultipolygon = null;
waysInRelationMembers = null;
relationMembersWayTags = null;
relationMembersNodeTags = null;
relationInfo = null;
nodeLocationDb.close();
roleIds.release();
Expand Down Expand Up @@ -775,6 +883,11 @@ private class NodeSourceFeature extends OsmFeature<OsmElement.Node> {
this.encodedLocation = node.encodedLocation();
}

NodeSourceFeature(OsmElement.Node node, List<RelationMember<OsmRelationInfo>> relationInfo) {
super(node, true, false, false, relationInfo);
this.encodedLocation = node.encodedLocation();
}

@Override
protected Geometry computeWorldGeometry() {
return GeoUtils.point(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.onthegomap.planetiler.reader.osm;

import com.carrotsearch.hppc.LongArrayList;
import java.util.Map;

/**
* Provides access to stored member data for relation_members processing.
* Used to retrieve way geometries, way tags, and node tags for relation members.
*/
public interface RelationMemberDataProvider {

/**
* Gets the node IDs for a way member.
* @param wayId the way ID
* @return the node IDs, or null if not found
*/
LongArrayList getWayGeometry(long wayId);

/**
* Gets the tags for a way member.
* @param wayId the way ID
* @return the tags, or null if not found
*/
Map<String, Object> getWayTags(long wayId);

/**
* Gets the tags for a node member.
* @param nodeId the node ID
* @return the tags, or null if not found
*/
Map<String, Object> getNodeTags(long nodeId);

/**
* Gets the coordinate for a node.
* @param nodeId the node ID
* @return the coordinate, or null if not found
*/
org.locationtech.jts.geom.Coordinate getNodeCoordinate(long nodeId);
}

Loading
Loading