MapBox

MapBox is a private sector mapping platform similar to OpenLayers or OpenStreetMap. It offers a wide variety of services including basemaps, navigation, mobile SDKs and for our purposes here - a desktop API for presenting maps in web pages.

Data can be consumed by the API in a number of formats such as GeoJSON or from a WFS. It can present vector and raster data. I have not used in anger yet, but it appears to have a reasonable free tier for smaller sites.

Links

MapBox Documentation
Python SDK
Guidance on Tokens


Basic Map Call

In this simple example we present a map and add markers manually. This is a very simple example to demonstrate the basics before moving on to more complex stuff.

We use HTMX to display the map and send in a trigger to initialise the map after the partial has setted in the dom.

    
document.body.addEventListener("initMap1", function(evt){

    mapboxgl.accessToken = '***'

    const map = new mapboxgl.Map({
        container: 'map1',
        style: 'mapbox://styles/mapbox/standard',
        center: [12.550343, 55.665957],
        zoom: 8
    });

    // Create a default Marker and add it to the map.
    const marker1 = new mapboxgl.Marker()
        .setLngLat([12.554729, 55.70651])
        .addTo(map);

    // Create a default Marker, colored black, rotated 45 degrees.
    const marker2 = new mapboxgl.Marker({ color: 'black', rotation: 45 })
        .setLngLat([12.65147, 55.608166])
        .addTo(map);

})

## TEMPLATE SNIPPET ***
## Note for flex, the min requirement is to set the height of the map otherwise does not appear.
<div id='map1' class="map shadow-dark" style='height: 300px;' ></div>

## DJANGO VIEW ###

# Set header to trigger JS init map when loading returning the map partial 
response["HX-Trigger-After-Settle"] = "initMap1"
       

Accessing External Data

In this example, from the MapBox site, we pull in geojosn from a public endpoint. First, the source of the data is defined and then this data is presented on a layer.

    
const map = new mapboxgl.Map({
	container: 'map2',
	style: 'mapbox://styles/mapbox/standard',
	center: [-74.0060152, 40.7127281],
	zoom: 5,
	maxZoom: 6
});

map.on('style.load', () => {
	map.addSource('urban-areas', {
		'type': 'geojson',
		'data': 'https://docs.mapbox.com/mapbox-gl-js/assets/ne_50m_urban_areas.geojson'
	});

	map.addLayer({
		'id': 'urban-areas-fill',
		'type': 'fill',
		'slot': 'middle',
		'source': 'urban-areas',
		'layout': {},
		'paint': {
			'fill-color': '#f08',
			'fill-opacity': 0.4
		}
	});
});
       

Accessing Data from Geoserver

In this example data is pulled in from the geoserver instance at fearnought.club.

Point data is retrieved as geojson and the paint attribute is used to give colours to selected classes. The data presented is limited to the first 750 features to ensure a reasonable response time.

    
    const apiEndPoint = new URL("https://fearnought.club/geoserver/fearnought_sheffield/ows");

    const parameters = {
        service: 'WFS',
        version: '1.1.0',
        request: 'GetFeature',
        typeName: 'fearnought_sheffield:events_2024_projected',
        maxFeatures: 1000,
        outputFormat: 'application/json',
        SrsName : 'EPSG:4326'
    };
    
    const searchParams = new URLSearchParams(parameters)
    
    apiEndPoint.search = searchParams   
    const finalURL = apiEndPoint.toString()
    console.log(finalURL)

    const map = new mapboxgl.Map({
        container: 'map3',
        style: 'mapbox://styles/mapbox/standard',
        center: [-1.469754, 53.383525],
        zoom: 8,
        maxZoom: 15
    });


    map.on('style.load', () => {
        map.addSource('sheffield-crime', {
            'type': 'geojson',
            'data': finalURL,
        });

        map.addLayer({
            'id': 'sheffield-crime',
            'type': 'circle',
            'slot': 'top',
            'source': 'sheffield-crime',
            'layout': {
                'visibility': 'visible',
            },
            "paint": {
                "circle-color": [
                "match",
                ["get", "Crime_type"],
                "Criminal damage and arson", "#fbb03b",
                "Vehicle crime", "#223b53",
                "Violence and sexual offences", "#e55e5e",
                "Other crime", "#ccc",
                "black" // Default color for other values
                ]
            }

        });
    });
       

External Data WMS

In this example the GetMap service is used from the a geoserver WMS. This returns an image, in this case a png. This requires a bounding box for both the WMS call and the setup of the mapBox source (hardocded in this example).

NOTE: for Images, the bbox coordinates need to be provided in [long, lat] pairs in order [top-left, top-right, bottom-right, bottom-left].

    
const apiEndPoint = new URL("https://fearnought.club/geoserver/fearnought_enschede/wms");

const parameters = {
    service: 'WMS',
    version: '1.1',
    request: 'GetMap',
    layers: 'fearnought_enschede:geo_enschedeboundary',
    styles : '',
    format: 'image/png',
    srs : 'EPSG:4326',
    width : 780,
    height : 330,
    bbox: "6.7642530835571835, 52.161286767163396, 6.9900289975756476, 52.288491432943324",
    transparent : true,

};

const searchParams = new URLSearchParams(parameters)

apiEndPoint.search = searchParams   
const finalURL = apiEndPoint.toString()
console.log(finalURL)

const map = new mapboxgl.Map({
    container: 'map4',
    style: 'mapbox://styles/mapbox/standard',
    center: [6.895493, 52.220333],
    zoom: 8,
    maxZoom: 15
});

map.on('style.load', () => {
    map.addSource('enschede-boundary', {
        'type': 'image',
        'url': finalURL,
        'coordinates': [
            [6.7642530835571835, 52.288491432943324],
            [6.9900289975756476, 52.288491432943324],
            [6.9900289975756476, 52.161286767163396],
            [6.7642530835571835, 52.161286767163396],
        ]
    });
    map.addLayer({
        id: 'enschede-boundary',
        'type': 'raster',
        'source': 'enschede-boundary',
    });

});
       

Accessing tilesets from MapBox Studio

MapBox offers a UI to build custom styles. Data can be uploaded as vector or raster. These have to be converted to tilesets for display in a map (this is mandatory for them to be displayed).

In this example a boundary layer for Enschede was uploaded as a geojson and then converted to a tileset. Likewise for an example buildings layer. The latter will only display on zoom levels of +13. Zoom-related style can be applied, which in this case is to make the buildings appear darker as we zoom in.

    
    const map = new mapboxgl.Map({
        container: 'map5',
        
        // Custom style
        style: 'mapbox://styles/craigholl/cmcx8ak7p00rg01sb5e2633eg',
        center: [6.895493, 52.220333],
        zoom: 10
    });