Skip to content

Commit

Permalink
Support Stadia API
Browse files Browse the repository at this point in the history
  • Loading branch information
rudokemper committed Jul 11, 2024
1 parent 9c146bd commit 35693e1
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 35 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

This headless Node.js MapGL renderer can be used to generate styled raster tiles in an MBTiles format. It can work with self-provided tilesets and a stylesheet, or an online API source with optional GeoJSON and OpenStreetMap data overlays.

The motivation to build this utility is to create offline background maps for use in mobile data collection applications, such as [Mapeo](https://mapeo.app/) and [ODK Collect](https://getodk.org/) / [KoboToolbox Collect](https://www.kobotoolbox.org/), or other offline-compatible tools that can work with self-hosted maps like [Terrastories](https://terrastories.app/). However, it can be helpful for any use case where having self-hosted raster MBTiles is a requirement.
The motivation to build this utility is to create offline background maps for use in mobile data collection applications, such as [Mapeo](https://mapeo.app/), [ODK Collect](https://getodk.org/), [Kobo Collect](https://www.kobotoolbox.org/), [Locus Map](https://www.locusmap.app/) or other offline-compatible tools that can work with self-hosted tiles like [Terrastories](https://terrastories.app/). However, it can be helpful for any use case where having self-hosted raster MBTiles is a requirement.

This tool started as an extension of [mbgl-renderer](https://github.com/consbio/mbgl-renderer), which was built to export single static map images. Our thanks go out to the contributors of that project.

Expand All @@ -20,16 +20,20 @@ This tool can be used in the following ways:

## Supported online API sources

> ❗️ To use these services, you are responsible for providing your own API token. In doing so, please carefully consult the terms of service and API limitations for each service.
> ❗️ To use these services, you are responsible for providing your own API token, and abiding by the service's terms of use. In doing so, please carefully consult the terms of service and API limitations for each service.
* Bing Imagery (Virtual Earth) - [Terms of Use](https://www.microsoft.com/en-us/maps/bing-maps/product)
* ESRI World Imagery - [Terms of use](https://www.arcgis.com/home/item.html?id=226d23f076da478bba4589e7eae95952)
* Google Hybrid - [Terms of Use](https://developers.google.com/maps/documentation/tile/policies)
* Mapbox - [Terms of Use](https://www-mapbox.webflow.io/pricing#tile)
* Mapbox Satellite - [Terms of Use](https://www-mapbox.webflow.io/pricing#tile)
* Overpass (to overlay OpenStreetMap data on top of imagery sources) - [Terms of Use](https://wiki.openstreetmap.org/wiki/Overpass_API)
* Planet PlanetScope monthly visual basemap (via NICFI) - [Terms of Use](https://developers.planet.com/docs/basemaps/tile-services/)
* Protomaps - [Terms of Use](https://protomaps.com/faq)
* Stadia Maps - [Terms of Use](https://docs.stadiamaps.com/limits/)

Please see the CLI options below for information on how to leverage these API sources.

If you would like to request the addition of an online API source that is not currently supported, please [file an issue](https://github.com/ConservationMetrics/mapgl-tile-renderer/issues) or submit a PR.

Note that depending on your bounding box and maximum zoom level, this tool has the capability to send a lot of requests. You can use a utility like the [Mapbox offline tile count estimator](https://docs.mapbox.com/playground/offline-estimator/) to ensure that your request will be reasonable, and in the case of any API sources with a freemium API limit, won't end up costing you.

Expand All @@ -44,7 +48,7 @@ $ npm install -g mapgl-tile-renderer

## CLI options

* `-s` or `--style`: Specify the style source. Use "self" for a self-provided style or one of the following for an online source: "bing", "esri", "google", "mapbox", "mapbox-satellite", "planet", "protomaps"
* `-s` or `--style`: Specify the style source. Use "self" for a self-provided style or one of the following for an online source: "bing", "esri", "google", "mapbox", "mapbox-satellite", "planet", "protomaps", "stadia-alidade-satellite", "stadia-stamen-terrain"

If using a self-provided style (`--style self`):
* `--stylelocation`: Location of your provided map style
Expand Down Expand Up @@ -136,7 +140,7 @@ docker run -it --rm -v "$(pwd)":/app/outputs/ mapgl-tile-renderer --style "mapbo

## Task worker listening to message queue

mapgl-tile-renderer can be configured as a task worker that listens for messages from a queue service, as submitted via [map-packer](https://github.com/conservationMetrics/map-packer/). Upon retrieval of a message, the tool will initiate rendering, store the file on the container (ideally a volume mount location), and update a PostgreSQL table with the render results.
mapgl-tile-renderer can be configured as a task worker that listens for messages from a queue service, as submitted via a tool like [map-packer](https://github.com/conservationMetrics/map-packer/). Upon retrieval of a message, the tool will initiate rendering, store the file on the container (ideally a volume mount location), and update a PostgreSQL table with the render results.

To set up mapgl-tile-renderer as a task worker, deploy the Docker image and provide the following environmental variables:

Expand All @@ -161,7 +165,7 @@ For Azure Storage Queue (and other queue services in the future), mapgl-tile-ren

Note that `outputDir` likely needs to be a volume mount directory on your mapgl-tile-renderer container, so that it can be accessed by map-packer or other tools for sharing and downloading.

For more information on the complete flow wherein mapgl-tile-renderer is deployed as a task worker, see the [map-packer](https://github.com/conservationMetrics/map-packer/) documentation.
For more information on the complete flow where mapgl-tile-renderer is deployed as a task worker, see the [map-packer](https://github.com/conservationMetrics/map-packer/) documentation.

## Running with Github Actions

Expand All @@ -188,7 +192,7 @@ Three easy ways to examine and inspect the MBTiles:

## Formats other than MBTiles

In the future, we may decide to extend this tool to support creating raster tiles in a different format, such as [PMTiles](https://github.com/protomaps/PMTiles). However, for the time being, you can use tools like [go-pmtiles](https://github.com/protomaps/go-pmtiles) to convert the MBTiles outputs generated by this tool.
In the future, we may decide to extend this tool to support creating raster tiles in a different format, such as XYZ or [PMTiles](https://github.com/protomaps/PMTiles). However, for the time being, you can use tools like [tippecanoe](https://github.com/felt/tippecanoe) or [go-pmtiles](https://github.com/protomaps/go-pmtiles) to convert the MBTiles outputs generated by this tool.

# For developers

Expand All @@ -209,4 +213,4 @@ To run tests and view coverage, run:
npm run test
```

To run tests that require a Mapbox or Planet access token, create a `.env.test` file and add MAPBOX_TOKEN, PLANET_TOKEN, and PROTOMAPS_TOKEN vars with your own token. (If not provided, tests requiring these will be skipped.)
To run tests that require an access token, create a `.env.test` file and add the respective token vars (e.g. MAPBOX_TOKEN, PLANET_TOKEN, PROTOMAPS_TOKEN, STADIA_TOKEN). If not provided, tests requiring these will be skipped.
2 changes: 1 addition & 1 deletion src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { initiateRendering } from "./initiate.js";

program
.name("mapgl-tile-renderer")
.description("Render styled Maplibre GL map tiles")
.description("Render styled MapGL map tiles")
.requiredOption("-s ,--style <type>", `Specify the style source`)
.option(
"-l, --stylelocation <type>",
Expand Down
17 changes: 15 additions & 2 deletions src/download_resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,23 @@ const downloadOnlineTiles = async (
break;
case "protomaps":
sourceUrl = `https://api.protomaps.com/tiles/v3/{z}/{x}/{y}.mvt?key=${apiKey}`;
sourceAttribution = "Protomaps";
sourceName = "Protomaps © OpenStreetMap";
sourceAttribution = "Protomaps © OpenStreetMap";
sourceName = "Protomaps";
sourceFormat = "mvt";
break;
case "stadia-alidade-satellite":
sourceUrl = `https://tiles.stadiamaps.com/tiles/alidade_satellite/{z}/{x}/{y}.jpg?api_key=${apiKey}`;
sourceAttribution =
"© Stadia Maps © OpenMapTiles © OpenStreetMap © CNES, Distribution Airbus DS, © Airbus DS, © PlanetObserver (Contains Copernicus Data)";
sourceName = "Stadia Maps Alidade Satellite";
sourceFormat = "jpg";
break;
case "stadia-stamen-terrain":
sourceUrl = `https://tiles.stadiamaps.com/tiles/stamen_terrain/{z}/{x}/{y}.jpg?api_key=${apiKey}`;
sourceAttribution = "© Stadia Maps © OpenMapTiles © OpenStreetMap";
sourceName = "Stadia Maps Stamen Terrain";
sourceFormat = "jpg";
break;
default:
throw new Error("Invalid source provided");
}
Expand Down
11 changes: 7 additions & 4 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const validOnlineStyles = [
"mapbox-satellite",
"planet",
"protomaps",
"stadia-alidade-satellite",
"stadia-stamen-terrain",
];

export const validateInputOptions = (
Expand Down Expand Up @@ -71,7 +73,9 @@ export const validateInputOptions = (
(style === "mapbox" ||
style === "mapbox-satellite" ||
style === "planet" ||
style === "protomaps") &&
style === "protomaps" ||
style === "stadia-alidade-satellite" ||
style === "stadia-stamen-terrain") &&
!apiKey
) {
raiseError(`You must provide an API key for ${style}`);
Expand Down Expand Up @@ -108,7 +112,6 @@ export const validateInputOptions = (

if (
openStreetMap &&
style !== "mapbox" &&
style !== "mapbox-satellite" &&
style !== "google" &&
style !== "planet" &&
Expand Down Expand Up @@ -140,7 +143,7 @@ export const validateInputOptions = (
if (bounds !== null) {
if (bounds.length !== 4) {
raiseError(
`Bounds must be west,south,east,north. Invalid value found: ${[
`Bounds must be west,south,east,north. Invalid value found: ${[
...bounds,
]}`,
);
Expand All @@ -149,7 +152,7 @@ export const validateInputOptions = (
bounds.forEach((b) => {
if (!Number.isFinite(b)) {
raiseError(
`Bounds must be valid floating point values. Invalid value found: ${[
`Bounds must be valid floating point values. Invalid value found: ${[
...bounds,
]}`,
);
Expand Down
63 changes: 43 additions & 20 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,25 @@ const tempDir = path.join(os.tmpdir());
// Load MAPBOX_API_TOKEN from .env.test
// Create this file if wanting to test Mapbox
dotenv.config();
const { MAPBOX_TOKEN, PLANET_TOKEN, PROTOMAPS_TOKEN } = process.env;

if (!MAPBOX_TOKEN) {
console.warn(
"MAPBOX_TOKEN environment variable is missing; tests that require this token will be skipped"
);
}

if (!PLANET_TOKEN) {
console.warn(
"PLANET_TOKEN environment variable is missing; tests that require this token will be skipped"
);
}

if (!PROTOMAPS_TOKEN) {
console.warn(
"PROTOMAPS_TOKEN environment variable is missing; tests that require this token will be skipped"
);
}
const { MAPBOX_TOKEN, PLANET_TOKEN, PROTOMAPS_TOKEN, STADIA_TOKEN } = process.env;

const tokens = {
MAPBOX_TOKEN,
PLANET_TOKEN,
PROTOMAPS_TOKEN,
STADIA_TOKEN
};

Object.entries(tokens).forEach(([key, value]) => {
if (!value) {
console.warn(`${key} environment variable is missing; tests that require this token will be skipped`);
}
});

const testMapbox = skipIf(!MAPBOX_TOKEN);
const testPlanet = skipIf(!PLANET_TOKEN);
const testProtomaps = skipIf(!PROTOMAPS_TOKEN);
const testStadia = skipIf(!STADIA_TOKEN);

// Mock p-limit because it's an ESM module that doesn't work well with jest
jest.mock("p-limit", () => () => async (fn) => {
Expand Down Expand Up @@ -237,7 +233,6 @@ test("Generates MBTiles from Bing with overlay GeoJSON", async () => {
fs.unlinkSync(`${tempDir}/output.mbtiles`);
});


test("Generates MBTiles from Bing with OpenStreetMap overlay", async () => {
await initiateRendering(
"bing",
Expand Down Expand Up @@ -294,6 +289,34 @@ testMapbox("Generates MBTiles from Mapbox Satellite", async () => {
fs.unlinkSync(`${tempDir}/output.mbtiles`);
});

testStadia("Generates MBTiles from Mapbox Satellite", async () => {
await initiateRendering(
"stadia-stamen-terrain",
null,
null,
STADIA_TOKEN,
null,
null,
null,
null,
[-79, 37, -77, 38],
0,
5,
1,
"jpg",
tempDir,
"output"
);

await new Promise((resolve) => setTimeout(resolve, 3000));

// Expect output.mbtiles to be greater than 69632 bytes
const stats = fs.statSync(`${tempDir}/output.mbtiles`);
expect(stats.size).toBeGreaterThan(69632)

fs.unlinkSync(`${tempDir}/output.mbtiles`);
});

test("Generates MBTiles from Esri", async () => {
await initiateRendering(
"esri",
Expand Down

0 comments on commit 35693e1

Please sign in to comment.