OSM QA Tiles Example Profile (#278)

pull/301/head
Michael Barry 2022-07-14 05:26:53 -04:00 zatwierdzone przez GitHub
rodzic 4b7a6018c9
commit c6ad30cc9a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
12 zmienionych plików z 391 dodań i 50 usunięć

Wyświetl plik

@ -15,10 +15,12 @@ import java.nio.file.Path;
public class BenchmarkOsmRead {
public static void main(String[] args) throws IOException {
OsmInputFile file = new OsmInputFile(Path.of("data/sources/northeast.osm.pbf"), true);
var profile = new Profile.NullProfile();
var stats = Stats.inMemory();
var config = PlanetilerConfig.from(Arguments.of());
var parsedArgs = Arguments.fromArgsOrConfigFile(args);
var config = PlanetilerConfig.from(parsedArgs);
var path = parsedArgs.inputFile("osm_path", "path to osm file", Path.of("data/sources/northeast.osm.pbf"));
OsmInputFile file = new OsmInputFile(path, config.osmLazyReads());
while (true) {
Timer timer = Timer.start();

Wyświetl plik

@ -43,7 +43,8 @@ public record PlanetilerConfig(
double simplifyToleranceBelowMaxZoom,
boolean osmLazyReads,
boolean compactDb,
boolean skipFilledTiles
boolean skipFilledTiles,
int tileWarningSizeBytes
) {
public static final int MIN_MINZOOM = 0;
@ -148,7 +149,10 @@ public record PlanetilerConfig(
true),
arguments.getBoolean("skip_filled_tiles",
"Skip writing tiles containing only polygon fills to the output",
false)
false),
(int) (arguments.getDouble("tile_warning_size_mb",
"Maximum size in megabytes of a tile to emit a warning about",
1d) * 1024 * 1024)
);
}

Wyświetl plik

@ -756,13 +756,17 @@ public final class Mbtiles implements Closeable {
public Metadata setMetadata(String name, Object value) {
if (value != null) {
LOGGER.debug("Set mbtiles metadata: {}={}", name, value);
String stringValue = value.toString();
LOGGER.debug("Set mbtiles metadata: {}={}", name,
stringValue.length() > 1_000 ?
(stringValue.substring(0, 1_000) + "... " + (stringValue.length() - 1_000) + " more characters") :
stringValue);
try (
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO " + METADATA_TABLE + " (" + METADATA_COL_NAME + "," + METADATA_COL_VALUE + ") VALUES(?, ?);")
) {
statement.setString(1, name);
statement.setString(2, value.toString());
statement.setString(2, stringValue);
statement.execute();
} catch (SQLException throwables) {
LOGGER.error("Error setting metadata " + name + "=" + value, throwables);

Wyświetl plik

@ -283,7 +283,7 @@ public class MbtilesWriter {
} else {
encoded = en.encode();
bytes = gzip(encoded);
if (encoded.length > 1_000_000) {
if (encoded.length > config.tileWarningSizeBytes()) {
LOGGER.warn("{} {}kb uncompressed",
tileFeatures.tileCoord(),
encoded.length / 1024);

Wyświetl plik

@ -20,6 +20,8 @@ public interface OsmElement extends WithTags {
/** OSM element ID */
long id();
Info info();
int cost();
enum Type {
@ -31,12 +33,13 @@ public interface OsmElement extends WithTags {
/** An un-handled element read from the .osm.pbf file (i.e. file header). */
record Other(
@Override long id,
@Override Map<String, Object> tags
@Override Map<String, Object> tags,
@Override Info info
) implements OsmElement {
@Override
public int cost() {
return 1 + tags.size();
return 1 + tags.size() + (info == null ? 0 : Info.COST);
}
}
@ -48,6 +51,7 @@ public interface OsmElement extends WithTags {
private final Map<String, Object> tags;
private final double lat;
private final double lon;
private final Info info;
// bailed out of a record to make encodedLocation lazy since it is fairly expensive to compute
private long encodedLocation = MISSING_LOCATION;
@ -55,16 +59,27 @@ public interface OsmElement extends WithTags {
long id,
Map<String, Object> tags,
double lat,
double lon
double lon,
Info info
) {
this.id = id;
this.tags = tags;
this.lat = lat;
this.lon = lon;
this.info = info;
}
public Node(long id, double lat, double lon) {
this(id, new HashMap<>(), lat, lon);
this(id, new HashMap<>(), lat, lon, null);
}
public Node(
long id,
Map<String, Object> tags,
double lat,
double lon
) {
this(id, tags, lat, lon, null);
}
@Override
@ -72,6 +87,11 @@ public interface OsmElement extends WithTags {
return id;
}
@Override
public Info info() {
return info;
}
@Override
public Map<String, Object> tags() {
return tags;
@ -94,7 +114,7 @@ public interface OsmElement extends WithTags {
@Override
public int cost() {
return 1 + tags.size();
return 1 + tags.size() + (info == null ? 0 : Info.COST);
}
@Override
@ -109,12 +129,13 @@ public interface OsmElement extends WithTags {
return this.id == that.id &&
Objects.equals(this.tags, that.tags) &&
Double.doubleToLongBits(this.lat) == Double.doubleToLongBits(that.lat) &&
Double.doubleToLongBits(this.lon) == Double.doubleToLongBits(that.lon);
Double.doubleToLongBits(this.lon) == Double.doubleToLongBits(that.lon) &&
Objects.equals(this.info, that.info);
}
@Override
public int hashCode() {
return Objects.hash(id, tags, lat, lon);
return Objects.hash(id, tags, lat, lon, info);
}
@Override
@ -123,7 +144,8 @@ public interface OsmElement extends WithTags {
"id=" + id + ", " +
"tags=" + tags + ", " +
"lat=" + lat + ", " +
"lon=" + lon + ']';
"lon=" + lon + ", " +
"info=" + info + ']';
}
}
@ -132,16 +154,21 @@ public interface OsmElement extends WithTags {
record Way(
@Override long id,
@Override Map<String, Object> tags,
LongArrayList nodes
LongArrayList nodes,
@Override Info info
) implements OsmElement {
public Way(long id) {
this(id, new HashMap<>(), new LongArrayList(5));
this(id, new HashMap<>(), new LongArrayList(5), null);
}
public Way(long id, Map<String, Object> tags, LongArrayList nodes) {
this(id, tags, nodes, null);
}
@Override
public int cost() {
return 1 + tags.size() + nodes.size();
return 1 + tags.size() + nodes.size() + (info == null ? 0 : Info.COST);
}
}
@ -149,11 +176,16 @@ public interface OsmElement extends WithTags {
record Relation(
@Override long id,
@Override Map<String, Object> tags,
List<Member> members
List<Member> members,
@Override Info info
) implements OsmElement {
public Relation(long id) {
this(id, new HashMap<>(), new ArrayList<>());
this(id, new HashMap<>(), new ArrayList<>(), null);
}
public Relation(long id, Map<String, Object> tags, List<Member> members) {
this(id, tags, members, null);
}
public Relation {
@ -164,7 +196,7 @@ public interface OsmElement extends WithTags {
@Override
public int cost() {
return 1 + tags.size() + members.size() * 3;
return 1 + tags.size() + members.size() * 3 + (info == null ? 0 : Info.COST);
}
/**
@ -176,4 +208,8 @@ public interface OsmElement extends WithTags {
String role
) {}
}
record Info(long changeset, long timestamp, int userId, int version, String user) {
private static final int COST = 2;
}
}

Wyświetl plik

@ -621,17 +621,20 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate {
* A source feature generated from OSM elements. Inferring the geometry can be expensive, so each subclass is
* constructed with the inputs necessary to create the geometry, but the geometry is constructed lazily on read.
*/
private abstract class OsmFeature extends SourceFeature {
private abstract class OsmFeature extends SourceFeature implements OsmSourceFeature {
private final OsmElement originalElement;
private final boolean polygon;
private final boolean line;
private final boolean point;
private Geometry latLonGeom;
private Geometry worldGeom;
public OsmFeature(OsmElement elem, boolean point, boolean line, boolean polygon,
List<RelationMember<OsmRelationInfo>> relationInfo) {
super(elem.tags(), name, null, relationInfo, elem.id());
this.originalElement = elem;
this.point = point;
this.line = line;
this.polygon = polygon;
@ -663,6 +666,11 @@ public class OsmReader implements Closeable, MemoryEstimator.HasEstimate {
public boolean canBePolygon() {
return polygon;
}
@Override
public OsmElement originalElement() {
return originalElement;
}
}
/** A {@link Point} created from an OSM node. */

Wyświetl plik

@ -0,0 +1,5 @@
package com.onthegomap.planetiler.reader.osm;
public interface OsmSourceFeature {
OsmElement originalElement();
}

Wyświetl plik

@ -149,7 +149,8 @@ public class PbfDecoder implements Iterable<OsmElement> {
node.getId(),
buildTags(node.getKeysCount(), node::getKeys, node::getVals),
fieldDecoder.decodeLatitude(node.getLat()),
fieldDecoder.decodeLongitude(node.getLon())
fieldDecoder.decodeLongitude(node.getLon()),
parseInfo(node.getInfo())
);
}
}
@ -198,7 +199,8 @@ public class PbfDecoder implements Iterable<OsmElement> {
return new OsmElement.Relation(
relation.getId(),
buildTags(relation.getKeysCount(), relation::getKeys, relation::getVals),
members
members,
parseInfo(relation.getInfo())
);
}
}
@ -241,27 +243,40 @@ public class PbfDecoder implements Iterable<OsmElement> {
return new OsmElement.Way(
way.getId(),
buildTags(way.getKeysCount(), way::getKeys, way::getVals),
wayNodesList
wayNodesList,
parseInfo(way.getInfo())
);
}
}
private OsmElement.Info parseInfo(Osmformat.Info info) {
return info == null ? null : new OsmElement.Info(
info.getChangeset(),
info.getTimestamp(),
info.getUid(),
info.getVersion(),
fieldDecoder.decodeString(info.getUserSid())
);
}
private class DenseNodeIterator implements Iterator<OsmElement.Node> {
final Osmformat.DenseNodes nodes;
long nodeId;
long latitude;
long longitude;
int i;
int kvIndex;
final Osmformat.DenseInfo denseInfo;
long nodeId = 0;
long latitude = 0;
long longitude = 0;
int i = 0;
int kvIndex = 0;
// info
long timestamp = 0;
long changeset = 0;
int uid = 0;
int userSid = 0;
public DenseNodeIterator(Osmformat.DenseNodes nodes) {
this.nodes = nodes;
nodeId = 0;
latitude = 0;
longitude = 0;
i = 0;
kvIndex = 0;
this.denseInfo = nodes.getDenseinfo();
}
@ -279,6 +294,16 @@ public class PbfDecoder implements Iterable<OsmElement> {
nodeId += nodes.getId(i);
latitude += nodes.getLat(i);
longitude += nodes.getLon(i);
int version = 0;
if (denseInfo != null) {
version = denseInfo.getVersion(i);
timestamp += denseInfo.getTimestamp(i);
changeset += denseInfo.getChangeset(i);
uid += denseInfo.getUid(i);
userSid += denseInfo.getUserSid(i);
}
i++;
// Build the tags. The key and value string indexes are sequential
@ -304,7 +329,14 @@ public class PbfDecoder implements Iterable<OsmElement> {
nodeId,
tags == null ? Collections.emptyMap() : tags,
((double) latitude) / 10000000,
((double) longitude) / 10000000
((double) longitude) / 10000000,
denseInfo == null ? null : new OsmElement.Info(
changeset,
timestamp,
uid,
version,
fieldDecoder.decodeString(userSid)
)
);
}
}

Wyświetl plik

@ -24,7 +24,7 @@ class OsmInputFileTest {
private final OsmElement.Node expectedNode = new OsmElement.Node(1737114566L, Map.of(
"highway", "crossing",
"crossing", "zebra"
), 43.7409723, 7.4303278);
), 43.7409723, 7.4303278, new OsmElement.Info(0, 1600807207, 0, 5, ""));
private final OsmElement.Way expectedWay = new OsmElement.Way(4097656L, Map.of(
"name", "Avenue Princesse Alice",
"lanes", "2",
@ -35,7 +35,7 @@ class OsmInputFileTest {
), LongArrayList.from(
21912089L, 7265761724L, 1079750744L, 2104793864L, 6340961560L, 1110560507L, 21912093L, 6340961559L, 21912095L,
7265762803L, 2104793866L, 6340961561L, 5603088200L, 6340961562L, 21912097L, 21912099L
));
), new OsmElement.Info(0, 1583398246, 0, 13, ""));
private final OsmElement.Relation expectedRel = new OsmElement.Relation(7360630L, Map.of(
"local_ref", "Saint-Roman",
"public_transport:version", "2",
@ -51,7 +51,7 @@ class OsmInputFileTest {
new OsmElement.Relation.Member(OsmElement.Type.NODE, 3465728159L, "stop"),
new OsmElement.Relation.Member(OsmElement.Type.NODE, 4939122068L, "platform"),
new OsmElement.Relation.Member(OsmElement.Type.NODE, 3805333988L, "stop")
));
), new OsmElement.Info(0, 1586074405, 0, 2, ""));
private final Envelope expectedBounds = new Envelope(7.409205, 7.448637, 43.72335, 43.75169);
@Test

Wyświetl plik

@ -1,11 +1,14 @@
package com.onthegomap.planetiler;
import static java.util.Map.entry;
import com.onthegomap.planetiler.basemap.BasemapMain;
import com.onthegomap.planetiler.basemap.util.VerifyMonaco;
import com.onthegomap.planetiler.benchmarks.BasemapMapping;
import com.onthegomap.planetiler.benchmarks.LongLongMapBench;
import com.onthegomap.planetiler.custommap.ConfiguredMapMain;
import com.onthegomap.planetiler.examples.BikeRouteOverlay;
import com.onthegomap.planetiler.examples.OsmQaTiles;
import com.onthegomap.planetiler.examples.ToiletsOverlay;
import com.onthegomap.planetiler.examples.ToiletsOverlayLowLevelApi;
import com.onthegomap.planetiler.mbtiles.Verify;
@ -20,17 +23,19 @@ import java.util.Map;
public class Main {
private static final EntryPoint DEFAULT_TASK = BasemapMain::main;
private static final Map<String, EntryPoint> ENTRY_POINTS = Map.of(
"generate-basemap", BasemapMain::main,
"generate-custom", ConfiguredMapMain::main,
"basemap", BasemapMain::main,
"example-bikeroutes", BikeRouteOverlay::main,
"example-toilets", ToiletsOverlay::main,
"example-toilets-lowlevel", ToiletsOverlayLowLevelApi::main,
"benchmark-mapping", BasemapMapping::main,
"benchmark-longlongmap", LongLongMapBench::main,
"verify-mbtiles", Verify::main,
"verify-monaco", VerifyMonaco::main
private static final Map<String, EntryPoint> ENTRY_POINTS = Map.ofEntries(
entry("generate-basemap", BasemapMain::main),
entry("generate-custom", ConfiguredMapMain::main),
entry("basemap", BasemapMain::main),
entry("example-bikeroutes", BikeRouteOverlay::main),
entry("example-toilets", ToiletsOverlay::main),
entry("example-toilets-lowlevel", ToiletsOverlayLowLevelApi::main),
entry("example-qa", OsmQaTiles::main),
entry("osm-qa", OsmQaTiles::main),
entry("benchmark-mapping", BasemapMapping::main),
entry("benchmark-longlongmap", LongLongMapBench::main),
entry("verify-mbtiles", Verify::main),
entry("verify-monaco", VerifyMonaco::main)
);
public static void main(String[] args) throws Exception {

Wyświetl plik

@ -0,0 +1,107 @@
package com.onthegomap.planetiler.examples;
import com.onthegomap.planetiler.FeatureCollector;
import com.onthegomap.planetiler.Planetiler;
import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.reader.osm.OsmSourceFeature;
import java.nio.file.Path;
/**
* Generates tiles with a raw copy of OSM data in a single "osm" layer at one zoom level, similar to
* <a href="http://osmlab.github.io/osm-qa-tiles/">OSM QA Tiles</a>.
* <p>
* Nodes are mapped to points and ways are mapped to polygons or linestrings, and multipolygon relations are mapped to
* polygons. Each output feature contains all key/value tags from the input feature, plus these extra attributes:
* <ul>
* <li>{@code @type}: node, way, or relation</li>
* <li>{@code @id}: OSM element ID</li>
* <li>{@code @changeset}: Changeset that last modified the element</li>
* <li>{@code @timestamp}: Timestamp at which the element was last modified</li>
* <li>{@code @version}: Version number of the OSM element</li>
* <li>{@code @uid}: User ID that last modified the element</li>
* <li>{@code @user}: User name that last modified the element</li>
* </ul>
* <p>
* To run this example:
* <ol>
* <li>build the examples: {@code mvn clean package}</li>
* <li>then run this example:
* {@code java -cp target/*-fatjar.jar com.onthegomap.planetiler.examples.OsmQaTiles --area=monaco --download}</li>
* <li>then run the demo tileserver: {@code tileserver-gl-light data/output.mbtiles}</li>
* <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li>
* </ol>
*/
public class OsmQaTiles implements Profile {
public static void main(String[] args) throws Exception {
run(Arguments.fromArgsOrConfigFile(args));
}
static void run(Arguments inArgs) throws Exception {
int zoom = inArgs.getInteger("zoom", "zoom level to generate tiles at", 12);
var args = inArgs.orElse(Arguments.of(
"minzoom", zoom,
"maxzoom", zoom,
"tile_warning_size_mb", 100
));
String area = args.getString("area", "geofabrik area to download", "monaco");
Planetiler.create(args)
.setProfile(new OsmQaTiles())
.addOsmSource("osm",
Path.of("data", "sources", area + ".osm.pbf"),
"planet".equalsIgnoreCase(area) ? "aws:latest" : ("geofabrik:" + area)
)
.overwriteOutput("mbtiles", Path.of("data", "qa.mbtiles"))
.run();
}
@Override
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
if (!sourceFeature.tags().isEmpty() && sourceFeature instanceof OsmSourceFeature osmFeature) {
var feature = sourceFeature.canBePolygon() ? features.polygon("osm") :
sourceFeature.canBeLine() ? features.line("osm") :
sourceFeature.isPoint() ? features.point("osm") :
null;
if (feature != null) {
var element = osmFeature.originalElement();
feature
.setMinPixelSize(0)
.setPixelTolerance(0)
.setBufferPixels(0);
for (var entry : sourceFeature.tags().entrySet()) {
feature.setAttr(entry.getKey(), entry.getValue());
}
feature
.setAttr("@id", sourceFeature.id())
.setAttr("@type", element instanceof OsmElement.Node ? "node" :
element instanceof OsmElement.Way ? "way" :
element instanceof OsmElement.Relation ? "relation" : null
);
var info = element.info();
if (info != null) {
feature
.setAttr("@version", info.version() == 0 ? null : info.version())
.setAttr("@timestamp", info.timestamp() == 0L ? null : info.timestamp())
.setAttr("@changeset", info.changeset() == 0L ? null : info.changeset())
.setAttr("@uid", info.userId() == 0 ? null : info.userId())
.setAttr("@user", info.user() == null || info.user().isBlank() ? null : info.user());
}
}
}
}
@Override
public String name() {
return "osm qa";
}
@Override
public String attribution() {
return """
<a href="https://www.openstreetmap.org/copyright" target="_blank">&copy; OpenStreetMap contributors</a>
""".trim();
}
}

Wyświetl plik

@ -0,0 +1,138 @@
package com.onthegomap.planetiler.examples;
import static com.onthegomap.planetiler.TestUtils.assertContains;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.onthegomap.planetiler.TestUtils;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
import com.onthegomap.planetiler.reader.SourceFeature;
import com.onthegomap.planetiler.reader.osm.OsmElement;
import com.onthegomap.planetiler.reader.osm.OsmSourceFeature;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
class OsmQaTilesTest {
private final OsmQaTiles profile = new OsmQaTiles();
@Test
void testNode() {
Map<String, Object> tags = Map.of("key", "value");
class TestNode extends SourceFeature implements OsmSourceFeature {
TestNode() {
super(tags, "source", "layer", null, 0);
}
@Override
public Geometry latLonGeometry() {
return null;
}
@Override
public Geometry worldGeometry() {
return TestUtils.newPoint(
0.5, 0.5
);
}
@Override
public boolean isPoint() {
return true;
}
@Override
public boolean canBePolygon() {
return false;
}
@Override
public boolean canBeLine() {
return false;
}
@Override
public OsmElement originalElement() {
return new OsmElement.Node(123, tags, 1, 1, new OsmElement.Info(1, 2, 3, 4, "user"));
}
}
var node = new TestNode();
var mapFeatures = TestUtils.processSourceFeature(node, profile);
// verify output attributes
assertEquals(1, mapFeatures.size());
var feature = mapFeatures.get(0);
assertEquals("osm", feature.getLayer());
assertEquals(Map.of(
"key", "value",
"@changeset", 1L,
"@timestamp", 2L,
"@id", 0L,
"@type", "node",
"@uid", 3,
"@user", "user",
"@version", 4
), feature.getAttrsAtZoom(12));
assertEquals(0, feature.getBufferPixelsAtZoom(12), 1e-2);
}
@Test
void integrationTest(@TempDir Path tmpDir) throws Exception {
Path dbPath = tmpDir.resolve("output.mbtiles");
OsmQaTiles.run(Arguments.of(
// Override input source locations
"osm_path", TestUtils.pathToResource("monaco-latest.osm.pbf"),
// Override temp dir location
"tmp", tmpDir.toString(),
// Override output location
"mbtiles", dbPath.toString()
));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("osm qa", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution"));
TestUtils
.assertNumFeatures(mbtiles, "osm", 12, Map.of(
"bus", "yes",
"name", "Crémaillère",
"public_transport", "stop_position",
"@type", "node",
"@version", 4L,
"@id", 1699777833L
), GeoUtils.WORLD_LAT_LON_BOUNDS, 1, Point.class);
TestUtils
.assertNumFeatures(mbtiles, "osm", 12, Map.of(
"boundary", "administrative",
"admin_level", "10",
"name", "Monte-Carlo",
"wikipedia", "fr:Monte-Carlo",
"ISO3166-2", "MC-MC",
"type", "boundary",
"wikidata", "Q45240",
"@type", "relation",
"@version", 9L,
"@id", 5986438L
), GeoUtils.WORLD_LAT_LON_BOUNDS, 1, Polygon.class);
TestUtils
.assertNumFeatures(mbtiles, "osm", 12, Map.of(
"name", "Avenue de la Costa",
"maxspeed", "50",
"lit", "yes",
"surface", "asphalt",
"lanes", "2",
"@type", "way",
"@version", 5L,
"@id", 166009791L
), GeoUtils.WORLD_LAT_LON_BOUNDS, 1, LineString.class);
}
}
}