Mapbox-GL Filter by Reachability
Finding out your vacation rental is too far from the beach is a letdown - symbolize and filter houses (4,389!) based on walk time to the beach in Lerici, Italy
<!DOCTYPE html>
<html>

<head>
    <!--  Include targomo core -->
    <script src="https://releases.targomo.com/core/latest.min.js"></script>
    <!--  Include mapboxgl javascript and css -->
    <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/mapbox-gl.js"></script>
    <!--  Include micro progress bar  -->
    <script src="https://targomo.com/developers/scripts/mipb.min.js"></script>
    <link href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/mapbox-gl.css" rel="stylesheet">
    <style>
        body,
        html {
            margin: 0;
            width: 100%;
            height: 100%;
        }
        #map {
            width: 100%;
            height: 100%;
        }
        #filter {
            position: absolute;
            margin: 10px;
            font-family: 'Open Sans', sans-serif;
            top: 0;
            display: flex;
            align-items: center;
            padding: 5px 8px;
            background: #fff;
        }
    </style>
</head>

<body>
    <!--  where the map will live  -->
    <div id="map"></div>
    <div id="filter">
        <div>Walk time (m)</div>
        <input type="range" min="0" max="30" value="30" name="time" step="1"
            onchange="rangevalue.value=value; filterPoints(value)" />
        <output id="rangevalue">30</output>
    </div>
    <script>
        // create targomo client
        const client = new tgm.TargomoClient('westcentraleurope', '__targomo_key_here__');
        // define map center and search source
        const lnglat = [9.918, 44.08];
        const travelTimes = [300, 600, 900, 1200, 1500, 1800];

        const attributionText = `<a href='https://targomo.com/developers/resources/attribution/' target='_blank'>&copy; Targomo</a>`

        // set the progress bar
        const pBar = new mipb({ fg: "#FF8319" });
        pBar.show();

        // add the map and set the center
        const map = new mapboxgl.Map({
            container: 'map',
            style: 'https://api.maptiler.com/maps/hybrid/style.json?key=__your_maptiler_api_key__',
            zoom: 12.5, center: lnglat, attributionControl: false,
        }).addControl(
            new mapboxgl.NavigationControl()
        ).addControl(
            new mapboxgl.AttributionControl({ compact: true, customAttribution: attributionText })
        );

        // disable scroll zoom
        map.scrollZoom.disable();

        map.on('load', () => {
            // find the first symbol layer
            const firstSymbolLayer = map.getStyle().layers.find(l => l.type === 'symbol');
            // add empty source
            map.addSource('sources', {
                "type": "geojson",
                "data": { type: 'FeatureCollection', features: [] }
            });
            map.addLayer({
                "id": "beaches",
                "type": "circle",
                "source": 'sources',
                'paint': {
                    'circle-radius': 7,
                    'circle-color': '#DAA520',
                    'circle-stroke-width': 1.5,
                    'circle-stroke-color': '#fff'
                }
            }, firstSymbolLayer.id);

            // add empty source
            map.addSource('targets', {
                "type": "geojson",
                "data": { type: 'FeatureCollection', features: [] }
            });
            // reachable houses
            map.addLayer({
                'id': 'houses-reachable',
                'type': 'circle',
                'source': 'targets',
                'filter': ['<', 'travelTime', 30 * 60],
                'paint': {
                    'circle-radius': {
                        'base': 1.75,
                        'stops': [[12, 2], [22, 180]]
                    },
                    'circle-color': {
                        'property': 'travelTime',
                        'stops': [
                            [360, '#1a9641'],
                            [720, '#a6d96a'],
                            [1080, '#ffffbf'],
                            [1440, '#fdae61'],
                            [1800, '#d7191c']
                        ]
                    },
                }
            }, 'beaches');
            // unreachable houses, symbolized gray
            map.addLayer({
                'id': 'houses-unreachable',
                'type': 'circle',
                'source': 'targets',
                'filter': ['>=', 'travelTime', 30 * 60],
                'paint': {
                    'circle-radius': {
                        'base': 1.75,
                        'stops': [[12, 2], [22, 180]]
                    },
                    'circle-color': '#aaa'
                }
            }, 'houses-reachable');

            // go get the actual data
            dataInit();
        })

        async function dataInit() {
            const beachurl = 'https://raw.githubusercontent.com/targomo/data-exports/master/overpass/beach_public_lerici.geojson';
            const houseurl = 'https://raw.githubusercontent.com/targomo/data-exports/master/overpass/building_house_lerici.geojson';
            
            // get OSM public beaches dataset
            const beaches = await fetch(beachurl).then(async (data) => {
                return await data.json();
            })

            // update map
            map.getSource('sources').setData(beaches);

            // get OSM building=house dataset
            const houses = await fetch(houseurl).then(async (data) => {
                return await data.json();
            })

            // update map
            map.getSource('targets').setData(houses);

            // calculate reachability of houses from beaches
            getReachability(beaches, houses);
        }

        function getReachability(beaches, houses) {
            // create formatted 'sources' for analysis
            const sources = beaches.features.map((beach) => {
                return {
                    id: beach.properties['@id'],
                    lat: beach.geometry.coordinates[1],
                    lng: beach.geometry.coordinates[0]
                }
            });

            // create formatted 'targets' for analysis
            const targets = houses.features.map((house) => {
                return {
                    id: house.properties['@id'],
                    lat: house.geometry.coordinates[1],
                    lng: house.geometry.coordinates[0]
                }
            });

            // define some options for the reachability service
            const options = {
                travelType: 'walk',
                maxEdgeWeight: 30 * 60,
                edgeWeight: 'time'
            };

            // calculate reachability of houses from beaches
            client.reachability.locations(sources, targets, options).then((reachability) => {
                // assign reachability to original GeoJSON
                houses.features.forEach((house) => {
                    const match = reachability.find((location) => {
                        return location.id === house.properties['@id']
                    });
                    house.properties.travelTime = match ? match.travelTime : 30 * 60 + 1;
                });
                // update source with travel-time augmented data
                map.getSource('targets').setData(houses);
                pBar.hide();
            })
        }

        function filterPoints(minutes) {
            // update the map filter to update symbolization based on range value
            map.setFilter('houses-reachable', ['<', 'travelTime', minutes * 60]);
            map.setFilter('houses-unreachable', ['>=', 'travelTime', minutes * 60 + 1]);
        }
    </script>
</body>

</html>
content_copy
Copied to clipboard