Saturday, May 25, 2019

Playing with Mapbox Vector Tiles (Part 3 - Using Mabox GL)


On this post I'm going to pick-up what I've described on my previous two posts and creating a demo of Mapbox Vector Tiles integrated with Mapbox GL.

I'll be more or less recreating what I did on this experiment: http://psousa.net/demos/maps-and-boardgames-part-2/demo3.html


That old experiment used:
  • TileMill to create the raster tiles
  • UTFGrid tiles to provide meta-information on the hexagons
  • Leaflet as the mapping technology
This new one uses:
  • Tipecanoe to generate the vector tiles (from geojson sources)
  • Mapbox GL JS as the mapping technology
The tricky bit is making sure that the helicopters snap to the displayed hexagons, ideally leveraging the vector data that's present on the tiles.

With that said, I'm actually going to start with the end-result, followed by the break-down on how it's built.


I didn't try to replicate the experience at 100%, but it's close enough.

Live demo at: http://psousa.net/demos/vector/part3/

How this was done:

Step 1: Creating the vector tiles

This map has two layers: a base-map with the various countries, like what I did on my previous point and an hexagon layer.

Creating the geojson for the countries layer.

I'll summarize the steps I did for my previous post:
  • Downloaded the file from: https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip
  • Converted it to Geojson (I've used QGIS)
Creating the geojson for the hexagon layer
  • Used my own tool at: https://github.com/pmcxs/world-hexagon-map
    • It's not the scope of this post to explain how I've generated the Geojson layer with hexagons. For now, I'll simply include the end-result on the sample repo
Using tippecanoe to generate the vector tiles, passing both geojson files as arguments

(please see my last post on how to install/use tippecanoe)

tippecanoe -o world.mbtiles -z6 world.geojson hexagons.geojson

Step 2: Creating the Mapbox GL JS map

I won't explain the code in detail (you can see it at: https://github.com/pmcxs/blog-experiments/tree/master/vector-tiles-part-3).

As such, I'll try to keep all my new experiments in Github and leveraging the README there, instead of pasting all the instructions on the blog post.

I'll highlight a couple of key points/features though:
  • The helicopters can be dragged. When dropping them, they'll snap to the hexagon's center
  • This operation actually uses the real data in the Vector Tiles
  • The helicopters have two level of detail icons
  • The zoom level is restricted between zoom level 3 and 6
From an API Point-of-view Mapbox GL JS isn't as mature as the likes of Leaflet or OpenLayers, but its performance makes up for it.

The main code is actually quite small:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>Simple Hexagon Map</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.54.0/mapbox-gl.js'></script>
    <script src="hexagons.js"></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.54.0/mapbox-gl.css' rel='stylesheet' />
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
    </style>
</head>
<body>

    <div id='map'></div>

    <script>
        var map = new mapboxgl.Map({
            container: 'map',
            style: 'server/style.json',
            center: [0, 42],
            maxZoom: 6.0,
            minZoom: 3.0,
            zoom: 4.5
        });

        var markers = [];

        map.on('load', function () {
            addHelicopter(166, 33);
            addHelicopter(170, 30);
            addHelicopter(175, 20);
            addHelicopter(180, 20);

            updateHelicoptersVisibility(map.getZoom());
        });

        map.on('zoom', function () {
            updateHelicoptersVisibility(map.getZoom());
        });

        function addHelicopter(u, v) {

            var hexagon = new HexDefinition(500);
            var centerPoint = hexagon.getCenterPointXY(u, v);
            var centerCoordinate = hexagon.pixelXYToLatLong(centerPoint.x, centerPoint.y, 10);

            var el = document.createElement('div');
            el.className = 'marker';

            var marker = new mapboxgl.Marker(el)
                .setLngLat([centerCoordinate.longitude, centerCoordinate.latitude])
                .setDraggable(true)
                .addTo(map);

             marker.on('dragend', function (e) {

                var coordinate = e.target.getLngLat();
                var point = map.project(coordinate);
                var features = map.queryRenderedFeatures(point, { layers: ['hexagons'] });

                if (features.length > 0) {

                    var u = features[0].properties["U"];
                    var v = features[0].properties["V"];
                    var centerPoint = hexagon.getCenterPointXY(u, v);
                    var centerCoordinate = hexagon.pixelXYToLatLong(centerPoint.x, centerPoint.y, 10);
                    e.target.setLngLat([centerCoordinate.longitude, centerCoordinate.latitude]);
                }
            });

            markers.push(marker);
        }

        function updateHelicoptersVisibility(zoomLevel) {
            for (var i = 0; i < markers.length; i++) {
                var element = markers[i].getElement();

                if (zoomLevel > 4.7) {

                    element.style.backgroundImage = 'url(heli_1.png)';
                    element.style.width = '64px';
                    element.style.height = '64px';
                }
                else {

                    element.style.backgroundImage = 'url(heli_2.png)';
                    element.style.width = '32px';
                    element.style.height = '32px';
                }
            }
        }
    </script>
</body>
</html>

No comments:

Post a Comment