From 2f1e1a46a189b58ca6b395d21a8cac8808a3cdeb Mon Sep 17 00:00:00 2001 From: Roman Karavia <3082173+rkaravia@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:40:18 +0200 Subject: [PATCH] Add preview website --- preview-website/.gitignore | 2 + preview-website/README.md | 21 ++++ preview-website/build.sh | 27 +++++ preview-website/public/index.html | 149 ++++++++++++++++++++++++ preview-website/public/preview-tiles.js | 136 +++++++++++++++++++++ 5 files changed, 335 insertions(+) create mode 100644 preview-website/.gitignore create mode 100644 preview-website/README.md create mode 100755 preview-website/build.sh create mode 100644 preview-website/public/index.html create mode 100644 preview-website/public/preview-tiles.js diff --git a/preview-website/.gitignore b/preview-website/.gitignore new file mode 100644 index 0000000..6366f1f --- /dev/null +++ b/preview-website/.gitignore @@ -0,0 +1,2 @@ +/build +/public/tiles diff --git a/preview-website/README.md b/preview-website/README.md new file mode 100644 index 0000000..19fdb99 --- /dev/null +++ b/preview-website/README.md @@ -0,0 +1,21 @@ +# Preview Website + +The code in this folder powers the [preview website](https://osm-regions.netlify.app/). + +It installs tippecanoe, downloads the OSM regions vector tileset, and extracts +the individual tiles (`.pbf` files) up to zoom level 8 using +[tile-join](https://github.com/mapbox/tippecanoe#tile-join). +This generates about 24k files; adding more tiles with zoom levels 9 or 10 would make the +build time out on Netlify. + +This approach makes it possible to preview the content of `regions.mbtiles` without +maintaining a tile server in the back-end, and without storing the individual `.pbf` +anywhere else. + +The deployment on Netlify is configured to run `build.sh` and then publish the content +of the `public` folder. + +## Run locally + +- Run `build.sh` +- Start a web server in the `public` folder and check the result diff --git a/preview-website/build.sh b/preview-website/build.sh new file mode 100755 index 0000000..71f9d13 --- /dev/null +++ b/preview-website/build.sh @@ -0,0 +1,27 @@ +#!/bin/bash +set -o errexit +set -o nounset + +# Enter directory of build script, so relative paths below are working +cd $(dirname "$0") + +# Build tippecanoe +( + mkdir -p build/tippecanoe + cd build/tippecanoe + curl -L https://github.com/mapbox/tippecanoe/archive/1.35.0.tar.gz | tar xz --strip-components=1 + make -j +) + +# Download and extract tileset +curl -L -o build/regions.zip https://github.com/nzzdev/osm-regions/releases/download/v0.1.0/regions-v0.1.0.zip +unzip build/regions.zip -d build + +# Extract tiles +./build/tippecanoe/tile-join \ + --no-tile-compression \ + --no-tile-size-limit \ + --maximum-zoom=8 \ + --output-to-directory=public/tiles \ + build/regions.mbtiles + diff --git a/preview-website/public/index.html b/preview-website/public/index.html new file mode 100644 index 0000000..b6393e6 --- /dev/null +++ b/preview-website/public/index.html @@ -0,0 +1,149 @@ + + + + + + OSM Regions Preview + + + + + + Fix, Fork, Contribute + + + + +
+ + + + + diff --git a/preview-website/public/preview-tiles.js b/preview-website/public/preview-tiles.js new file mode 100644 index 0000000..464d246 --- /dev/null +++ b/preview-website/public/preview-tiles.js @@ -0,0 +1,136 @@ +const map = new mapboxgl.Map({ + container: document.getElementById("map"), + hash: true, + style: + "https://api.maptiler.com/maps/positron/style.json?key=qdSqvQdaTpTPAsu0YByB", +}); + +map.on("load", () => { + const baseUrl = `${window.location.protocol}//${window.location.host}`; + + // Add regions as source + map.addSource("regions", { + maxzoom: 8, + promoteId: "wikidata", + type: "vector", + tiles: [`${baseUrl}/tiles/{z}/{x}/{y}.pbf`], + }); + + ["countries", "subdivisions"].forEach((regionType, i) => { + const visibility = ["visible", "none"][i]; + + // Fill polygons blue, with highlight on mouseover + map.addLayer({ + id: `${regionType}-area`, + type: "fill", + source: "regions", + "source-layer": regionType, + paint: { + "fill-color": [ + "case", + ["boolean", ["feature-state", "mouseover"], false], + "#fad250", + "steelblue", + ], + "fill-opacity": [ + "case", + ["boolean", ["feature-state", "mouseover"], false], + 0.4, + 0.1, + ], + }, + layout: { visibility }, + }); + + // Add blue outline + map.addLayer({ + id: `${regionType}-outline`, + type: "line", + source: "regions", + "source-layer": regionType, + paint: { + "line-width": 1, + "line-color": "steelblue", + }, + layout: { visibility }, + }); + + // Show attributes on mouseover + const popup = new mapboxgl.Popup({ + closeButton: false, + closeOnClick: false, + }); + + let oldFeatures = []; + + map.on("mousemove", `${regionType}-area`, ({ features, lngLat }) => { + if (features.length) { + map.getCanvas().style.cursor = "pointer"; + + // Update highlighting of features + setMouseoverState(oldFeatures, false); + setMouseoverState(features, true); + oldFeatures = features; + + // Show popup at mouse location + const wikidataIds = features.map( + ({ properties: { wikidata } }) => wikidata + ); + popup + .setLngLat(lngLat) + .setHTML(`
${wikidataIds.join(", ")}
`) + .addTo(map); + } + }); + + map.on("mouseleave", `${regionType}-area`, ({ features }) => { + map.getCanvas().style.cursor = ""; + + // Remove highlighting from features + setMouseoverState(oldFeatures, false); + + popup.remove(); + }); + }); + + // Adapted from https://docs.mapbox.com/mapbox-gl-js/example/toggle-layers/ + const toggleableLayers = { + countries: "Countries", + subdivisions: "Subdivisions", + }; + Object.entries(toggleableLayers).forEach(([id, name]) => { + const link = document.createElement("a"); + link.href = "#"; + link.id = id; + link.textContent = name; + if (map.getLayoutProperty(`${id}-area`, "visibility") !== "none") { + link.className = "active"; + } + + link.onclick = function (event) { + event.preventDefault(); + event.stopPropagation(); + + const visibility = map.getLayoutProperty(`${this.id}-area`, "visibility"); + + if (visibility === "none") { + this.className = "active"; + map.setLayoutProperty(`${this.id}-area`, "visibility", "visible"); + map.setLayoutProperty(`${this.id}-outline`, "visibility", "visible"); + } else { + map.setLayoutProperty(`${this.id}-area`, "visibility", "none"); + map.setLayoutProperty(`${this.id}-outline`, "visibility", "none"); + this.className = ""; + } + }; + + const layers = document.getElementById("menu"); + layers.appendChild(link); + }); +}); + +function setMouseoverState(features, state) { + features.forEach((feature) => { + map.setFeatureState(feature, { mouseover: state }); + }); +}