Query API and Web Services

  1. Home
  2. Query API and Web Services
  3. Create a Webmap using Koordinates tiles and Mapbox GL

Create a Webmap using Koordinates tiles and Mapbox GL

This guide introduces the combination of WMTS tile layers from a Koordinates site into a map using the Mapbox GL web mapping framework.

The code we're using in this guide has been used to make a webmap, published on Codepen here. Our aim with this webmap is to allow users to click on the map to show information about the closest earthquake.

Prerequisites:

  • An API key from your Koordinates account. It should have the "Query layer data" and "Access cartographic tile images (WMTS)" scopes.
Please note that Mapbox GL (and this example code) don't support IE11 and older browsers. 

HTML/CSS

The HTML in the example contains:

  • imports of the Mapbox GL javascript and stylesheet
  • the Mapbox GL map container
  • a template for our example popup content

This is similar to the introductory Mapbox GL example. You can also load the Mapbox GL library via your module bundler (npm/webpack).

The CSS in the example contains the sizing for the map container, and some simple styling for the popup.

Javascript

The first step is to create the default map, and specify the Mapbox style that we want to use as a base map, and the initial centre ([Longitude, Latitude]) and zoom level.

var map = new mapboxgl.Map({
    container: "map", // container id
    style: "mapbox://styles/mapbox/outdoors-v11",
    center: [175, -41],
    zoom: 6
});

In this example, we want to insert our data between the landuse/landcover layers and the symbol layers (roads, POIs, labels) in the map. We do this by finding the first symbol layer of the basemap style:

// Find the ID of the first symbol layer in the map style
// We use this to insert our layer beneath roads/pois/labels/etc
var firstSymbolId = map.getStyle().layers.find(l => (l.type === "symbol")).id;

The next step is to define the Koordinates layer as a raster layer and add it to the map. The tiles entry comes from the Koordinates layer's 'Services' tab as the zoom / x / y template.

Remember to insert your own API key, Layer ID, and Style ID in the URL

eg. https://koordinates-tiles-a.global.ssl.fastly.net/services;key={YOURAPIKEY}/tiles/v4/layer={layerID}/EPSG:3857/{z}/{x}/{y}.png for the layer's default style,

or https://koordinates-tiles-a.global.ssl.fastly.net/services;key={YOURAPIKEY}/tiles/v4/layer={layerID},style={styleID}/EPSG:3857/{z}/{x}/{y}.png for a custom style.

map.addLayer({
    id: "koordinates-earthquakes",
    type: "raster",
    source: {
        type: "raster",
        tiles: [
            // This is the XYZ template URL from the Koordinates layer
            // services page: https://labs.koordinates.com/layer/7328-new-zealand-earthquakes/webservices/
            "https://koordinates-tiles-a.global.ssl.fastly.net/services;key=50f747d8d7b04646b682cb5efb10f3f8/tiles/v4/layer=7328/EPSG:3857/{z}/{x}/{y}.png"
            ],
            tileSize: 256,
            maxzoom: 22,
            attribution: "<a href='https://labs.koordinates.com/layer/7328-new-zealand-earthquakes/'>New Zealand Earthquakes</a>"
        }
    },
    firstSymbolId // Insert the layer beneath the first symbol layer.
);

The attribution property contains a link to the data source and license information. This is important for third party layers which often require attribution as part of their license.

Now the Koordinates layer will appear on the map. Next up, query the Koordinates Vector Query API when the user clicks on the map and display a popup with the closest feature's information. We start with the click event from the Mapbox map:

var quakeMarker;
map.on("click", function(e) {
    // construct the query url
    let queryUrl = new URL('https://labs.koordinates.com/services/query/v1/vector.json');
    // documentation of parameters at https://help.koordinates.com/api/query-api/vector-query/
    let queryParams = {
        key: KOORDINATES_API_KEY,  // Koordinates API Key
        layer: 7328,                              // Koordinates Layer ID
        x: e.lngLat.lng,                          // map click location
        y: e.lngLat.lat,
        max_results: 1,                           // find the closest result
        radius: 1000,                             // max. search distance 1km
        geometry: true                            // Include geometry in the response
    };
    queryUrl.search = new URLSearchParams(queryParams);

    // make the API request
    fetch(queryUrl)
        .then(function(response) {
            if (!response.ok) {
                throw new Error("HTTP error, status = " + response.status);
            }
            return response.json();
        })
        .then(function(response) {
            // extract the first feature from the response
            const feature = response.vectorQuery.layers['7328'].features[0];
            console.log("Feature:", feature);

            if (feature === undefined) {
                // no features within 1km
                if (quakeMarker) {
                    // remove marker from map
                    quakeMarker.remove();
                    quakeMarker = null;
                }
                return;
            }

            // position map marker
            if (!quakeMarker) {
                // add marker
                quakeMarker = new mapboxgl.Marker()
                    .setLngLat(feature.geometry.coordinates)
                    .addTo(map);
            } else {
                // move existing marker
                quakeMarker.setLngLat(feature.geometry.coordinates);
            }

            // create popup content
            let popupTemplate = document.getElementById('popup-template');
            let popupContent = document.importNode(popupTemplate.content, true);
            popupContent.querySelector('.origintime').textContent = feature.properties.origintime;
            popupContent.querySelector('.magnitude').textContent = feature.properties.magnitude;
            popupContent.querySelector('.depth').textContent = feature.properties.depth;

            // open map popup
            let popup = new mapboxgl.Popup({anchor: 'left', offset: 10})
                .setDOMContent(popupContent)
                .setLngLat(feature.geometry.coordinates)
                .addTo(map);
        });
    });
});

The response from the vector query API is a GeoJSON FeatureCollection for each queried layer, sorted in order of distance from the query location, within the specified radius, and limited to max_results features per layer.

First we extract the closes GeoJSON Feature from the response, then we create or update a Marker on the map in the feature's position. Then we create a Popup showing information formatted from the feature's properties.

And there we have it — our working webmap built from Koordinates tile services!