planetiler/flatmap-examples
Michael Barry 7420c7652e
Open-source Flatmap (#3)
* remove markdown-link-check exclusions

* no shared public read access token (github deletes them automatically when checked into a public repo)
2021-10-25 06:28:45 -04:00
..
src CI and docs 2021-10-19 21:57:47 -04:00
README.md Open-source Flatmap (#3) 2021-10-25 06:28:45 -04:00
child.pom.xml CI and docs 2021-10-19 21:57:47 -04:00
pom.xml CI and docs 2021-10-19 21:57:47 -04:00

README.md

Flatmap Example Project

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
  • Maven
    • on mac: brew install maven
  • Node.js
    • on mac: brew install node
  • 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 dependency
    • com.onthegomap:flatmap-core test dependency for test utilities
    • maven-assembly-plugin build plugin configuration to create a single executable jar file from mvn 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:
  • src/test/java/com/onthegomap/flatmap/examples unit and integration tests for each of the map generators

Until Flatmap gets into Maven Central, to resolve flatmap-core dependencies, you will need to create a GitHub personal access token with read:packages scope, then add the following to your ~/.m2/settings.xml:

<servers>
  <server>
    <id>github-flatmap</id>
    <username>username</username>
    <password>token</password>
  </server>
</servers>

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.

Testing your profile

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.

Next Steps

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)