2021-12-23 10:42:24 +00:00
|
|
|
package com.onthegomap.planetiler;
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
import static com.onthegomap.planetiler.TestUtils.*;
|
2022-03-03 13:52:45 +00:00
|
|
|
import static org.junit.jupiter.api.Assertions.*;
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2023-02-05 19:16:05 +00:00
|
|
|
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
|
|
|
|
import com.onthegomap.planetiler.archive.TileArchiveWriter;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.collection.FeatureGroup;
|
|
|
|
import com.onthegomap.planetiler.collection.LongLongMap;
|
2022-03-23 00:34:54 +00:00
|
|
|
import com.onthegomap.planetiler.collection.LongLongMultimap;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.config.Arguments;
|
|
|
|
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
|
|
|
import com.onthegomap.planetiler.geo.GeoUtils;
|
|
|
|
import com.onthegomap.planetiler.geo.GeometryException;
|
|
|
|
import com.onthegomap.planetiler.geo.TileCoord;
|
2023-01-27 02:43:07 +00:00
|
|
|
import com.onthegomap.planetiler.geo.TileOrder;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.mbtiles.Mbtiles;
|
2023-03-18 18:38:04 +00:00
|
|
|
import com.onthegomap.planetiler.pmtiles.ReadablePmtiles;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.reader.SimpleFeature;
|
|
|
|
import com.onthegomap.planetiler.reader.SimpleReader;
|
|
|
|
import com.onthegomap.planetiler.reader.SourceFeature;
|
2022-12-15 19:19:22 +00:00
|
|
|
import com.onthegomap.planetiler.reader.SourceFeatureProcessor;
|
2022-03-01 01:52:30 +00:00
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmBlockSource;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmElement;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmReader;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmRelationInfo;
|
|
|
|
import com.onthegomap.planetiler.stats.Stats;
|
2023-01-02 16:26:00 +00:00
|
|
|
import com.onthegomap.planetiler.util.BuildInfo;
|
2021-05-08 10:53:37 +00:00
|
|
|
import java.io.IOException;
|
2021-05-23 11:34:47 +00:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
2021-05-31 11:16:44 +00:00
|
|
|
import java.util.ArrayList;
|
2021-05-30 11:42:06 +00:00
|
|
|
import java.util.Comparator;
|
2021-05-31 11:16:44 +00:00
|
|
|
import java.util.HashMap;
|
2022-01-16 15:00:57 +00:00
|
|
|
import java.util.HashSet;
|
2021-05-08 10:53:37 +00:00
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
2022-01-16 15:00:57 +00:00
|
|
|
import java.util.Set;
|
2021-05-30 11:42:06 +00:00
|
|
|
import java.util.TreeMap;
|
2021-08-11 12:40:49 +00:00
|
|
|
import java.util.concurrent.CopyOnWriteArrayList;
|
2021-05-08 10:53:37 +00:00
|
|
|
import java.util.function.BiConsumer;
|
2021-05-28 10:08:13 +00:00
|
|
|
import java.util.function.Consumer;
|
2021-05-08 10:53:37 +00:00
|
|
|
import java.util.function.Function;
|
2021-06-02 11:54:21 +00:00
|
|
|
import java.util.stream.DoubleStream;
|
2021-05-08 10:53:37 +00:00
|
|
|
import org.junit.jupiter.api.Test;
|
2021-08-17 01:51:49 +00:00
|
|
|
import org.junit.jupiter.api.io.TempDir;
|
2021-05-23 11:34:47 +00:00
|
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
|
|
import org.junit.jupiter.params.provider.CsvSource;
|
2022-01-07 15:01:22 +00:00
|
|
|
import org.junit.jupiter.params.provider.ValueSource;
|
2021-05-22 11:07:34 +00:00
|
|
|
import org.locationtech.jts.geom.Coordinate;
|
2021-05-26 00:53:04 +00:00
|
|
|
import org.locationtech.jts.geom.Geometry;
|
2021-05-23 11:34:47 +00:00
|
|
|
import org.locationtech.jts.geom.MultiPolygon;
|
2022-05-24 21:46:56 +00:00
|
|
|
import org.locationtech.jts.geom.Polygon;
|
2021-05-23 11:34:47 +00:00
|
|
|
import org.locationtech.jts.io.InputStreamInStream;
|
|
|
|
import org.locationtech.jts.io.WKBReader;
|
2021-09-18 10:07:44 +00:00
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2021-05-16 10:42:57 +00:00
|
|
|
/**
|
|
|
|
* In-memory tests with fake data and profiles to ensure all features work end-to-end.
|
|
|
|
*/
|
2022-04-23 10:36:24 +00:00
|
|
|
class PlanetilerTests {
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(PlanetilerTests.class);
|
2021-09-18 10:07:44 +00:00
|
|
|
|
2021-05-13 10:25:06 +00:00
|
|
|
private static final String TEST_PROFILE_NAME = "test name";
|
|
|
|
private static final String TEST_PROFILE_DESCRIPTION = "test description";
|
|
|
|
private static final String TEST_PROFILE_ATTRIBUTION = "test attribution";
|
|
|
|
private static final String TEST_PROFILE_VERSION = "test version";
|
2022-07-26 11:51:31 +00:00
|
|
|
private static final int Z15_TILES = 1 << 15;
|
|
|
|
private static final double Z15_WIDTH = 1d / Z15_TILES;
|
2021-05-13 10:25:06 +00:00
|
|
|
private static final int Z14_TILES = 1 << 14;
|
|
|
|
private static final double Z14_WIDTH = 1d / Z14_TILES;
|
|
|
|
private static final int Z13_TILES = 1 << 13;
|
|
|
|
private static final double Z13_WIDTH = 1d / Z13_TILES;
|
2021-05-22 11:07:34 +00:00
|
|
|
private static final int Z12_TILES = 1 << 12;
|
2021-05-23 11:34:47 +00:00
|
|
|
private static final int Z4_TILES = 1 << 4;
|
2022-05-24 21:46:56 +00:00
|
|
|
private static final Polygon WORLD_POLYGON = newPolygon(
|
|
|
|
worldCoordinateList(
|
|
|
|
Z14_WIDTH / 2, Z14_WIDTH / 2,
|
|
|
|
1 - Z14_WIDTH / 2, Z14_WIDTH / 2,
|
|
|
|
1 - Z14_WIDTH / 2, 1 - Z14_WIDTH / 2,
|
|
|
|
Z14_WIDTH / 2, 1 - Z14_WIDTH / 2,
|
|
|
|
Z14_WIDTH / 2, Z14_WIDTH / 2
|
|
|
|
),
|
|
|
|
List.of()
|
|
|
|
);
|
2021-08-05 01:22:20 +00:00
|
|
|
private final Stats stats = Stats.inMemory();
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2022-05-24 22:46:52 +00:00
|
|
|
@TempDir
|
|
|
|
Path tempDir;
|
2022-05-24 21:46:56 +00:00
|
|
|
|
2022-03-01 01:52:30 +00:00
|
|
|
private static <T extends OsmElement> T with(T elem, Consumer<T> fn) {
|
2021-05-31 10:21:53 +00:00
|
|
|
fn.accept(elem);
|
|
|
|
return elem;
|
|
|
|
}
|
|
|
|
|
2022-12-15 19:19:22 +00:00
|
|
|
private <F extends SourceFeature> void processReaderFeatures(FeatureGroup featureGroup, Profile profile,
|
|
|
|
PlanetilerConfig config, List<F> features) {
|
|
|
|
SourceFeatureProcessor.processFiles(
|
|
|
|
"test",
|
|
|
|
List.of(Path.of("mock-path")), path -> new SimpleReader<F>("test") {
|
|
|
|
@Override
|
|
|
|
public long getFeatureCount() {
|
|
|
|
return features.size();
|
|
|
|
}
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2022-12-15 19:19:22 +00:00
|
|
|
@Override
|
|
|
|
public void readFeatures(Consumer<F> next) {
|
|
|
|
features.forEach(next);
|
|
|
|
}
|
2021-05-08 10:53:37 +00:00
|
|
|
|
2022-12-15 19:19:22 +00:00
|
|
|
@Override
|
|
|
|
public void close() { /* pass */ }
|
|
|
|
}, featureGroup, config, profile, stats
|
|
|
|
);
|
2021-05-08 10:53:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private void processOsmFeatures(FeatureGroup featureGroup, Profile profile, PlanetilerConfig config,
|
2022-03-01 01:52:30 +00:00
|
|
|
List<? extends OsmElement> osmElements) throws IOException {
|
|
|
|
OsmBlockSource elems = next -> {
|
2021-05-28 10:08:13 +00:00
|
|
|
// process the same order they come in from an OSM file
|
2022-03-01 01:52:30 +00:00
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Other).toList()));
|
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Node).toList()));
|
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Way).toList()));
|
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Relation).toList()));
|
2021-05-28 10:08:13 +00:00
|
|
|
};
|
|
|
|
var nodeMap = LongLongMap.newInMemorySortedTable();
|
2022-03-23 00:34:54 +00:00
|
|
|
var multipolygons = LongLongMultimap.newInMemoryReplaceableMultimap();
|
|
|
|
try (var reader = new OsmReader("osm", () -> elems, nodeMap, multipolygons, profile, Stats.inMemory())) {
|
2021-05-28 10:08:13 +00:00
|
|
|
reader.pass1(config);
|
|
|
|
reader.pass2(featureGroup, config);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults run(
|
2021-05-13 10:25:06 +00:00
|
|
|
Map<String, String> args,
|
2021-05-28 10:08:13 +00:00
|
|
|
Runner runner,
|
2021-05-31 10:21:53 +00:00
|
|
|
Profile profile
|
2021-05-28 10:08:13 +00:00
|
|
|
) throws Exception {
|
2021-12-23 10:42:24 +00:00
|
|
|
PlanetilerConfig config = PlanetilerConfig.from(Arguments.of(args));
|
2023-01-27 02:43:07 +00:00
|
|
|
FeatureGroup featureGroup = FeatureGroup.newInMemoryFeatureGroup(TileOrder.TMS, profile, stats);
|
2021-05-28 10:08:13 +00:00
|
|
|
runner.run(featureGroup, profile, config);
|
2021-09-10 00:46:20 +00:00
|
|
|
featureGroup.prepare();
|
2023-03-18 18:38:04 +00:00
|
|
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase(config.arguments())) {
|
|
|
|
TileArchiveWriter.writeOutput(featureGroup, db, () -> 0L, new TileArchiveMetadata(profile, config),
|
2023-01-17 12:05:45 +00:00
|
|
|
config,
|
2021-09-18 01:12:24 +00:00
|
|
|
stats);
|
2021-05-22 11:07:34 +00:00
|
|
|
var tileMap = TestUtils.getTileMap(db);
|
2021-09-10 00:46:20 +00:00
|
|
|
tileMap.values().forEach(fs -> fs.forEach(f -> f.geometry().validate()));
|
2023-03-18 18:38:04 +00:00
|
|
|
int tileDataCount = db.compactDb() ? TestUtils.getTilesDataCount(db) : 0;
|
|
|
|
return new PlanetilerResults(tileMap, db.metadata().toMap(), tileDataCount);
|
2021-05-08 10:53:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithReaderFeatures(
|
2021-05-28 10:08:13 +00:00
|
|
|
Map<String, String> args,
|
2021-09-10 00:46:20 +00:00
|
|
|
List<SimpleFeature> features,
|
2021-05-28 10:08:13 +00:00
|
|
|
BiConsumer<SourceFeature, FeatureCollector> profileFunction
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processReaderFeatures(featureGroup, profile, config, features),
|
2021-05-31 10:21:53 +00:00
|
|
|
TestProfile.processSourceFeatures(profileFunction)
|
2021-05-28 10:08:13 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithReaderFeatures(
|
2021-05-31 11:16:44 +00:00
|
|
|
Map<String, String> args,
|
2021-09-10 00:46:20 +00:00
|
|
|
List<SimpleFeature> features,
|
2021-05-31 11:16:44 +00:00
|
|
|
BiConsumer<SourceFeature, FeatureCollector> profileFunction,
|
|
|
|
LayerPostprocessFunction postProcess
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processReaderFeatures(featureGroup, profile, config, features),
|
|
|
|
new TestProfile(profileFunction, a -> null, postProcess)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithReaderFeaturesProfile(
|
2021-06-23 01:46:42 +00:00
|
|
|
Map<String, String> args,
|
2021-09-10 00:46:20 +00:00
|
|
|
List<SimpleFeature> features,
|
2021-06-23 01:46:42 +00:00
|
|
|
Profile profileToUse
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processReaderFeatures(featureGroup, profile, config, features),
|
|
|
|
profileToUse
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithOsmElements(
|
2021-05-28 10:08:13 +00:00
|
|
|
Map<String, String> args,
|
2022-03-01 01:52:30 +00:00
|
|
|
List<OsmElement> features,
|
2021-05-28 10:08:13 +00:00
|
|
|
BiConsumer<SourceFeature, FeatureCollector> profileFunction
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
2021-05-31 10:21:53 +00:00
|
|
|
TestProfile.processSourceFeatures(profileFunction)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithOsmElements(
|
2021-06-23 01:46:42 +00:00
|
|
|
Map<String, String> args,
|
2022-03-01 01:52:30 +00:00
|
|
|
List<OsmElement> features,
|
2021-06-23 01:46:42 +00:00
|
|
|
Profile profileToUse
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
|
|
|
profileToUse
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private PlanetilerResults runWithOsmElements(
|
2021-05-31 10:21:53 +00:00
|
|
|
Map<String, String> args,
|
2022-03-01 01:52:30 +00:00
|
|
|
List<OsmElement> features,
|
2021-09-10 00:46:20 +00:00
|
|
|
Function<OsmElement.Relation, List<OsmRelationInfo>> preprocessOsmRelation,
|
2021-05-31 10:21:53 +00:00
|
|
|
BiConsumer<SourceFeature, FeatureCollector> profileFunction
|
|
|
|
) throws Exception {
|
|
|
|
return run(
|
|
|
|
args,
|
|
|
|
(featureGroup, profile, config) -> processOsmFeatures(featureGroup, profile, config, features),
|
2021-06-01 10:29:55 +00:00
|
|
|
new TestProfile(profileFunction, preprocessOsmRelation, (a, b, c) -> c)
|
2021-05-28 10:08:13 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
private SimpleFeature newReaderFeature(Geometry geometry, Map<String, Object> attrs) {
|
|
|
|
return SimpleFeature.create(geometry, attrs);
|
2021-06-07 11:46:03 +00:00
|
|
|
}
|
|
|
|
|
2021-05-13 10:25:06 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMetadataButNoPoints() throws Exception {
|
2021-05-13 10:25:06 +00:00
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(),
|
|
|
|
(sourceFeature, features) -> {
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assertEquals(Map.of(), results.tiles);
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
"name", TEST_PROFILE_NAME,
|
|
|
|
"description", TEST_PROFILE_DESCRIPTION,
|
|
|
|
"attribution", TEST_PROFILE_ATTRIBUTION,
|
|
|
|
"version", TEST_PROFILE_VERSION,
|
|
|
|
"type", "baselayer",
|
|
|
|
"format", "pbf",
|
|
|
|
"minzoom", "0",
|
|
|
|
"maxzoom", "14",
|
2023-03-18 18:38:04 +00:00
|
|
|
"center", "0,0",
|
2021-05-13 10:25:06 +00:00
|
|
|
"bounds", "-180,-85.05113,180,85.05113"
|
|
|
|
), results.metadata);
|
2023-01-02 16:26:00 +00:00
|
|
|
assertSubmap(Map.of(
|
|
|
|
"planetiler:version", BuildInfo.get().version()
|
|
|
|
), results.metadata);
|
2021-05-13 10:25:06 +00:00
|
|
|
assertSameJson(
|
2023-03-18 18:38:04 +00:00
|
|
|
"[]",
|
|
|
|
results.metadata.get("vector_layers")
|
2021-05-13 10:25:06 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-18 01:12:24 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOverrideMetadata() throws Exception {
|
2021-09-18 01:12:24 +00:00
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of(
|
2023-03-18 18:38:04 +00:00
|
|
|
"archive_name", "override_name",
|
|
|
|
"archive_description", "override_description",
|
|
|
|
"archive_attribution", "override_attribution",
|
|
|
|
"archive_version", "override_version",
|
|
|
|
"archive_type", "override_type"
|
2021-09-18 01:12:24 +00:00
|
|
|
),
|
|
|
|
List.of(),
|
|
|
|
(sourceFeature, features) -> {
|
|
|
|
}
|
|
|
|
);
|
|
|
|
assertEquals(Map.of(), results.tiles);
|
|
|
|
assertSubmap(Map.of(
|
2023-01-17 12:05:45 +00:00
|
|
|
"name", "override_name",
|
|
|
|
"description", "override_description",
|
|
|
|
"attribution", "override_attribution",
|
|
|
|
"version", "override_version",
|
|
|
|
"type", "override_type"
|
2021-09-18 01:12:24 +00:00
|
|
|
), results.metadata);
|
|
|
|
}
|
|
|
|
|
2021-05-13 10:25:06 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testSinglePoint() throws Exception {
|
2022-07-26 11:51:31 +00:00
|
|
|
double x = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double y = 0.5 + Z14_WIDTH / 4;
|
2021-05-13 10:25:06 +00:00
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
double lng = GeoUtils.getWorldLon(x);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
2022-07-26 11:51:31 +00:00
|
|
|
Map.of("threads", "1", "maxzoom", "15"),
|
2021-05-13 10:25:06 +00:00
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPoint(lng, lat), Map.of(
|
2021-05-13 10:25:06 +00:00
|
|
|
"attr", "value"
|
|
|
|
))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.point("layer")
|
2022-07-26 11:51:31 +00:00
|
|
|
.setZoomRange(13, 15)
|
2021-09-10 00:46:20 +00:00
|
|
|
.setAttr("name", "name value")
|
|
|
|
.inheritAttrFromSource("attr")
|
2021-05-13 10:25:06 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
2022-07-26 11:51:31 +00:00
|
|
|
TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15), List.of(
|
2021-05-13 10:25:06 +00:00
|
|
|
feature(newPoint(128, 128), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
),
|
2022-07-26 11:51:31 +00:00
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
2021-05-13 10:25:06 +00:00
|
|
|
feature(newPoint(64, 64), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
2022-07-26 11:51:31 +00:00
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
feature(newPoint(32, 32), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
2021-05-13 10:25:06 +00:00
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
assertSameJson(
|
|
|
|
"""
|
2023-03-18 18:38:04 +00:00
|
|
|
[
|
|
|
|
{"id": "layer", "fields": {"name": "String", "attr": "String"}, "minzoom": 13, "maxzoom": 15}
|
|
|
|
]
|
2021-05-13 10:25:06 +00:00
|
|
|
""",
|
2023-03-18 18:38:04 +00:00
|
|
|
results.metadata.get("vector_layers")
|
2021-05-13 10:25:06 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-16 10:53:37 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMultiPoint() throws Exception {
|
2021-05-16 10:53:37 +00:00
|
|
|
double x1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double y1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double x2 = x1 + Z13_WIDTH / 256d;
|
|
|
|
double y2 = y1 + Z13_WIDTH / 256d;
|
|
|
|
double lat1 = GeoUtils.getWorldLat(y1);
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lat2 = GeoUtils.getWorldLat(y2);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x2);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newMultiPoint(
|
2021-05-16 10:53:37 +00:00
|
|
|
newPoint(lng1, lat1),
|
|
|
|
newPoint(lng2, lat2)
|
|
|
|
), Map.of(
|
|
|
|
"attr", "value"
|
|
|
|
))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.setAttr("name", "name value")
|
|
|
|
.inheritAttrFromSource("attr")
|
2021-05-16 10:53:37 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newMultiPoint(
|
|
|
|
newPoint(128, 128),
|
|
|
|
newPoint(130, 130)
|
|
|
|
), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
feature(newMultiPoint(
|
|
|
|
newPoint(64, 64),
|
|
|
|
newPoint(65, 65)
|
|
|
|
), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-05-16 10:42:57 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testLabelGridLimit() throws Exception {
|
2021-05-16 10:42:57 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
double lng3 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 20d / 256);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPoint(lng1, lat), Map.of("rank", "1")),
|
|
|
|
newReaderFeature(newPoint(lng2, lat), Map.of("rank", "2")),
|
|
|
|
newReaderFeature(newPoint(lng3, lat), Map.of("rank", "3"))
|
2021-05-16 10:42:57 +00:00
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.inheritAttrFromSource("rank")
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(Integer.parseInt(in.getTag("rank").toString()))
|
2022-01-10 11:41:15 +00:00
|
|
|
.setPointLabelGridSizeAndLimit(13, 128, 2)
|
2021-09-10 00:46:20 +00:00
|
|
|
.setBufferPixels(128)
|
2021-05-16 10:42:57 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newPoint(64, 128), Map.of("rank", "1")),
|
|
|
|
feature(newPoint(74, 128), Map.of("rank", "2")),
|
|
|
|
feature(newPoint(84, 128), Map.of("rank", "3"))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
2021-09-18 00:18:06 +00:00
|
|
|
// omit rank=3 due to label grid size
|
|
|
|
feature(newPoint(32, 64), Map.of("rank", "1")),
|
|
|
|
feature(newPoint(37, 64), Map.of("rank", "2"))
|
2021-05-16 10:42:57 +00:00
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-05-18 10:53:12 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testLineString() throws Exception {
|
2021-05-18 10:53:12 +00:00
|
|
|
double x1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double y1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double x2 = x1 + Z14_WIDTH;
|
|
|
|
double y2 = y1 + Z14_WIDTH;
|
|
|
|
double lat1 = GeoUtils.getWorldLat(y1);
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lat2 = GeoUtils.getWorldLat(y2);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x2);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newLineString(lng1, lat1, lng2, lat2), Map.of(
|
2021-05-18 10:53:12 +00:00
|
|
|
"attr", "value"
|
|
|
|
))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.line("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.setBufferPixels(4)
|
2021-05-18 10:53:12 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newLineString(128, 128, 260, 260), Map.of())
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of(
|
|
|
|
feature(newLineString(-4, -4, 128, 128), Map.of())
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
feature(newLineString(64, 64, 192, 192), Map.of())
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-06-25 11:06:55 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testNumPointsAttr() throws Exception {
|
2021-06-25 11:06:55 +00:00
|
|
|
double x1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double y1 = 0.5 + Z14_WIDTH / 2 - Z14_WIDTH / 2;
|
|
|
|
double x2 = x1 + Z14_WIDTH;
|
|
|
|
double y2 = y1 + Z14_WIDTH + Z14_WIDTH / 2;
|
|
|
|
double x3 = x2 + Z14_WIDTH;
|
|
|
|
double y3 = y2 + Z14_WIDTH;
|
|
|
|
double lat1 = GeoUtils.getWorldLat(y1);
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lat2 = GeoUtils.getWorldLat(y2);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x2);
|
|
|
|
double lat3 = GeoUtils.getWorldLat(y3);
|
|
|
|
double lng3 = GeoUtils.getWorldLon(x3);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(newLineString(lng1, lat1, lng2, lat2, lng3, lat3), Map.of(
|
|
|
|
"attr", "value"
|
|
|
|
))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.line("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.setBufferPixels(4)
|
|
|
|
.setNumPointsAttr("_numpoints")
|
2021-06-25 11:06:55 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2 + 2, Z14_TILES / 2 + 2, 14), List.of(
|
|
|
|
feature(newLineString(-4, -4, 128, 128), Map.of(
|
|
|
|
"_numpoints", 3L
|
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-05-22 11:07:34 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMultiLineString() throws Exception {
|
2021-05-22 11:07:34 +00:00
|
|
|
double x1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double y1 = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double x2 = x1 + Z14_WIDTH;
|
|
|
|
double y2 = y1 + Z14_WIDTH;
|
|
|
|
double lat1 = GeoUtils.getWorldLat(y1);
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lat2 = GeoUtils.getWorldLat(y2);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x2);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newMultiLineString(
|
2021-05-22 11:07:34 +00:00
|
|
|
newLineString(lng1, lat1, lng2, lat2),
|
|
|
|
newLineString(lng2, lat2, lng1, lat1)
|
|
|
|
), Map.of(
|
|
|
|
"attr", "value"
|
|
|
|
))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.line("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.setBufferPixels(4)
|
2021-05-22 11:07:34 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newMultiLineString(
|
|
|
|
newLineString(128, 128, 260, 260),
|
|
|
|
newLineString(260, 260, 128, 128)
|
|
|
|
), Map.of())
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14), List.of(
|
|
|
|
feature(newMultiLineString(
|
|
|
|
newLineString(-4, -4, 128, 128),
|
|
|
|
newLineString(128, 128, -4, -4)
|
|
|
|
), Map.of())
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
feature(newMultiLineString(
|
|
|
|
newLineString(64, 64, 192, 192),
|
|
|
|
newLineString(192, 192, 64, 64)
|
|
|
|
), Map.of())
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-05-26 00:53:04 +00:00
|
|
|
public List<Coordinate> z14CoordinateList(double... coords) {
|
2021-05-22 11:07:34 +00:00
|
|
|
List<Coordinate> points = newCoordinateList(coords);
|
|
|
|
points.forEach(c -> {
|
2021-05-23 11:34:47 +00:00
|
|
|
c.x = GeoUtils.getWorldLon(0.5 + c.x * Z14_WIDTH);
|
|
|
|
c.y = GeoUtils.getWorldLat(0.5 + c.y * Z14_WIDTH);
|
2021-05-22 11:07:34 +00:00
|
|
|
});
|
|
|
|
return points;
|
|
|
|
}
|
|
|
|
|
2021-06-02 11:54:21 +00:00
|
|
|
public List<Coordinate> z14CoordinatePixelList(double... coords) {
|
|
|
|
return z14CoordinateList(DoubleStream.of(coords).map(c -> c / 256d).toArray());
|
|
|
|
}
|
|
|
|
|
2021-05-22 11:07:34 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testPolygonWithHoleSpanningMultipleTiles() throws Exception {
|
2021-05-22 11:07:34 +00:00
|
|
|
List<Coordinate> outerPoints = z14CoordinateList(
|
|
|
|
0.5, 0.5,
|
|
|
|
3.5, 0.5,
|
|
|
|
3.5, 2.5,
|
|
|
|
0.5, 2.5,
|
|
|
|
0.5, 0.5
|
|
|
|
);
|
|
|
|
List<Coordinate> innerPoints = z14CoordinateList(
|
|
|
|
1.25, 1.25,
|
|
|
|
1.75, 1.25,
|
|
|
|
1.75, 1.75,
|
|
|
|
1.25, 1.75,
|
|
|
|
1.25, 1.25
|
|
|
|
);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPolygon(
|
2021-05-22 11:07:34 +00:00
|
|
|
outerPoints,
|
|
|
|
List.of(innerPoints)
|
|
|
|
), Map.of())
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(12, 14)
|
|
|
|
.setBufferPixels(4)
|
2021-05-22 11:07:34 +00:00
|
|
|
);
|
|
|
|
|
2021-05-23 11:34:47 +00:00
|
|
|
assertEquals(Map.ofEntries(
|
|
|
|
// Z12
|
|
|
|
newTileEntry(Z12_TILES / 2, Z12_TILES / 2, 12, List.of(
|
|
|
|
feature(newPolygon(
|
|
|
|
rectangleCoordList(32, 32, 256 - 32, 128 + 32),
|
|
|
|
List.of(
|
|
|
|
rectangleCoordList(64 + 16, 128 - 16) // hole
|
|
|
|
)
|
|
|
|
), Map.of())
|
|
|
|
)),
|
|
|
|
|
|
|
|
// Z13
|
|
|
|
newTileEntry(Z13_TILES / 2, Z13_TILES / 2, 13, List.of(
|
|
|
|
feature(newPolygon(
|
|
|
|
rectangleCoordList(64, 256 + 4),
|
|
|
|
List.of(rectangleCoordList(128 + 32, 256 - 32)) // hole
|
|
|
|
), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z13_TILES / 2 + 1, Z13_TILES / 2, 13, List.of(
|
|
|
|
feature(rectangle(-4, 64, 256 - 64, 256 + 4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z13_TILES / 2, Z13_TILES / 2 + 1, 13, List.of(
|
|
|
|
feature(rectangle(64, -4, 256 + 4, 64), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z13_TILES / 2 + 1, Z13_TILES / 2 + 1, 13, List.of(
|
|
|
|
feature(rectangle(-4, -4, 256 - 64, 64), Map.of())
|
|
|
|
)),
|
|
|
|
|
2021-05-22 11:07:34 +00:00
|
|
|
// Z14 - row 1
|
|
|
|
newTileEntry(Z14_TILES / 2, Z14_TILES / 2, 14, List.of(
|
|
|
|
feature(tileBottomRight(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2, 14, List.of(
|
|
|
|
feature(tileBottom(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2, 14, List.of(
|
|
|
|
feature(tileBottom(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2, 14, List.of(
|
|
|
|
feature(tileBottomLeft(4), Map.of())
|
|
|
|
)),
|
|
|
|
// Z14 - row 2
|
|
|
|
newTileEntry(Z14_TILES / 2, Z14_TILES / 2 + 1, 14, List.of(
|
|
|
|
feature(tileRight(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 1, 14, List.of(
|
|
|
|
feature(newPolygon(
|
2021-05-27 09:54:45 +00:00
|
|
|
tileFill(4),
|
2021-05-22 11:07:34 +00:00
|
|
|
List.of(newCoordinateList(
|
|
|
|
64, 64,
|
|
|
|
192, 64,
|
|
|
|
192, 192,
|
|
|
|
64, 192,
|
|
|
|
64, 64
|
|
|
|
))
|
|
|
|
), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2 + 1, 14, List.of(
|
|
|
|
feature(newPolygon(tileFill(5), List.of()), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2 + 1, 14, List.of(
|
|
|
|
feature(tileLeft(4), Map.of())
|
|
|
|
)),
|
|
|
|
// Z14 - row 3
|
|
|
|
newTileEntry(Z14_TILES / 2, Z14_TILES / 2 + 2, 14, List.of(
|
|
|
|
feature(tileTopRight(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 1, Z14_TILES / 2 + 2, 14, List.of(
|
|
|
|
feature(tileTop(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2 + 2, 14, List.of(
|
|
|
|
feature(tileTop(4), Map.of())
|
|
|
|
)),
|
|
|
|
newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2 + 2, 14, List.of(
|
|
|
|
feature(tileTopLeft(4), Map.of())
|
|
|
|
))
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2022-07-26 11:51:31 +00:00
|
|
|
@Test
|
|
|
|
void testZ15Fill() throws Exception {
|
|
|
|
List<Coordinate> outerPoints = z14CoordinateList(
|
|
|
|
-2, -2,
|
|
|
|
2, -2,
|
|
|
|
2, 2,
|
|
|
|
-2, 2,
|
|
|
|
-2, -2
|
|
|
|
);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1", "maxzoom", "15"),
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(newPolygon(
|
|
|
|
outerPoints
|
|
|
|
), Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(15, 15)
|
|
|
|
.setBufferPixels(4)
|
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(List.of(
|
|
|
|
feature(newPolygon(tileFill(5)), Map.of())
|
|
|
|
), results.tiles.get(TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15)));
|
|
|
|
}
|
|
|
|
|
2021-05-23 11:34:47 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testFullWorldPolygon() throws Exception {
|
2021-05-23 11:34:47 +00:00
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-05-24 21:46:56 +00:00
|
|
|
newReaderFeature(WORLD_POLYGON, Map.of())
|
2021-05-23 11:34:47 +00:00
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(0, 6)
|
|
|
|
.setBufferPixels(4)
|
2021-05-23 11:34:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(5461, results.tiles.size());
|
|
|
|
// spot-check one filled tile
|
|
|
|
assertEquals(List.of(rectangle(-5, 256 + 5).norm()), results.tiles.get(TileCoord.ofXYZ(
|
|
|
|
Z4_TILES / 2, Z4_TILES / 2, 4
|
|
|
|
)).stream().map(d -> d.geometry().geom().norm()).toList());
|
|
|
|
}
|
|
|
|
|
2022-06-04 01:04:17 +00:00
|
|
|
@Test
|
|
|
|
void testSkipFill() throws Exception {
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1", "skip-filled-tiles", "true"),
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(WORLD_POLYGON, Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(0, 6)
|
|
|
|
.setBufferPixels(4)
|
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(481, results.tiles.size());
|
|
|
|
// spot-check one filled tile does not exist
|
|
|
|
assertNull(results.tiles.get(TileCoord.ofXYZ(
|
|
|
|
Z4_TILES / 2, Z4_TILES / 2, 4
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
2021-05-23 11:34:47 +00:00
|
|
|
@ParameterizedTest
|
|
|
|
@CsvSource({
|
2021-05-23 18:24:19 +00:00
|
|
|
"chesapeake.wkb, 4076",
|
2021-05-23 11:34:47 +00:00
|
|
|
"mdshore.wkb, 19904",
|
2023-03-20 20:41:18 +00:00
|
|
|
"njshore.wkb, 10571",
|
|
|
|
"kobroor.wkb, 21693"
|
2021-05-23 11:34:47 +00:00
|
|
|
})
|
2022-04-23 10:36:24 +00:00
|
|
|
void testComplexShorelinePolygons__TAKES_A_MINUTE_OR_TWO(String fileName, int expected)
|
2021-06-02 11:54:21 +00:00
|
|
|
throws Exception {
|
2021-09-18 10:07:44 +00:00
|
|
|
LOGGER.warn("Testing complex shoreline processing for " + fileName + " ...");
|
2023-03-20 20:41:18 +00:00
|
|
|
Geometry geometry = new WKBReader()
|
2021-08-17 01:51:49 +00:00
|
|
|
.read(new InputStreamInStream(Files.newInputStream(TestUtils.pathToResource(fileName))));
|
2021-05-23 11:34:47 +00:00
|
|
|
assertNotNull(geometry);
|
|
|
|
|
|
|
|
// automatically checks for self-intersections
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(geometry, Map.of())
|
2021-05-23 11:34:47 +00:00
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(0, 14)
|
|
|
|
.setBufferPixels(4)
|
2021-05-23 11:34:47 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(expected, results.tiles.size());
|
|
|
|
}
|
|
|
|
|
2021-05-26 00:53:04 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testReorderNestedMultipolygons() throws Exception {
|
2021-05-26 00:53:04 +00:00
|
|
|
List<Coordinate> outerPoints1 = worldRectangle(10d / 256, 240d / 256);
|
|
|
|
List<Coordinate> innerPoints1 = worldRectangle(20d / 256, 230d / 256);
|
|
|
|
List<Coordinate> outerPoints2 = worldRectangle(30d / 256, 220d / 256);
|
|
|
|
List<Coordinate> innerPoints2 = worldRectangle(40d / 256, 210d / 256);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newMultiPolygon(
|
2021-05-26 00:53:04 +00:00
|
|
|
newPolygon(outerPoints2, List.of(innerPoints2)),
|
|
|
|
newPolygon(outerPoints1, List.of(innerPoints1))
|
|
|
|
), Map.of())
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setBufferPixels(0)
|
2021-05-26 00:53:04 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
var tileContents = results.tiles.get(TileCoord.ofXYZ(0, 0, 0));
|
|
|
|
assertEquals(1, tileContents.size());
|
|
|
|
Geometry geom = tileContents.get(0).geometry().geom();
|
|
|
|
assertTrue(geom instanceof MultiPolygon, geom.toString());
|
|
|
|
MultiPolygon multiPolygon = (MultiPolygon) geom;
|
|
|
|
assertSameNormalizedFeature(newPolygon(
|
|
|
|
rectangleCoordList(10, 240),
|
|
|
|
List.of(rectangleCoordList(20, 230))
|
|
|
|
), multiPolygon.getGeometryN(0));
|
|
|
|
assertSameNormalizedFeature(newPolygon(
|
|
|
|
rectangleCoordList(30, 220),
|
|
|
|
List.of(rectangleCoordList(40, 210))
|
|
|
|
), multiPolygon.getGeometryN(1));
|
|
|
|
assertEquals(2, multiPolygon.getNumGeometries());
|
|
|
|
}
|
|
|
|
|
2021-05-28 10:08:13 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmPoint() throws Exception {
|
2021-05-28 10:08:13 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, 0, 0), t -> t.setTag("attr", "value"))
|
2021-05-28 10:08:13 +00:00
|
|
|
),
|
|
|
|
(in, features) -> {
|
|
|
|
if (in.isPoint()) {
|
|
|
|
features.point("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr");
|
2021-05-28 10:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newPoint(128, 128), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-08-14 09:55:00 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmPointSkipPass1() throws Exception {
|
2021-08-14 09:55:00 +00:00
|
|
|
var results = run(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
(featureGroup, profile, config) -> {
|
2022-03-01 01:52:30 +00:00
|
|
|
List<? extends OsmElement> osmElements = List.<OsmElement>of(
|
|
|
|
with(new OsmElement.Node(1, 0, 0), t -> t.setTag("attr", "value"))
|
2021-08-14 09:55:00 +00:00
|
|
|
);
|
2022-03-01 01:52:30 +00:00
|
|
|
OsmBlockSource elems = next -> {
|
2021-08-14 09:55:00 +00:00
|
|
|
// process the same order they come in from an OSM file
|
2022-03-01 01:52:30 +00:00
|
|
|
next.accept(
|
|
|
|
OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Other).toList()));
|
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Node).toList()));
|
|
|
|
next.accept(OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Way).toList()));
|
|
|
|
next.accept(
|
|
|
|
OsmBlockSource.Block.of(osmElements.stream().filter(e -> e instanceof OsmElement.Relation).toList()));
|
2021-08-14 09:55:00 +00:00
|
|
|
};
|
|
|
|
var nodeMap = LongLongMap.newInMemorySortedTable();
|
2022-03-23 00:34:54 +00:00
|
|
|
var multipolygons = LongLongMultimap.newInMemoryReplaceableMultimap();
|
|
|
|
try (var reader = new OsmReader("osm", () -> elems, nodeMap, multipolygons, profile, Stats.inMemory())) {
|
2021-08-14 09:55:00 +00:00
|
|
|
// skip pass 1
|
|
|
|
reader.pass2(featureGroup, config);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
TestProfile.processSourceFeatures((in, features) -> {
|
|
|
|
if (in.isPoint()) {
|
|
|
|
features.point("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr");
|
2021-08-14 09:55:00 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newPoint(128, 128), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-08-05 11:02:35 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testExceptionWhileProcessingOsm() {
|
2021-08-05 11:02:35 +00:00
|
|
|
assertThrows(RuntimeException.class, () -> runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, 0, 0), t -> t.setTag("attr", "value"))
|
2021-08-05 11:02:35 +00:00
|
|
|
),
|
|
|
|
(in, features) -> {
|
2022-10-04 23:57:59 +00:00
|
|
|
throw new ExpectedError();
|
2021-08-05 11:02:35 +00:00
|
|
|
}
|
|
|
|
));
|
|
|
|
}
|
|
|
|
|
2021-05-28 10:08:13 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmLine() throws Exception {
|
2021-05-28 10:08:13 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
new OsmElement.Node(1, 0, 0),
|
|
|
|
new OsmElement.Node(2, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.75)),
|
|
|
|
with(new OsmElement.Way(3), way -> {
|
2021-05-28 10:08:13 +00:00
|
|
|
way.setTag("attr", "value");
|
2022-03-01 01:52:30 +00:00
|
|
|
way.nodes().add(1, 2);
|
2021-05-28 10:08:13 +00:00
|
|
|
})
|
|
|
|
),
|
|
|
|
(in, features) -> {
|
|
|
|
if (in.canBeLine()) {
|
|
|
|
features.line("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr");
|
2021-05-28 10:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newLineString(128, 128, 192, 192), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value"
|
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmLineOrPolygon() throws Exception {
|
2021-05-28 10:08:13 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
new OsmElement.Node(1, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.25)),
|
|
|
|
new OsmElement.Node(2, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.75)),
|
|
|
|
new OsmElement.Node(3, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.75)),
|
|
|
|
new OsmElement.Node(4, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.25)),
|
|
|
|
with(new OsmElement.Way(6), way -> {
|
2021-05-28 10:08:13 +00:00
|
|
|
way.setTag("attr", "value");
|
2022-03-01 01:52:30 +00:00
|
|
|
way.nodes().add(1, 2, 3, 4, 1);
|
2021-05-28 10:08:13 +00:00
|
|
|
})
|
|
|
|
),
|
|
|
|
(in, features) -> {
|
|
|
|
if (in.canBeLine()) {
|
|
|
|
features.line("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value1")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr");
|
2021-05-28 10:08:13 +00:00
|
|
|
}
|
|
|
|
if (in.canBePolygon()) {
|
|
|
|
features.polygon("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value2")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr");
|
2021-05-28 10:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2021-05-30 11:42:06 +00:00
|
|
|
assertSubmap(sortListValues(Map.of(
|
2021-05-28 10:08:13 +00:00
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newLineString(
|
2021-05-30 11:42:06 +00:00
|
|
|
64, 64,
|
|
|
|
192, 64,
|
2021-05-28 10:08:13 +00:00
|
|
|
192, 192,
|
2021-05-30 11:42:06 +00:00
|
|
|
64, 192,
|
|
|
|
64, 64
|
2021-05-28 10:08:13 +00:00
|
|
|
), Map.of(
|
|
|
|
"attr", "value",
|
|
|
|
"name", "name value1"
|
|
|
|
)),
|
2021-05-30 11:42:06 +00:00
|
|
|
feature(rectangle(64, 192), Map.of(
|
2021-05-28 10:08:13 +00:00
|
|
|
"attr", "value",
|
|
|
|
"name", "name value2"
|
|
|
|
))
|
|
|
|
)
|
2021-05-30 11:42:06 +00:00
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2022-01-07 15:01:22 +00:00
|
|
|
@ParameterizedTest
|
|
|
|
@ValueSource(strings = {"multipolygon", "boundary", "land_area"})
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmMultipolygon(String relationType) throws Exception {
|
2021-09-10 00:46:20 +00:00
|
|
|
record TestRelationInfo(long id, String name) implements OsmRelationInfo {}
|
2021-05-28 10:08:13 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
new OsmElement.Node(1, GeoUtils.getWorldLat(0.125), GeoUtils.getWorldLon(0.125)),
|
|
|
|
new OsmElement.Node(2, GeoUtils.getWorldLat(0.125), GeoUtils.getWorldLon(0.875)),
|
|
|
|
new OsmElement.Node(3, GeoUtils.getWorldLat(0.875), GeoUtils.getWorldLon(0.875)),
|
|
|
|
new OsmElement.Node(4, GeoUtils.getWorldLat(0.875), GeoUtils.getWorldLon(0.125)),
|
|
|
|
|
|
|
|
new OsmElement.Node(5, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.25)),
|
|
|
|
new OsmElement.Node(6, GeoUtils.getWorldLat(0.25), GeoUtils.getWorldLon(0.75)),
|
|
|
|
new OsmElement.Node(7, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.75)),
|
|
|
|
new OsmElement.Node(8, GeoUtils.getWorldLat(0.75), GeoUtils.getWorldLon(0.25)),
|
|
|
|
|
|
|
|
new OsmElement.Node(9, GeoUtils.getWorldLat(0.375), GeoUtils.getWorldLon(0.375)),
|
|
|
|
new OsmElement.Node(10, GeoUtils.getWorldLat(0.375), GeoUtils.getWorldLon(0.625)),
|
|
|
|
new OsmElement.Node(11, GeoUtils.getWorldLat(0.625), GeoUtils.getWorldLon(0.625)),
|
|
|
|
new OsmElement.Node(12, GeoUtils.getWorldLat(0.625), GeoUtils.getWorldLon(0.375)),
|
|
|
|
new OsmElement.Node(13, GeoUtils.getWorldLat(0.375 + 1e-12), GeoUtils.getWorldLon(0.375)),
|
|
|
|
|
|
|
|
with(new OsmElement.Way(14), way -> way.nodes().add(1, 2, 3, 4, 1)),
|
|
|
|
with(new OsmElement.Way(15), way -> way.nodes().add(5, 6, 7, 8, 5)),
|
|
|
|
with(new OsmElement.Way(16), way -> way.nodes().add(9, 10, 11, 12, 13)),
|
|
|
|
|
|
|
|
with(new OsmElement.Relation(17), rel -> {
|
2022-01-07 15:01:22 +00:00
|
|
|
rel.setTag("type", relationType);
|
2021-05-28 10:08:13 +00:00
|
|
|
rel.setTag("attr", "value");
|
2021-05-30 11:42:06 +00:00
|
|
|
rel.setTag("should_emit", "yes");
|
2022-03-01 01:52:30 +00:00
|
|
|
rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 14, "outer"));
|
|
|
|
rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 15, null)); // missing
|
|
|
|
rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 16, "inner")); // incorrect
|
2021-06-24 09:16:30 +00:00
|
|
|
}),
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Relation(18), rel -> {
|
2021-06-24 09:16:30 +00:00
|
|
|
rel.setTag("type", "relation");
|
|
|
|
rel.setTag("name", "rel name");
|
2022-03-01 01:52:30 +00:00
|
|
|
rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 17, "outer"));
|
2021-05-28 10:08:13 +00:00
|
|
|
})
|
|
|
|
),
|
2021-06-24 09:16:30 +00:00
|
|
|
in -> in.hasTag("type", "relation") ?
|
2021-08-14 09:55:00 +00:00
|
|
|
List.of(new TestRelationInfo(in.id(), in.getString("name"))) :
|
2021-06-24 09:16:30 +00:00
|
|
|
null,
|
2021-05-28 10:08:13 +00:00
|
|
|
(in, features) -> {
|
2021-05-30 11:42:06 +00:00
|
|
|
if (in.hasTag("should_emit")) {
|
2021-05-28 10:08:13 +00:00
|
|
|
features.polygon("layer")
|
|
|
|
.setZoomRange(0, 0)
|
|
|
|
.setAttr("name", "name value")
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr")
|
2021-06-24 09:16:30 +00:00
|
|
|
.setAttr("relname",
|
|
|
|
in.relationInfo(TestRelationInfo.class).stream().map(c -> c.relation().name).findFirst().orElse(null));
|
2021-05-28 10:08:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newMultiPolygon(
|
2021-05-30 11:42:06 +00:00
|
|
|
newPolygon(
|
|
|
|
rectangleCoordList(0.125 * 256, 0.875 * 256),
|
|
|
|
List.of(
|
|
|
|
rectangleCoordList(0.25 * 256, 0.75 * 256)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
rectangle(0.375 * 256, 0.625 * 256)
|
2021-05-28 10:08:13 +00:00
|
|
|
), Map.of(
|
|
|
|
"attr", "value",
|
2021-06-24 09:16:30 +00:00
|
|
|
"name", "name value",
|
|
|
|
"relname", "rel name"
|
2021-05-28 10:08:13 +00:00
|
|
|
))
|
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
|
|
|
|
2021-05-31 10:21:53 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmLineInRelation() throws Exception {
|
2021-09-10 00:46:20 +00:00
|
|
|
record TestRelationInfo(long id, String name) implements OsmRelationInfo {}
|
2021-05-31 10:21:53 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
new OsmElement.Node(1, 0, 0),
|
|
|
|
new OsmElement.Node(2, GeoUtils.getWorldLat(0.375), 0),
|
|
|
|
new OsmElement.Node(3, GeoUtils.getWorldLat(0.25), 0),
|
|
|
|
new OsmElement.Node(4, GeoUtils.getWorldLat(0.125), 0),
|
|
|
|
with(new OsmElement.Way(5), way -> {
|
2021-05-31 10:21:53 +00:00
|
|
|
way.setTag("attr", "value1");
|
2022-03-01 01:52:30 +00:00
|
|
|
way.nodes().add(1, 2);
|
2021-05-31 10:21:53 +00:00
|
|
|
}),
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Way(6), way -> {
|
2021-05-31 10:21:53 +00:00
|
|
|
way.setTag("attr", "value2");
|
2022-03-01 01:52:30 +00:00
|
|
|
way.nodes().add(3, 4);
|
2021-05-31 10:21:53 +00:00
|
|
|
}),
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Relation(6), rel -> {
|
2021-05-31 10:21:53 +00:00
|
|
|
rel.setTag("name", "relation name");
|
2022-03-01 01:52:30 +00:00
|
|
|
rel.members().add(new OsmElement.Relation.Member(OsmElement.Type.WAY, 6, "role"));
|
2021-05-31 10:21:53 +00:00
|
|
|
})
|
|
|
|
),
|
|
|
|
(relation) -> {
|
|
|
|
if (relation.hasTag("name", "relation name")) {
|
2021-08-14 09:55:00 +00:00
|
|
|
return List.of(new TestRelationInfo(relation.id(), relation.getString("name")));
|
2021-05-31 10:21:53 +00:00
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}, (in, features) -> {
|
2021-06-18 11:21:43 +00:00
|
|
|
var relationInfos = in.relationInfo(TestRelationInfo.class);
|
|
|
|
var firstRelation = relationInfos.stream().findFirst();
|
2021-05-31 10:21:53 +00:00
|
|
|
if (in.canBeLine()) {
|
|
|
|
features.line("layer")
|
|
|
|
.setZoomRange(0, 0)
|
2021-06-18 11:21:43 +00:00
|
|
|
.setAttr("relname", firstRelation.map(d -> d.relation().name).orElse(null))
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("attr")
|
2021-08-14 09:55:00 +00:00
|
|
|
.setAttr("relrole", firstRelation.map(OsmReader.RelationMember::role).orElse(null));
|
2021-05-31 10:21:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
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",
|
2021-06-18 11:21:43 +00:00
|
|
|
"relname", "relation name",
|
|
|
|
"relrole", "role"
|
2021-05-31 10:21:53 +00:00
|
|
|
))
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2022-01-16 15:00:57 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testPreprocessOsmNodesAndWays() throws Exception {
|
2022-01-16 15:00:57 +00:00
|
|
|
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(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, 0, 0), node -> node.setTag("a", "b")),
|
|
|
|
new OsmElement.Node(2, GeoUtils.getWorldLat(0.375), 0),
|
|
|
|
with(new OsmElement.Way(3), way -> {
|
|
|
|
way.nodes().add(1, 2);
|
2022-01-16 15:00:57 +00:00
|
|
|
}),
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Way(4), way -> {
|
|
|
|
way.nodes().add(1, 2);
|
2022-01-16 15:00:57 +00:00
|
|
|
})
|
|
|
|
)),
|
|
|
|
profile
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0), List.of(
|
|
|
|
feature(newPoint(128, 128), Map.of())
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2021-05-31 11:16:44 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testPostProcessNodeUseLabelGridRank() throws Exception {
|
2021-05-31 11:16:44 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
double lng3 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 20d / 256);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPoint(lng1, lat), Map.of("rank", "1")),
|
|
|
|
newReaderFeature(newPoint(lng2, lat), Map.of("rank", "2")),
|
|
|
|
newReaderFeature(newPoint(lng3, lat), Map.of("rank", "3"))
|
2021-05-31 11:16:44 +00:00
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.inheritAttrFromSource("rank")
|
2021-09-18 00:18:06 +00:00
|
|
|
.setSortKey(Integer.parseInt(in.getTag("rank").toString()))
|
2022-01-10 11:41:15 +00:00
|
|
|
.setPointLabelGridPixelSize(13, 8)
|
2021-09-10 00:46:20 +00:00
|
|
|
.setBufferPixels(8),
|
2021-05-31 11:16:44 +00:00
|
|
|
(layer, zoom, items) -> {
|
|
|
|
if ("layer".equals(layer) && zoom == 13) {
|
2021-09-10 00:46:20 +00:00
|
|
|
List<VectorTile.Feature> result = new ArrayList<>(items.size());
|
2021-05-31 11:16:44 +00:00
|
|
|
Map<Long, Integer> rankInGroup = new HashMap<>();
|
2021-09-18 00:18:06 +00:00
|
|
|
for (var item : items) {
|
2021-05-31 11:16:44 +00:00
|
|
|
result.add(item.copyWithExtraAttrs(Map.of(
|
|
|
|
"grouprank", rankInGroup.merge(item.group(), 1, Integer::sum)
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
return items;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newPoint(64, 128), Map.of("rank", "1")),
|
|
|
|
feature(newPoint(74, 128), Map.of("rank", "2")),
|
|
|
|
feature(newPoint(84, 128), Map.of("rank", "3"))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
2021-09-18 00:18:06 +00:00
|
|
|
feature(newPoint(32, 64), Map.of("rank", "1", "grouprank", 1L)),
|
|
|
|
feature(newPoint(37, 64), Map.of("rank", "2", "grouprank", 2L)),
|
2021-05-31 11:16:44 +00:00
|
|
|
// separate group
|
2021-09-18 00:18:06 +00:00
|
|
|
feature(newPoint(42, 64), Map.of("rank", "3", "grouprank", 1L))
|
2021-05-31 11:16:44 +00:00
|
|
|
)
|
|
|
|
), results.tiles);
|
|
|
|
}
|
2021-05-31 10:21:53 +00:00
|
|
|
|
2021-06-01 10:29:55 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMergeLineStrings() throws Exception {
|
2022-07-26 11:51:31 +00:00
|
|
|
double y = 0.5 + Z15_WIDTH / 2;
|
2021-06-01 10:29:55 +00:00
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
2022-07-26 11:51:31 +00:00
|
|
|
double x1 = 0.5 + Z15_WIDTH / 4;
|
2021-06-01 10:29:55 +00:00
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
2022-07-26 11:51:31 +00:00
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 10d / 256);
|
|
|
|
double lng3 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 20d / 256);
|
|
|
|
double lng4 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 30d / 256);
|
2021-06-01 10:29:55 +00:00
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
2022-07-26 11:51:31 +00:00
|
|
|
Map.of("threads", "1", "maxzoom", "15"),
|
2021-06-01 10:29:55 +00:00
|
|
|
List.of(
|
|
|
|
// merge at z13 (same "group"):
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newLineString(
|
2021-06-01 10:29:55 +00:00
|
|
|
lng1, lat,
|
|
|
|
lng2, lat
|
|
|
|
), Map.of("group", "1", "other", "1")),
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newLineString(
|
2021-06-01 10:29:55 +00:00
|
|
|
lng2, lat,
|
|
|
|
lng3, lat
|
|
|
|
), Map.of("group", "1", "other", "2")),
|
|
|
|
// don't merge at z13:
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newLineString(
|
2021-06-01 10:29:55 +00:00
|
|
|
lng3, lat,
|
|
|
|
lng4, lat
|
|
|
|
), Map.of("group", "2", "other", "3"))
|
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.line("layer")
|
2022-07-26 11:51:31 +00:00
|
|
|
.setMinZoom(13)
|
2021-09-10 00:46:20 +00:00
|
|
|
.setAttrWithMinzoom("z14attr", in.getTag("other"), 14)
|
|
|
|
.inheritAttrFromSource("group"),
|
2021-06-01 10:29:55 +00:00
|
|
|
(layer, zoom, items) -> FeatureMerge.mergeLineStrings(items, 0, 0, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
2022-07-26 11:51:31 +00:00
|
|
|
TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15), List.of(
|
2021-06-01 10:29:55 +00:00
|
|
|
feature(newLineString(64, 128, 74, 128), Map.of("group", "1", "z14attr", "1")),
|
|
|
|
feature(newLineString(74, 128, 84, 128), Map.of("group", "1", "z14attr", "2")),
|
|
|
|
feature(newLineString(84, 128, 94, 128), Map.of("group", "2", "z14attr", "3"))
|
|
|
|
),
|
2022-07-26 11:51:31 +00:00
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newLineString(32, 64, 37, 64), Map.of("group", "1", "z14attr", "1")),
|
|
|
|
feature(newLineString(37, 64, 42, 64), Map.of("group", "1", "z14attr", "2")),
|
|
|
|
feature(newLineString(42, 64, 47, 64), Map.of("group", "2", "z14attr", "3"))
|
|
|
|
),
|
2021-06-01 10:29:55 +00:00
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
// merge 32->37 and 37->42 since they have same attrs
|
2022-07-26 11:51:31 +00:00
|
|
|
feature(newLineString(16, 32, 21, 32), Map.of("group", "1")),
|
|
|
|
feature(newLineString(21, 32, 23.5, 32), Map.of("group", "2"))
|
2021-06-01 10:29:55 +00:00
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2021-10-20 01:57:47 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMergeLineStringsIgnoresRoundingIntersections() throws Exception {
|
2021-10-20 01:57:47 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
double lat2a = GeoUtils.getWorldLat(y + Z14_WIDTH * 0.5 / 4096);
|
|
|
|
double lat2b = GeoUtils.getWorldLat(y + Z14_WIDTH * 1.5 / 4096);
|
|
|
|
double lat3 = GeoUtils.getWorldLat(y + Z14_WIDTH * 10 / 4096);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
double lng3 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 20d / 256);
|
|
|
|
|
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
|
|
|
// group two parallel lines that almost touch at the midpoint
|
|
|
|
// need to retain extra precision while merging to ensure it
|
|
|
|
// doesn't confuse the line merger
|
|
|
|
newReaderFeature(newLineString(
|
|
|
|
lng1, lat,
|
|
|
|
lng2, lat2a
|
|
|
|
), Map.of()),
|
|
|
|
newReaderFeature(newLineString(
|
|
|
|
lng2, lat2a,
|
|
|
|
lng3, lat
|
|
|
|
), Map.of()),
|
|
|
|
|
|
|
|
newReaderFeature(newLineString(
|
|
|
|
lng1, lat3,
|
|
|
|
lng2, lat2b
|
|
|
|
), Map.of()),
|
|
|
|
newReaderFeature(newLineString(
|
|
|
|
lng2, lat2b,
|
|
|
|
lng3, lat3
|
|
|
|
), Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.line("layer").setZoomRange(13, 13),
|
|
|
|
(layer, zoom, items) -> FeatureMerge.mergeLineStrings(items, 0, 0, 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
feature(newMultiLineString(
|
|
|
|
newLineString(32, 64.3125, 37, 64.0625, 42, 64.3125),
|
|
|
|
newLineString(32, 64, 37, 64.0625, 42, 64)
|
|
|
|
), Map.of())
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2021-06-01 10:29:55 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testMergePolygons() throws Exception {
|
2021-06-01 10:29:55 +00:00
|
|
|
var results = runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2021-06-02 11:54:21 +00:00
|
|
|
// merge same group:
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPolygon(z14CoordinatePixelList(
|
2021-06-01 10:29:55 +00:00
|
|
|
10, 10,
|
|
|
|
20, 10,
|
|
|
|
20, 20,
|
|
|
|
10, 20,
|
|
|
|
10, 10
|
|
|
|
)), Map.of("group", "1")),
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPolygon(z14CoordinatePixelList(
|
2021-06-01 10:29:55 +00:00
|
|
|
20.5, 10,
|
|
|
|
30, 10,
|
|
|
|
30, 20,
|
|
|
|
20.5, 20,
|
|
|
|
20.5, 10
|
2021-06-02 11:54:21 +00:00
|
|
|
)), Map.of("group", "1")),
|
|
|
|
// don't merge - different group:
|
2021-06-07 11:46:03 +00:00
|
|
|
newReaderFeature(newPolygon(z14CoordinatePixelList(
|
2021-06-01 10:29:55 +00:00
|
|
|
10, 20.5,
|
|
|
|
20, 20.5,
|
|
|
|
20, 30,
|
|
|
|
10, 30,
|
|
|
|
10, 20.5
|
2021-06-02 11:54:21 +00:00
|
|
|
)), Map.of("group", "2"))
|
2021-06-01 10:29:55 +00:00
|
|
|
),
|
2021-09-10 00:46:20 +00:00
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(14, 14)
|
|
|
|
.inheritAttrFromSource("group"),
|
|
|
|
(layer, zoom, items) -> FeatureMerge.mergeNearbyPolygons(
|
|
|
|
items,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
1,
|
|
|
|
1
|
|
|
|
)
|
2021-06-01 10:29:55 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(rectangle(10, 10, 30, 20), Map.of("group", "1")),
|
|
|
|
feature(rectangle(10, 20.5, 20, 30), Map.of("group", "2"))
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2021-06-23 01:46:42 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testReaderProfileFinish() throws Exception {
|
2021-06-23 01:46:42 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
|
|
|
|
var results = runWithReaderFeaturesProfile(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(newPoint(lng1, lat), Map.of("a", 1, "b", 2)),
|
|
|
|
newReaderFeature(newPoint(lng2, lat), Map.of("a", 3, "b", 4))
|
|
|
|
),
|
|
|
|
new Profile.NullProfile() {
|
2021-08-11 12:40:49 +00:00
|
|
|
private final List<SourceFeature> featureList = new CopyOnWriteArrayList<>();
|
2021-06-23 01:46:42 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature in, FeatureCollector features) {
|
|
|
|
featureList.add(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void finish(String name, FeatureCollector.Factory featureCollectors,
|
|
|
|
Consumer<FeatureCollector.Feature> next) {
|
|
|
|
if ("test".equals(name)) {
|
2021-08-11 12:40:49 +00:00
|
|
|
for (SourceFeature in : featureList) {
|
|
|
|
var features = featureCollectors.get(in);
|
|
|
|
features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("a");
|
2021-08-11 12:40:49 +00:00
|
|
|
for (var feature : features) {
|
|
|
|
next.accept(feature);
|
2021-06-23 01:46:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newPoint(64, 128), Map.of("a", 1L)),
|
|
|
|
feature(newPoint(74, 128), Map.of("a", 3L))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
// merge 32->37 and 37->42 since they have same attrs
|
|
|
|
feature(newPoint(32, 64), Map.of("a", 1L)),
|
|
|
|
feature(newPoint(37, 64), Map.of("a", 3L))
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmProfileFinish() throws Exception {
|
2021-06-23 01:46:42 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
|
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, lat, lng1), t -> t.setTag("a", 1)),
|
|
|
|
with(new OsmElement.Node(2, lat, lng2), t -> t.setTag("a", 3))
|
2021-06-23 01:46:42 +00:00
|
|
|
),
|
|
|
|
new Profile.NullProfile() {
|
2021-08-11 12:40:49 +00:00
|
|
|
private final List<SourceFeature> featureList = new CopyOnWriteArrayList<>();
|
2021-06-23 01:46:42 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature in, FeatureCollector features) {
|
|
|
|
featureList.add(in);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void finish(String name, FeatureCollector.Factory featureCollectors,
|
|
|
|
Consumer<FeatureCollector.Feature> next) {
|
|
|
|
if ("osm".equals(name)) {
|
2021-08-11 12:40:49 +00:00
|
|
|
for (SourceFeature in : featureList) {
|
|
|
|
var features = featureCollectors.get(in);
|
|
|
|
features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
2021-09-10 00:46:20 +00:00
|
|
|
.inheritAttrFromSource("a");
|
2021-08-11 12:40:49 +00:00
|
|
|
for (var feature : features) {
|
|
|
|
next.accept(feature);
|
2021-06-23 01:46:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newPoint(64, 128), Map.of("a", 1L)),
|
2021-09-18 10:05:50 +00:00
|
|
|
feature(newPoint(74, 128), Map.of("a", 3L))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
// merge 32->37 and 37->42 since they have same attrs
|
|
|
|
feature(newPoint(32, 64), Map.of("a", 1L)),
|
|
|
|
feature(newPoint(37, 64), Map.of("a", 3L))
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testOsmProfileFinishForwardingProfile() throws Exception {
|
2021-09-18 10:05:50 +00:00
|
|
|
double y = 0.5 + Z14_WIDTH / 2;
|
|
|
|
double lat = GeoUtils.getWorldLat(y);
|
|
|
|
|
|
|
|
double x1 = 0.5 + Z14_WIDTH / 4;
|
|
|
|
double lng1 = GeoUtils.getWorldLon(x1);
|
|
|
|
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
|
|
|
ForwardingProfile profile = new ForwardingProfile() {
|
|
|
|
@Override
|
|
|
|
public String name() {
|
|
|
|
return "test";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
List<SourceFeature> featureList = new CopyOnWriteArrayList<>();
|
|
|
|
profile.registerSourceHandler("osm", (in, features) -> featureList.add(in));
|
|
|
|
profile.registerHandler((ForwardingProfile.FinishHandler) (name, featureCollectors, next) -> {
|
|
|
|
if ("osm".equals(name)) {
|
|
|
|
for (SourceFeature in : featureList) {
|
|
|
|
var features = featureCollectors.get(in);
|
|
|
|
features.point("layer")
|
|
|
|
.setZoomRange(13, 14)
|
|
|
|
.inheritAttrFromSource("a");
|
|
|
|
for (var feature : features) {
|
|
|
|
next.accept(feature);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, lat, lng1), t -> t.setTag("a", 1)),
|
|
|
|
with(new OsmElement.Node(2, lat, lng2), t -> t.setTag("a", 3))
|
2021-09-18 10:05:50 +00:00
|
|
|
),
|
|
|
|
profile
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(sortListValues(Map.of(
|
|
|
|
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
|
|
|
feature(newPoint(64, 128), Map.of("a", 1L)),
|
2021-06-23 01:46:42 +00:00
|
|
|
feature(newPoint(74, 128), Map.of("a", 3L))
|
|
|
|
),
|
|
|
|
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
|
|
|
// merge 32->37 and 37->42 since they have same attrs
|
|
|
|
feature(newPoint(32, 64), Map.of("a", 1L)),
|
|
|
|
feature(newPoint(37, 64), Map.of("a", 3L))
|
|
|
|
)
|
|
|
|
)), sortListValues(results.tiles));
|
|
|
|
}
|
|
|
|
|
2021-05-31 10:21:53 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-05-22 11:07:34 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-05-31 10:21:53 +00:00
|
|
|
private interface Runner {
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
void run(FeatureGroup featureGroup, Profile profile, PlanetilerConfig config) throws Exception;
|
2021-05-31 10:21:53 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 10:53:37 +00:00
|
|
|
private interface LayerPostprocessFunction {
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
List<VectorTile.Feature> process(String layer, int zoom, List<VectorTile.Feature> items)
|
2021-06-01 10:29:55 +00:00
|
|
|
throws GeometryException;
|
2021-05-08 10:53:37 +00:00
|
|
|
}
|
|
|
|
|
2022-01-16 15:00:57 +00:00
|
|
|
private record PlanetilerResults(
|
2022-05-24 21:46:56 +00:00
|
|
|
Map<TileCoord, List<TestUtils.ComparableFeature>> tiles, Map<String, String> metadata, int tileDataCount
|
2021-05-23 20:10:44 +00:00
|
|
|
) {}
|
2021-05-13 10:25:06 +00:00
|
|
|
|
2022-01-16 15:00:57 +00:00
|
|
|
private record TestProfile(
|
2021-05-08 10:53:37 +00:00
|
|
|
@Override String name,
|
|
|
|
@Override String description,
|
|
|
|
@Override String attribution,
|
|
|
|
@Override String version,
|
|
|
|
BiConsumer<SourceFeature, FeatureCollector> processFeature,
|
2021-09-10 00:46:20 +00:00
|
|
|
Function<OsmElement.Relation, List<OsmRelationInfo>> preprocessOsmRelation,
|
2021-05-08 10:53:37 +00:00
|
|
|
LayerPostprocessFunction postprocessLayerFeatures
|
|
|
|
) implements Profile {
|
|
|
|
|
|
|
|
TestProfile(
|
|
|
|
BiConsumer<SourceFeature, FeatureCollector> processFeature,
|
2021-09-10 00:46:20 +00:00
|
|
|
Function<OsmElement.Relation, List<OsmRelationInfo>> preprocessOsmRelation,
|
2021-05-08 10:53:37 +00:00
|
|
|
LayerPostprocessFunction postprocessLayerFeatures
|
|
|
|
) {
|
2021-05-13 10:25:06 +00:00
|
|
|
this(TEST_PROFILE_NAME, TEST_PROFILE_DESCRIPTION, TEST_PROFILE_ATTRIBUTION, TEST_PROFILE_VERSION, processFeature,
|
|
|
|
preprocessOsmRelation,
|
2021-05-08 10:53:37 +00:00
|
|
|
postprocessLayerFeatures);
|
|
|
|
}
|
|
|
|
|
|
|
|
static TestProfile processSourceFeatures(BiConsumer<SourceFeature, FeatureCollector> processFeature) {
|
2021-06-01 10:29:55 +00:00
|
|
|
return new TestProfile(processFeature, (a) -> null, (a, b, c) -> c);
|
2021-05-08 10:53:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2021-09-10 00:46:20 +00:00
|
|
|
public List<OsmRelationInfo> preprocessOsmRelation(
|
2021-08-14 09:55:00 +00:00
|
|
|
OsmElement.Relation relation) {
|
2021-05-08 10:53:37 +00:00
|
|
|
return preprocessOsmRelation.apply(relation);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
|
|
|
|
processFeature.accept(sourceFeature, features);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-03-09 02:08:03 +00:00
|
|
|
public void release() {}
|
2021-05-08 10:53:37 +00:00
|
|
|
|
|
|
|
@Override
|
2021-09-10 00:46:20 +00:00
|
|
|
public List<VectorTile.Feature> postProcessLayerFeatures(String layer, int zoom,
|
|
|
|
List<VectorTile.Feature> items) throws GeometryException {
|
2021-05-08 10:53:37 +00:00
|
|
|
return postprocessLayerFeatures.process(layer, zoom, items);
|
|
|
|
}
|
|
|
|
}
|
2021-07-27 02:01:55 +00:00
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
private static <T> List<T> orEmpty(List<T> in) {
|
2021-07-27 02:01:55 +00:00
|
|
|
return in == null ? List.of() : in;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testBadRelation() throws Exception {
|
2021-07-27 02:01:55 +00:00
|
|
|
// this threw an exception in OsmMultipolygon.build
|
|
|
|
OsmXml osmInfo = TestUtils.readOsmXml("bad_spain_relation.xml");
|
2023-02-24 18:14:50 +00:00
|
|
|
List<OsmElement> elements = convertToOsmElements(osmInfo);
|
|
|
|
|
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
elements,
|
|
|
|
(in, features) -> {
|
|
|
|
if (in.hasTag("landuse", "forest")) {
|
|
|
|
features.polygon("layer")
|
|
|
|
.setZoomRange(12, 14)
|
|
|
|
.setBufferPixels(4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertEquals(11, results.tiles.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
private static List<OsmElement> convertToOsmElements(OsmXml osmInfo) {
|
2022-03-01 01:52:30 +00:00
|
|
|
List<OsmElement> elements = new ArrayList<>();
|
2021-07-27 02:01:55 +00:00
|
|
|
for (var node : orEmpty(osmInfo.nodes())) {
|
2023-03-20 20:41:18 +00:00
|
|
|
var newNode = new OsmElement.Node(node.id(), node.lat(), node.lon());
|
|
|
|
elements.add(newNode);
|
|
|
|
for (var tag : orEmpty(node.tags())) {
|
|
|
|
newNode.setTag(tag.k(), tag.v());
|
|
|
|
}
|
2021-07-27 02:01:55 +00:00
|
|
|
}
|
|
|
|
for (var way : orEmpty(osmInfo.ways())) {
|
2022-03-01 01:52:30 +00:00
|
|
|
var readerWay = new OsmElement.Way(way.id());
|
2021-07-27 02:01:55 +00:00
|
|
|
elements.add(readerWay);
|
|
|
|
for (var tag : orEmpty(way.tags())) {
|
|
|
|
readerWay.setTag(tag.k(), tag.v());
|
|
|
|
}
|
|
|
|
for (var nodeRef : orEmpty(way.nodeRefs())) {
|
2022-03-01 01:52:30 +00:00
|
|
|
readerWay.nodes().add(nodeRef.ref());
|
2021-07-27 02:01:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var relation : orEmpty(osmInfo.relation())) {
|
2022-03-01 01:52:30 +00:00
|
|
|
var readerRelation = new OsmElement.Relation(relation.id());
|
2021-07-27 02:01:55 +00:00
|
|
|
elements.add(readerRelation);
|
|
|
|
for (var tag : orEmpty(relation.tags())) {
|
|
|
|
readerRelation.setTag(tag.k(), tag.v());
|
|
|
|
}
|
|
|
|
for (var member : orEmpty(relation.members())) {
|
2022-03-01 01:52:30 +00:00
|
|
|
readerRelation.members().add(new OsmElement.Relation.Member(switch (member.type()) {
|
|
|
|
case "way" -> OsmElement.Type.WAY;
|
|
|
|
case "relation" -> OsmElement.Type.RELATION;
|
|
|
|
case "node" -> OsmElement.Type.NODE;
|
2021-07-27 02:01:55 +00:00
|
|
|
default -> throw new IllegalStateException("Unexpected value: " + member.type());
|
|
|
|
}, member.ref(), member.role()));
|
|
|
|
}
|
|
|
|
}
|
2023-02-24 18:14:50 +00:00
|
|
|
return elements;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
void testIssue496BaseballMultipolygon() throws Exception {
|
|
|
|
// this generated a polygon that covered an entire z11 tile where the buffer intersected the baseball field
|
|
|
|
OsmXml osmInfo = TestUtils.readOsmXml("issue_496_baseball_multipolygon.xml");
|
|
|
|
List<OsmElement> elements = convertToOsmElements(osmInfo);
|
2021-07-27 02:01:55 +00:00
|
|
|
|
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
elements,
|
|
|
|
(in, features) -> {
|
2023-02-24 18:14:50 +00:00
|
|
|
if (in.hasTag("natural", "sand")) {
|
|
|
|
features.polygon("test")
|
|
|
|
.setBufferPixels(4)
|
|
|
|
.setPixelTolerance(0.5)
|
|
|
|
.setMinPixelSize(0.1)
|
|
|
|
.setAttr("id", in.id());
|
2021-07-27 02:01:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2023-02-24 18:14:50 +00:00
|
|
|
double areaAtZ14 = 20;
|
|
|
|
|
|
|
|
for (var entry : results.tiles().entrySet()) {
|
|
|
|
var tile = entry.getKey();
|
|
|
|
for (var feature : entry.getValue()) {
|
|
|
|
var geom = feature.geometry().geom();
|
|
|
|
double area = geom.getArea();
|
|
|
|
double expectedMaxArea = areaAtZ14 / (1 << (14 - tile.z()));
|
|
|
|
assertTrue(area < expectedMaxArea, "tile=" + tile + " area=" + area + " geom=" + geom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(8, results.tiles.size());
|
2021-07-27 02:01:55 +00:00
|
|
|
}
|
2021-08-17 01:51:49 +00:00
|
|
|
|
2023-03-20 20:41:18 +00:00
|
|
|
@Test
|
|
|
|
void testIssue509LenaDelta() throws Exception {
|
|
|
|
OsmXml osmInfo = TestUtils.readOsmXml("issue_509_lena_delta.xml");
|
|
|
|
List<OsmElement> elements = convertToOsmElements(osmInfo);
|
|
|
|
|
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
elements,
|
|
|
|
(in, features) -> {
|
|
|
|
if (in.hasTag("natural", "water")) {
|
|
|
|
features.polygon("water").setAttr("id", in.id()).setMinZoom(10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
Map<Integer, Integer> counts = new TreeMap<>();
|
|
|
|
for (var tile : results.tiles().keySet()) {
|
|
|
|
counts.merge(tile.z(), 1, Integer::sum);
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(Map.of(
|
|
|
|
10, 39,
|
|
|
|
11, 125,
|
|
|
|
12, 397,
|
|
|
|
13, 1160,
|
|
|
|
14, 3108
|
|
|
|
), counts);
|
|
|
|
}
|
|
|
|
|
2022-05-24 22:46:52 +00:00
|
|
|
@ParameterizedTest
|
|
|
|
@ValueSource(strings = {
|
|
|
|
"",
|
|
|
|
"--write-threads=2 --process-threads=2 --feature-read-threads=2 --threads=4",
|
|
|
|
"--free-osm-after-read",
|
2022-06-03 09:25:17 +00:00
|
|
|
"--osm-parse-node-bounds",
|
2023-03-20 20:41:18 +00:00
|
|
|
"--output-format=pmtiles",
|
2022-05-24 22:46:52 +00:00
|
|
|
})
|
|
|
|
void testPlanetilerRunner(String args) throws Exception {
|
2023-03-18 18:38:04 +00:00
|
|
|
boolean pmtiles = args.contains("pmtiles");
|
2022-03-03 13:52:45 +00:00
|
|
|
Path originalOsm = TestUtils.pathToResource("monaco-latest.osm.pbf");
|
2023-03-18 18:38:04 +00:00
|
|
|
Path output = tempDir.resolve(pmtiles ? "output.pmtiles" : "output.mbtiles");
|
2022-03-03 13:52:45 +00:00
|
|
|
Path tempOsm = tempDir.resolve("monaco-temp.osm.pbf");
|
|
|
|
Files.copy(originalOsm, tempOsm);
|
|
|
|
Planetiler.create(Arguments.fromArgs(
|
2022-07-20 12:06:08 +00:00
|
|
|
("--tmpdir=" + tempDir.resolve("data") + " " + args).split("\\s+")
|
2022-03-09 02:08:03 +00:00
|
|
|
))
|
2021-08-17 01:51:49 +00:00
|
|
|
.setProfile(new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature source, FeatureCollector features) {
|
|
|
|
if (source.canBePolygon() && source.hasTag("building", "yes")) {
|
|
|
|
features.polygon("building").setZoomRange(0, 14).setMinPixelSize(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2022-03-03 13:52:45 +00:00
|
|
|
.addOsmSource("osm", tempOsm)
|
2021-08-17 01:51:49 +00:00
|
|
|
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
|
|
|
|
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
|
2023-01-26 01:56:30 +00:00
|
|
|
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
|
2023-03-18 18:38:04 +00:00
|
|
|
.setOutput(output)
|
2021-08-17 01:51:49 +00:00
|
|
|
.run();
|
|
|
|
|
2022-03-03 13:52:45 +00:00
|
|
|
// make sure it got deleted after write
|
2022-05-24 22:46:52 +00:00
|
|
|
if (args.contains("free-osm-after-read")) {
|
|
|
|
assertFalse(Files.exists(tempOsm));
|
|
|
|
}
|
2022-03-03 13:52:45 +00:00
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
try (
|
|
|
|
var db = pmtiles ? ReadablePmtiles.newReadFromFile(output) : Mbtiles.newReadOnlyDatabase(output)
|
|
|
|
) {
|
2021-08-17 01:51:49 +00:00
|
|
|
int features = 0;
|
|
|
|
var tileMap = TestUtils.getTileMap(db);
|
|
|
|
for (var tile : tileMap.values()) {
|
|
|
|
for (var feature : tile) {
|
|
|
|
feature.geometry().validate();
|
|
|
|
features++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertEquals(11, tileMap.size(), "num tiles");
|
|
|
|
assertEquals(2146, features, "num buildings");
|
2023-01-02 16:26:00 +00:00
|
|
|
assertSubmap(Map.of(
|
|
|
|
"planetiler:version", BuildInfo.get().version(),
|
|
|
|
"planetiler:osm:osmosisreplicationtime", "2021-04-21T20:21:46Z",
|
|
|
|
"planetiler:osm:osmosisreplicationseq", "2947",
|
|
|
|
"planetiler:osm:osmosisreplicationurl", "http://download.geofabrik.de/europe/monaco-updates"
|
2023-03-18 18:38:04 +00:00
|
|
|
), db.metadata().toMap());
|
2021-08-17 01:51:49 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-04 11:26:26 +00:00
|
|
|
|
2022-12-15 19:19:22 +00:00
|
|
|
@Test
|
|
|
|
void testPlanetilerRunnerShapefile() throws Exception {
|
|
|
|
Path mbtiles = tempDir.resolve("output.mbtiles");
|
|
|
|
Path resourceDir = TestUtils.pathToResource("");
|
|
|
|
|
|
|
|
Planetiler.create(Arguments.fromArgs("--tmpdir=" + tempDir.resolve("data")))
|
|
|
|
.setProfile(new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature source, FeatureCollector features) {
|
|
|
|
features.point("stations")
|
|
|
|
.setZoomRange(0, 14)
|
2023-01-02 16:41:12 +00:00
|
|
|
.setAttr("source", source.getSource())
|
|
|
|
.setAttr("layer", source.getSourceLayer());
|
2022-12-15 19:19:22 +00:00
|
|
|
}
|
|
|
|
})
|
2023-01-01 22:29:00 +00:00
|
|
|
// Match *.shp within [shapefile.zip, shapefile-copy.zip]
|
|
|
|
.addShapefileGlobSource("shapefile-glob", resourceDir, "shape*.zip")
|
|
|
|
// Match *.shp within shapefile.zip
|
|
|
|
.addShapefileGlobSource("shapefile-glob-zip", resourceDir.resolve("shapefile.zip"), "*.shp")
|
|
|
|
// Match *.shp within shapefile.zip
|
2022-12-15 19:19:22 +00:00
|
|
|
.addShapefileSource("shapefile", resourceDir.resolve("shapefile.zip"))
|
2023-03-18 18:38:04 +00:00
|
|
|
.setOutput(mbtiles)
|
2022-12-15 19:19:22 +00:00
|
|
|
.run();
|
|
|
|
|
|
|
|
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) {
|
2023-01-01 22:29:00 +00:00
|
|
|
long fileCount = 0, globCount = 0, globZipCount = 0;
|
2022-12-15 19:19:22 +00:00
|
|
|
var tileMap = TestUtils.getTileMap(db);
|
|
|
|
for (var tile : tileMap.values()) {
|
|
|
|
for (var feature : tile) {
|
|
|
|
feature.geometry().validate();
|
2023-01-02 16:41:12 +00:00
|
|
|
assertEquals("stations", feature.attrs().get("layer"));
|
2022-12-15 19:19:22 +00:00
|
|
|
switch ((String) feature.attrs().get("source")) {
|
|
|
|
case "shapefile" -> fileCount++;
|
2023-01-01 22:29:00 +00:00
|
|
|
case "shapefile-glob" -> globCount++;
|
|
|
|
case "shapefile-glob-zip" -> globZipCount++;
|
2022-12-15 19:19:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertTrue(fileCount > 0);
|
2023-01-01 22:29:00 +00:00
|
|
|
// `shapefile` and `shapefile-glob-zip` both match only one file.
|
|
|
|
assertEquals(fileCount, globZipCount);
|
|
|
|
// `shapefile-glob` matches two input files, should have 2x number of features of `shapefile`.
|
|
|
|
assertEquals(2 * fileCount, globCount);
|
2022-12-15 19:19:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-02 17:19:05 +00:00
|
|
|
@ParameterizedTest
|
|
|
|
@ValueSource(strings = {
|
|
|
|
"",
|
|
|
|
"--write-threads=2 --process-threads=2 --feature-read-threads=2 --threads=4",
|
2023-01-26 01:56:30 +00:00
|
|
|
"--input-file=geopackage.gpkg"
|
2023-01-02 17:19:05 +00:00
|
|
|
})
|
|
|
|
void testPlanetilerRunnerGeoPackage(String args) throws Exception {
|
|
|
|
Path mbtiles = tempDir.resolve("output.mbtiles");
|
2023-01-26 01:56:30 +00:00
|
|
|
String inputFile = Arguments.fromArgs(args).getString("input-file", "", "geopackage.gpkg.zip");
|
2023-01-02 17:19:05 +00:00
|
|
|
|
|
|
|
Planetiler.create(Arguments.fromArgs((args + " --tmpdir=" + tempDir.resolve("data")).split("\\s+")))
|
|
|
|
.setProfile(new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public void processFeature(SourceFeature source, FeatureCollector features) {
|
|
|
|
features.point("stations")
|
|
|
|
.setZoomRange(0, 14)
|
|
|
|
.setAttr("name", source.getString("name"));
|
|
|
|
}
|
|
|
|
})
|
2023-01-26 01:56:30 +00:00
|
|
|
.addGeoPackageSource("geopackage", TestUtils.pathToResource(inputFile), null)
|
2023-03-18 18:38:04 +00:00
|
|
|
.setOutput(mbtiles)
|
2023-01-02 17:19:05 +00:00
|
|
|
.run();
|
|
|
|
|
|
|
|
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) {
|
|
|
|
Set<String> uniqueNames = new HashSet<>();
|
|
|
|
long featureCount = 0;
|
|
|
|
var tileMap = TestUtils.getTileMap(db);
|
|
|
|
for (var tile : tileMap.values()) {
|
|
|
|
for (var feature : tile) {
|
|
|
|
feature.geometry().validate();
|
|
|
|
featureCount++;
|
|
|
|
uniqueNames.add((String) feature.attrs().get("name"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertTrue(featureCount > 0);
|
|
|
|
assertEquals(86, uniqueNames.size());
|
|
|
|
assertTrue(uniqueNames.contains("Van Dörn Street"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-03 12:25:24 +00:00
|
|
|
private void runWithProfile(Path tempDir, Profile profile, boolean force) throws Exception {
|
|
|
|
Planetiler.create(Arguments.of("tmpdir", tempDir, "force", Boolean.toString(force)))
|
|
|
|
.setProfile(profile)
|
|
|
|
.addOsmSource("osm", TestUtils.pathToResource("monaco-latest.osm.pbf"))
|
|
|
|
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
|
|
|
|
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
|
2023-01-26 01:56:30 +00:00
|
|
|
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
|
2023-03-18 18:38:04 +00:00
|
|
|
.setOutput(tempDir.resolve("output.mbtiles"))
|
2022-03-03 12:25:24 +00:00
|
|
|
.run();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-05-24 22:46:52 +00:00
|
|
|
void testPlanetilerMemoryCheck() {
|
2022-03-03 12:25:24 +00:00
|
|
|
assertThrows(Exception.class, () -> runWithProfile(tempDir, new Profile.NullProfile() {
|
2022-03-09 02:08:03 +00:00
|
|
|
@Override
|
|
|
|
public long estimateIntermediateDiskBytes(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, false)
|
2022-03-03 12:25:24 +00:00
|
|
|
);
|
|
|
|
assertThrows(Exception.class, () -> runWithProfile(tempDir, new Profile.NullProfile() {
|
2022-03-09 02:08:03 +00:00
|
|
|
@Override
|
|
|
|
public long estimateOutputBytes(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, false)
|
2022-03-03 12:25:24 +00:00
|
|
|
);
|
|
|
|
assertThrows(Exception.class, () -> runWithProfile(tempDir, new Profile.NullProfile() {
|
2022-03-09 02:08:03 +00:00
|
|
|
@Override
|
|
|
|
public long estimateRamRequired(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, false)
|
2022-03-03 12:25:24 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
2022-05-24 22:46:52 +00:00
|
|
|
void testPlanetilerMemoryCheckForce() throws Exception {
|
2022-03-03 12:25:24 +00:00
|
|
|
runWithProfile(tempDir, new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public long estimateIntermediateDiskBytes(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, true);
|
|
|
|
runWithProfile(tempDir, new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public long estimateOutputBytes(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, true);
|
|
|
|
runWithProfile(tempDir, new Profile.NullProfile() {
|
|
|
|
@Override
|
|
|
|
public long estimateRamRequired(long osmSize) {
|
|
|
|
return Long.MAX_VALUE / 10L;
|
|
|
|
}
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
2022-02-04 11:26:26 +00:00
|
|
|
@Test
|
2022-04-23 10:36:24 +00:00
|
|
|
void testHandleProfileException() throws Exception {
|
2022-02-04 11:26:26 +00:00
|
|
|
var results = runWithOsmElements(
|
|
|
|
Map.of("threads", "1"),
|
|
|
|
List.of(
|
2022-03-01 01:52:30 +00:00
|
|
|
with(new OsmElement.Node(1, 0, 0), t -> t.setTag("attr", "value"))
|
2022-02-04 11:26:26 +00:00
|
|
|
),
|
|
|
|
(in, features) -> {
|
2022-03-01 01:52:30 +00:00
|
|
|
throw new IllegalStateException("intentional exception!") {
|
|
|
|
|
|
|
|
// suppress stack trace in logs
|
|
|
|
@Override
|
|
|
|
public synchronized Throwable fillInStackTrace() {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
};
|
2022-02-04 11:26:26 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
assertSubmap(Map.of(), results.tiles);
|
|
|
|
}
|
2022-05-24 21:46:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
private PlanetilerResults runForCompactTest(boolean compactDbEnabled) throws Exception {
|
|
|
|
return runWithReaderFeatures(
|
2023-03-18 18:38:04 +00:00
|
|
|
Map.of("threads", "1", "mbtiles-compact", Boolean.toString(compactDbEnabled)),
|
2022-05-24 21:46:56 +00:00
|
|
|
List.of(
|
|
|
|
newReaderFeature(WORLD_POLYGON, Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(0, 2)
|
|
|
|
.setBufferPixels(0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
void testCompactDb() throws Exception {
|
|
|
|
|
|
|
|
var compactResult = runForCompactTest(true);
|
|
|
|
var nonCompactResult = runForCompactTest(false);
|
|
|
|
|
|
|
|
assertEquals(nonCompactResult.tiles, compactResult.tiles);
|
|
|
|
assertTrue(
|
|
|
|
compactResult.tileDataCount() < compactResult.tiles.size(),
|
|
|
|
"tileDataCount=%s should be less than tileCount=%s".formatted(
|
|
|
|
compactResult.tileDataCount(), compactResult.tiles.size()
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2022-06-20 10:31:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
private PlanetilerResults runForMaxZoomTest(Map<String, String> attrs) throws Exception {
|
|
|
|
double z8Pixel = 1d / (1 << 8) / 256d;
|
|
|
|
double tileMiddle = 0.5 + Z14_WIDTH / 2;
|
|
|
|
return runWithReaderFeatures(
|
|
|
|
attrs,
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(newLineString(worldCoordinateList(
|
|
|
|
tileMiddle, tileMiddle,
|
|
|
|
tileMiddle + z8Pixel / 2, tileMiddle
|
|
|
|
)), Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.line("layer").setBufferPixels(0).setZoomRange(0, 14)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
void testRenderMaxzoom() throws Exception {
|
|
|
|
|
|
|
|
var baseResult = runForMaxZoomTest(Map.of(
|
|
|
|
"minzoom", "0",
|
|
|
|
"maxzoom", "14")
|
|
|
|
);
|
|
|
|
var maxzoomResult = runForMaxZoomTest(Map.of(
|
|
|
|
"minzoom", "0",
|
|
|
|
"maxzoom", "8"
|
|
|
|
));
|
|
|
|
var renderMaxzoomResult = runForMaxZoomTest(Map.of(
|
|
|
|
"minzoom", "0",
|
|
|
|
"maxzoom", "8",
|
|
|
|
"render_maxzoom", "8"
|
|
|
|
));
|
|
|
|
|
|
|
|
// the feature is too small to include at z8, unless z8 is the max zoom for rendering to support overzooming
|
|
|
|
var z8Tile = TileCoord.ofXYZ((1 << 8) / 2, (1 << 8) / 2, 8);
|
|
|
|
assertFalse(baseResult.tiles.containsKey(z8Tile));
|
|
|
|
assertTrue(renderMaxzoomResult.tiles.containsKey(z8Tile));
|
|
|
|
assertFalse(maxzoomResult.tiles.containsKey(z8Tile));
|
|
|
|
}
|
2022-07-22 10:48:04 +00:00
|
|
|
|
|
|
|
private PlanetilerResults runForBoundsTest(int minzoom, int maxzoom, String key, String value) throws Exception {
|
|
|
|
return runWithReaderFeatures(
|
|
|
|
Map.of("threads", "1", key, value),
|
|
|
|
List.of(
|
|
|
|
newReaderFeature(WORLD_POLYGON, Map.of())
|
|
|
|
),
|
|
|
|
(in, features) -> features.polygon("layer")
|
|
|
|
.setZoomRange(minzoom, maxzoom)
|
|
|
|
.setBufferPixels(0)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
void testBoundFilters() throws Exception {
|
|
|
|
var origResult = runForBoundsTest(0, 2, "", "");
|
|
|
|
var bboxResult = runForBoundsTest(0, 2, "bounds", "1,-85.05113,180,-1");
|
|
|
|
var polyResult = runForBoundsTest(0, 2, "polygon", TestUtils.pathToResource("bottomrightearth.poly").toString());
|
|
|
|
|
|
|
|
assertEquals(1 + 4 + 16, origResult.tiles.size());
|
|
|
|
assertEquals(Set.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0),
|
|
|
|
TileCoord.ofXYZ(1, 1, 1),
|
|
|
|
TileCoord.ofXYZ(2, 2, 2),
|
|
|
|
TileCoord.ofXYZ(3, 2, 2),
|
|
|
|
TileCoord.ofXYZ(2, 3, 2),
|
|
|
|
TileCoord.ofXYZ(3, 3, 2)
|
|
|
|
), bboxResult.tiles.keySet());
|
|
|
|
assertEquals(Set.of(
|
|
|
|
TileCoord.ofXYZ(0, 0, 0),
|
|
|
|
TileCoord.ofXYZ(1, 1, 1),
|
|
|
|
// TileCoord.ofXYZ(2, 2, 2), - omit since this one is outside of triangle
|
|
|
|
TileCoord.ofXYZ(3, 2, 2),
|
|
|
|
TileCoord.ofXYZ(2, 3, 2),
|
|
|
|
TileCoord.ofXYZ(3, 3, 2)
|
|
|
|
), polyResult.tiles.keySet());
|
|
|
|
|
|
|
|
// but besides the omitted tile, the rest should be the same
|
|
|
|
bboxResult.tiles.remove(TileCoord.ofXYZ(2, 2, 2));
|
|
|
|
assertEquals(bboxResult.tiles, polyResult.tiles);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Test
|
|
|
|
void testBoundFiltersFill() throws Exception {
|
|
|
|
var polyResultz8 = runForBoundsTest(8, 8, "polygon", TestUtils.pathToResource("bottomrightearth.poly").toString());
|
|
|
|
|
|
|
|
int z8tiles = 1 << 8;
|
|
|
|
assertFalse(polyResultz8.tiles.containsKey(TileCoord.ofXYZ(z8tiles * 3 / 4, z8tiles * 5 / 8, 8)));
|
|
|
|
assertTrue(polyResultz8.tiles.containsKey(TileCoord.ofXYZ(z8tiles * 3 / 4, z8tiles * 7 / 8, 8)));
|
|
|
|
}
|
2021-05-08 10:53:37 +00:00
|
|
|
}
|