Created
March 19, 2020 20:28
-
-
Save planemad/c8bf709ce4ccf3aadfa3d1b94f81960d to your computer and use it in GitHub Desktop.
Create a Mapbox vector tileset using OpenStreetMap data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -e -u -o pipefail | |
set -x | |
# | |
# Step 0: Setup | |
# | |
echo "Install Dependencies (Ubuntu)" | |
apt-get update | |
apt-get install software-properties-common | |
add-apt-repository ppa:ubuntugis/ubuntugis-unstable | |
apt-get install tmux vim osmium-tool gdal-bin python3-gdal python3-numpy | |
echo "Install Mapbox Tileset CLI" | |
git clone https://github.com/mapbox/tilesets-cli.git | |
cd tilesets-cli | |
mkvirtualenv tilesets-cli | |
pip install . | |
cd .. | |
mkdir planet-maps | |
cd planet-maps | |
# | |
# Step 1: Create a data extract from OpenStreetMap that contains all the required features | |
# | |
echo "# Download OSM PBF" | |
# Source: https://wiki.openstreetmap.org/wiki/Planet.osm#Planet.osm_mirrors | |
OSM_PBF="https://ftpmirror.your.org/pub/openstreetmap/pbf/planet-latest.osm.pbf" | |
# OSM_PBF="https://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf" | |
wget -O ./planet.osm.pbf $OSM_PBF | |
# Create a filtered extract of the planet file that has data required for the tileset | |
# Use osmium tags-filter to set the geometry types and OSM tags of interest | |
# https://wiki.openstreetmap.org/wiki/OpenRailwayMap/Tagging | |
extract="railways" | |
echo "# Creating ${extract} planet extract" | |
mkdir ${extract} -p | |
osmium tags-filter ./planet.osm.pbf nwr/railway nwr/train=yes w/historic=railway w/historic:railway w/abandoned:railway w/disused:railway w/construction:railway w/proposed:railway -o ${extract}/planet.osm.pbf | |
echo "# Test ${extract} extract" | |
ogrinfo ${extract}/planet.osm.pbf -so | |
# Create data layers from the planet extract | |
# All OSM tags can be accessed using hstore_get_value(all_tags, 'key') | |
echo "# Create feature layers" | |
# If specific tags are defined in osmconf.ini they can be used as a column directly for easy filtering | |
vi ./osmconf.ini | |
# https://wiki.openstreetmap.org/wiki/Tag:railway%3Dstation | |
ogr2ogr -f GeoJSONSeq ${extract}/railway-stations.geojsonl ${extract}/planet.osm.pbf \ | |
-oo CONFIG_FILE=./osmconf.ini \ | |
-dialect sqlite \ | |
-sql "SELECT | |
coalesce(name_en,name) as name_en, | |
coalesce(hstore_get_value(all_tags, 'railway:ref'),hstore_get_value(all_tags, 'ref')) AS ref, | |
hstore_get_value(all_tags, 'wikidata') as qid, | |
railway, | |
station, | |
CASE WHEN platforms IS NOT NULL THEN CAST(hstore_get_value(all_tags, 'platforms') AS INT) ELSE 1 END as platforms, | |
network, operator, | |
all_tags, | |
CASE WHEN platforms IS NOT NULL THEN 100-CAST(hstore_get_value(all_tags, 'platforms') AS INT) ELSE 100 END as z_order, | |
geometry | |
FROM points WHERE | |
railway IN ('station','halt') | |
OR public_transport='station' | |
UNION ALL | |
SELECT | |
coalesce(name_en,name) as name_en, | |
coalesce(hstore_get_value(all_tags, 'railway:ref'),hstore_get_value(all_tags, 'ref')) AS ref, | |
hstore_get_value(all_tags, 'wikidata') as qid, | |
railway, | |
station, | |
CASE WHEN platforms IS NOT NULL THEN CAST(hstore_get_value(all_tags, 'platforms') AS INT) ELSE 1 END as platforms, | |
network, operator, | |
all_tags, | |
CASE WHEN platforms IS NOT NULL THEN 100-CAST(hstore_get_value(all_tags, 'platforms') AS INT) ELSE 100 END as z_order, | |
ST_PointOnSurface(geometry) | |
FROM multipolygons WHERE | |
railway IN ('station','halt') | |
OR public_transport='station'" | |
# https://wiki.openstreetmap.org/wiki/Railways | |
# https://wiki.openstreetmap.org/wiki/OpenRailwayMap/Tagging#Tracks | |
ogr2ogr -f GeoJSONSeq ${extract}/railway-lines.geojsonl ${extract}/planet.osm.pbf \ | |
-oo CONFIG_FILE=./osmconf.ini \ | |
-dialect sqlite \ | |
-sql "SELECT | |
coalesce(name_en,name) as name_en, | |
coalesce(hstore_get_value(all_tags, 'railway:ref'),hstore_get_value(all_tags, 'ref')) AS ref, | |
railway, | |
usage, | |
CASE WHEN tracks IS NOT NULL THEN CAST(tracks AS INT) ELSE 0 END AS tracks, | |
service, | |
CASE WHEN maxspeed IS NOT NULL THEN | |
ROUND( | |
CAST(maxspeed AS REAL) * | |
CASE WHEN maxspeed LIKE '%mph' THEN 1.61 ELSE 1 END | |
,0) | |
ELSE 0 END AS maxspeed, | |
electrified, | |
gauge, | |
bridge, | |
tunnel, | |
network, operator, | |
geometry | |
FROM lines WHERE | |
railway IN ('rail','subway','light_rail','tram','narrow_gauge','monorail','miniature','preserved','funicular','construction','proposed','disused','abandoned','historic','razed')" | |
# | |
# Step 2: Use extracted data to create a Mapbox vector tile source | |
# https://docs.mapbox.com/help/tutorials/get-started-tilesets-api-and-cli/ | |
# | |
# Signup for an account at mapbox.com and create a new secret token with tileset scope enabled | |
# | |
export MAPBOX_ACCESS_TOKEN=<token> | |
export mapbox_username=planemad | |
echo "Add tileset source" | |
# tilesets delete-source ${mapbox_username} planet-railway-stations | |
tilesets add-source ${mapbox_username} planet-railway-stations ${extract}/railway-stations.geojsonl | |
# tilesets delete-source ${mapbox_username} planet-railway-lines | |
tilesets add-source ${mapbox_username} planet-railway-lines ${extract}/railway-lines.geojsonl | |
echo "Verify sources" | |
tilesets list-sources ${mapbox_username} | |
# | |
# Step 3: Create a vector tileset recipe to convert each data source into vector tileset | |
# | |
# This step might require multiple iterations to optimize how the features are packed into the tiles | |
# | |
# Resources: | |
# https://docs.mapbox.com/help/troubleshooting/tileset-recipe-reference/ | |
# https://docs.mapbox.com/help/troubleshooting/tileset-recipe-examples | |
# | |
tileset_id="railway-stations" | |
tileset_name="Railway Stations" | |
tileset_recipe=' | |
{ | |
"version": 1, | |
"layers": { | |
"stations": { | |
"source": "mapbox://tileset-source/planemad/planet-railway-stations", | |
"minzoom": 0, | |
"maxzoom": 15, | |
"tiles": { | |
"limit": [ | |
[ "highest_where_in_distance", true, 50000, "platform" ] | |
] | |
}, | |
"features" : { | |
"attributes" : { | |
"allowed_output" : [ | |
"name_en", "ref", "railway", "station", "platforms" | |
] | |
} | |
} | |
} | |
} | |
} | |
' | |
echo $tileset_recipe > ${extract}/${tileset_id}-recipe.json | |
tileset_id="railway-lines" | |
tileset_name="Railway Lines" | |
tileset_recipe=' | |
{ | |
"version": 1, | |
"layers": { | |
"railways": { | |
"source": "mapbox://tileset-source/planemad/planet-railway-lines", | |
"minzoom": 1, | |
"maxzoom": 11, | |
"features" : { | |
"filter" : [ | |
"case", | |
[ | |
"all", | |
["<", ["zoom"], 5], | |
[ "==", [ "get", "service"], null ] | |
], | |
true, | |
true | |
], | |
"attributes" : { | |
"allowed_output" : [ | |
"railway", "usage", "service", "electrified", "maxspeed", "tracks", "gauge" | |
] | |
} | |
}, | |
"tiles": { | |
"union": [ | |
{ | |
"where": [ "==", [ "get", "service"], null ], | |
"group_by": ["railway","usage","electrified","tracks","gauge","service"], | |
"maintain_direction": false, | |
"aggregate": { "maxspeed": "max" } | |
}, | |
{ | |
"where": [ "!=", [ "get", "service"], null ], | |
"group_by": ["railway","usage","electrified","gauge","service"], | |
"maintain_direction": false | |
} | |
] | |
} | |
} | |
} | |
} | |
' | |
echo $tileset_recipe > ${extract}/${tileset_id}-recipe.json | |
exit 0 | |
# | |
# Step 4: Upload recipe and publish tileset | |
# Needs to be run for each tileset | |
# | |
# Create tileset with recipe on first run | |
echo "Create tileset and add recipe" | |
tilesets create ${mapbox_username}.${tileset_id} --recipe ${extract}/${tileset_id}-recipe.json --name "${tileset_name}" | |
# Update recipe for subsequent recipe updates | |
echo "Update and publish tileset" | |
tilesets update-recipe ${mapbox_username}.${tileset_id} ${extract}/${tileset_id}-recipe.json | |
# Start a job to refresh the tileset with the latest data and recipe | |
tilesets publish ${mapbox_username}.${tileset_id} | |
tilesets status ${mapbox_username}.${tileset_id} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment