Add profile hooks for preprocessing OSM nodes and ways (#56)

pull/49/head
Michael Barry 2022-01-16 10:00:57 -05:00 zatwierdzone przez GitHub
rodzic ad9d547ffc
commit 6f2dbc4f4b
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 159 dodań i 3 usunięć

Wyświetl plik

@ -27,6 +27,10 @@ import java.util.function.Consumer;
public abstract class ForwardingProfile implements Profile {
private final List<Handler> handlers = new ArrayList<>();
/** Handlers that pre-process OSM nodes during pass 1 through the data. */
private final List<OsmNodePreprocessor> osmNodePreprocessors = new ArrayList<>();
/** Handlers that pre-process OSM ways during pass 1 through the data. */
private final List<OsmWayPreprocessor> osmWayPreprocessors = new ArrayList<>();
/** Handlers that pre-process OSM relations during pass 1 through the data. */
private final List<OsmRelationPreprocessor> osmRelationPreprocessors = new ArrayList<>();
/** Handlers that get a callback when each source is finished reading. */
@ -53,6 +57,12 @@ public abstract class ForwardingProfile implements Profile {
*/
public void registerHandler(Handler handler) {
this.handlers.add(handler);
if (handler instanceof OsmNodePreprocessor osmNodePreprocessor) {
osmNodePreprocessors.add(osmNodePreprocessor);
}
if (handler instanceof OsmWayPreprocessor osmWayPreprocessor) {
osmWayPreprocessors.add(osmWayPreprocessor);
}
if (handler instanceof OsmRelationPreprocessor osmRelationPreprocessor) {
osmRelationPreprocessors.add(osmRelationPreprocessor);
}
@ -65,6 +75,20 @@ public abstract class ForwardingProfile implements Profile {
}
}
@Override
public void preprocessOsmNode(OsmElement.Node node) {
for (OsmNodePreprocessor osmNodePreprocessor : osmNodePreprocessors) {
osmNodePreprocessor.preprocessOsmNode(node);
}
}
@Override
public void preprocessOsmWay(OsmElement.Way way) {
for (OsmWayPreprocessor osmWayPreprocessor : osmWayPreprocessors) {
osmWayPreprocessor.preprocessOsmWay(way);
}
}
@Override
public List<OsmRelationInfo> preprocessOsmRelation(OsmElement.Relation relation) {
// delegate OSM relation pre-processing to each layer, if it implements FeaturePostProcessor
@ -159,6 +183,30 @@ public abstract class ForwardingProfile implements Profile {
Consumer<FeatureCollector.Feature> emit);
}
/** Handlers should implement this interface to pre-process OSM nodes during pass 1 through the data. */
public interface OsmNodePreprocessor extends Handler {
/**
* Extracts information from an OSM node during pass 1 of the input OSM data that the profile may need during
* pass2.
*
* @see Profile#preprocessOsmNode(OsmElement.Node)
*/
void preprocessOsmNode(OsmElement.Node node);
}
/** Handlers should implement this interface to pre-process OSM ways during pass 1 through the data. */
public interface OsmWayPreprocessor extends Handler {
/**
* Extracts information from an OSM way during pass 1 of the input OSM data that the profile may need during pass2.
*
* @see Profile#preprocessOsmWay(OsmElement.Way)
*/
void preprocessOsmWay(OsmElement.Way way);
}
/** Handlers should implement this interface to pre-process OSM relations during pass 1 through the data. */
public interface OsmRelationPreprocessor extends Handler {

Wyświetl plik

@ -32,6 +32,28 @@ import java.util.function.Consumer;
public interface Profile {
// TODO might want to break this apart into sub-interfaces that ForwardingProfile (and MbtilesMetadata) can use too
/**
* Allows profile to extract any information it needs from a {@link OsmElement.Node} during the first pass through OSM
* elements.
* <p>
* The default implementation does nothing.
*
* @param node the OSM node
*/
default void preprocessOsmNode(OsmElement.Node node) {
}
/**
* Allows profile to extract any information it needs from a {@link OsmElement.Way} during the first pass through OSM
* elements.
* <p>
* The default implementation does nothing.
*
* @param way the OSM way
*/
default void preprocessOsmWay(OsmElement.Way way) {
}
/**
* Extracts information from <a href="https://wiki.openstreetmap.org/wiki/Relation">OSM relations</a> that will be
* passed along to {@link #processFeature(SourceFeature, FeatureCollector)} for any OSM element in that relation.

Wyświetl plik

@ -160,10 +160,12 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate {
}
if (readerElement instanceof ReaderNode node) {
PASS1_NODES.inc();
profile.preprocessOsmNode(OsmElement.fromGraphopper(node));
// TODO allow limiting node storage to only ones that profile cares about
nodeLocationDb.put(node.getId(), GeoUtils.encodeFlatLocation(node.getLon(), node.getLat()));
} else if (readerElement instanceof ReaderWay) {
} else if (readerElement instanceof ReaderWay way) {
PASS1_WAYS.inc();
profile.preprocessOsmWay(OsmElement.fromGraphopper(way));
} else if (readerElement instanceof ReaderRelation rel) {
PASS1_RELATIONS.inc();
// don't leak graphhopper classes out through public API

Wyświetl plik

@ -10,6 +10,7 @@ import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -25,6 +26,38 @@ public class ForwardingProfileTests {
}
};
@Test
public void testPreprocessOsmNode() {
var node1 = new OsmElement.Node(1, 2, 3);
var node2 = new OsmElement.Node(2, 3, 4);
List<OsmElement.Node> calledWith = new ArrayList<>();
profile.registerHandler((ForwardingProfile.OsmNodePreprocessor) calledWith::add);
profile.preprocessOsmNode(node1);
assertEquals(List.of(node1), calledWith);
List<OsmElement.Node> calledWith2 = new ArrayList<>();
profile.registerHandler((ForwardingProfile.OsmNodePreprocessor) calledWith2::add);
profile.preprocessOsmNode(node2);
assertEquals(List.of(node1, node2), calledWith);
assertEquals(List.of(node2), calledWith2);
}
@Test
public void testPreprocessOsmWay() {
var way1 = new OsmElement.Way(1);
var way2 = new OsmElement.Way(2);
List<OsmElement.Way> calledWith = new ArrayList<>();
profile.registerHandler((ForwardingProfile.OsmWayPreprocessor) calledWith::add);
profile.preprocessOsmWay(way1);
assertEquals(List.of(way1), calledWith);
List<OsmElement.Way> calledWith2 = new ArrayList<>();
profile.registerHandler((ForwardingProfile.OsmWayPreprocessor) calledWith2::add);
profile.preprocessOsmWay(way2);
assertEquals(List.of(way1, way2), calledWith);
assertEquals(List.of(way2), calledWith2);
}
@Test
public void testPreprocessOsmRelation() {
record RelA(@Override long id) implements OsmRelationInfo {}

Wyświetl plik

@ -35,8 +35,10 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
@ -1031,6 +1033,55 @@ public class PlanetilerTests {
)), sortListValues(results.tiles));
}
@Test
public void testPreprocessOsmNodesAndWays() throws Exception {
Set<Long> nodes1 = new HashSet<>();
Set<Long> nodes2 = new HashSet<>();
var profile = new Profile.NullProfile() {
@Override
public void preprocessOsmNode(OsmElement.Node node) {
if (node.hasTag("a", "b")) {
nodes1.add(node.id());
}
}
@Override
public void preprocessOsmWay(OsmElement.Way way) {
if (nodes1.contains(way.nodes().get(0))) {
nodes2.add(way.nodes().get(0));
}
}
@Override
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
if (sourceFeature.isPoint() && nodes2.contains(sourceFeature.id())) {
features.point("start_nodes")
.setMaxZoom(0);
}
}
};
var results = run(
Map.of("threads", "1"),
(featureGroup, p, config) -> processOsmFeatures(featureGroup, p, config, List.of(
with(new ReaderNode(1, 0, 0), node -> node.setTag("a", "b")),
new ReaderNode(2, GeoUtils.getWorldLat(0.375), 0),
with(new ReaderWay(3), way -> {
way.getNodes().add(1, 2);
}),
with(new ReaderWay(4), way -> {
way.getNodes().add(1, 2);
})
)),
profile
);
assertSubmap(sortListValues(Map.of(
TileCoord.ofXYZ(0, 0, 0), List.of(
feature(newPoint(128, 128), Map.of())
)
)), sortListValues(results.tiles));
}
@Test
public void testPostProcessNodeUseLabelGridRank() throws Exception {
double y = 0.5 + Z14_WIDTH / 2;
@ -1421,11 +1472,11 @@ public class PlanetilerTests {
throws GeometryException;
}
private static record PlanetilerResults(
private record PlanetilerResults(
Map<TileCoord, List<TestUtils.ComparableFeature>> tiles, Map<String, String> metadata
) {}
private static record TestProfile(
private record TestProfile(
@Override String name,
@Override String description,
@Override String attribution,