This is a minimal example project that shows how to create custom maps with Flatmap.
Requirements:
- Java 16 or later
- on mac:
brew install --cask temurin
- on mac:
- Maven
- on mac:
brew install maven
- on mac:
- Node.js
- on mac:
brew install node
- on mac:
- TileServer GL
npm install -g tileserver-gl-light
- Also recommended: IntelliJ IDEA
- Disk: 5-10x as much free space as the input data
- RAM: 1.5x the size of the
.osm.pbf
file
First, make a copy of this example project. It contains:
- pom.xml - build instructions for Maven:
com.onthegomap:flatmap-core
main FlatMap dependencycom.onthegomap:flatmap-core
test dependency for test utilitiesmaven-assembly-plugin
build plugin configuration to create a single executable jar file frommvn package
goal command
child.pom.xml
exists for the parent pom.xml to treat this as a child project, you can remove it to run as a standalone project- src/main/java/com/onthegomap/flatmap/examples - some minimal example
map profiles:
- ToiletsOverlay - demonstrates how to build a simple overlay with toilets locations from OpenStreetMap
- BikeRouteOverlay - demonstrates how to use OSM relations to build an overlay map of bicycle routes
- ToiletsOverlayLowLevelApi - alternate driver for the ToiletsOverlay using lower-level Flatmap APIs
- src/test/java/com/onthegomap/flatmap/examples unit and integration tests for each of the map generators
Then, create a new class that implements com.onthegomap.flatmap.Profile
:
package com.onthegomap.flatmap.examples;
import com.onthegomap.flatmap.FeatureCollector;
import com.onthegomap.flatmap.FlatmapRunner;
import com.onthegomap.flatmap.Profile;
import com.onthegomap.flatmap.reader.SourceFeature;
import java.nio.file.Path;
public class MyProfile implements Profile {
@Override
public String name() {
// name that shows up in the MBTiles metadata table
return "My Profile";
}
}
Then, implement the processFeature()
method that determines what vector tile features to emit for each source feature.
For example, to include a map of toilets from OpenStreetMap
at zoom level 12 and above:
@Override
public void processFeature(SourceFeature sourceFeature, FeatureCollector features) {
if (sourceFeature.isPoint() && sourceFeature.hasTag("amenity", "toilets")) {
features.point("toilets") // create a point in layer named "toilets"
.setMinZoom(12)
.setAttr("customers_only", sourceFeature.hasTag("access", "customers"))
.setAttr("indoor", sourceFeature.getBoolean("indoor"))
.setAttr("name", sourceFeature.getTag("name"))
.setAttr("operator", sourceFeature.getTag("operator"));
}
}
Next, add a main
entrypoint that
uses FlatmapRunner to define input sources
and default input/output paths:
public static void main(String... args) throws Exception {
FlatmapRunner.create(args)
.setProfile(new MyProfile())
// if input.pbf not found, download Monaco from Geofabrik
.addOsmSource("osm", Path.of("data", "sources", "input.pbf"), "geofabrik:monaco")
.overwriteOutput("mbtiles", Path.of("data", "toilets.mbtiles"))
.run();
}
Then build the application into a single jar file with all dependencies included:
mvn clean package
And run the application:
java -cp target/*-with-deps.jar com.onthegomap.flatmap.examples.MyProfile
Then, to inspect the tiles:
tileserver-gl-light --mbtiles data/toilets.mbtiles
Finally, open http://localhost:8080 to see your tiles.
Unit tests verify the logic for mapping source features to vector tile features, and integration tests run the entire profile end-to-end and ensure the output vector tiles contain features you expect. TestUtils contains utilities for unit and integration testing.
A basic unit test:
@Test
public void unitTest() {
var profile = new MyProfile();
var node = SimpleFeature.create(
TestUtils.newPoint(1, 2),
Map.of("amenity", "toilets")
);
List<FeatureCollector.Feature> mapFeatures = TestUtils.processSourceFeature(node, profile);
// Then inspect attributes of each of vector tile fetures emitted...
assertEquals(1, mapFeatures.length);
assertEquals(12, mapFeatures.get(0).getMinZoom());
}
A basic integration test:
@Test
public void integrationTest(@TempDir Path tmpDir) throws Exception {
Path mbtilesPath = tmpDir.resolve("output.mbtiles");
MyProfile.main(
"--osm_path=" + TestUtils.pathToResource("monaco-latest.osm.pbf"),
"--tmp=" + tmpDir,
"--mbtiles=" + mbtilesPath,
));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(mbtilesPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll();
assertEquals("My Profile", metadata.get("name"));
// then inspect features in the emitted vector tiles
TestUtils.assertNumFeatures(mbtiles, "toilets", 14, Map.of(), GeoUtils.WORLD_LAT_LON_BOUNDS,
34, Point.class);
}
}
See ToiletsProfileTest for a complete unit and integration test.
Check out:
- The other minimal examples
- The basemap profile for a full-featured example of a complex profile with processing broken out into a handler per layer
- FlatmapRunner for more options when invoking the program
- FeatureCollector for the full API to construct vector tile features
- SourceFeature and WithTags for the full API to extract data from source features
- Profile for the rest of methods you can implement
to:
- customize OSM relation preprocessing
- set MBTiles metadata attributes
- get notified when a source finishes processing
- and post-process vector-tile features (i.e. merge touching linestrings or polygons)