Leaflet JS Shadow Simulator

Yesterday I published the first version 0.1.0 of leaflet-shadow-simulator on NPM. Today I will show you how to simulate shadows on some popular online maps. I will use a Bookmarklet to inject leaflet-shadow-simulator into these maps. Let’s begin.

Sun hits trail camp at 7:00

Sun hits Trail Camp around 7am

A favorite newcomer in the online mapping scene is Felt. Felt allows you to build data-rich collaborative maps and best of all, it’s based on Leaflet JS. Once you create an account, you are given some example maps and one of them is the Mount Whitney summit hike. It outlines the trail and a camping location where you will spend the first night. If you’re like me and have a hard time using your fingers when they’re cold, you probably don’t want to get up until the sun warms your tent. But when will that be? We can find out by injecting leaflet-shadow-simulator into the map.

The following 50 lines of code

javascript: (() => {
    // load leaflet-shadow-simulator
    var script = document.createElement('script');
    script.src = 'https://unpkg.com/leaflet-shadow-simulator@0.1.0/dist/leaflet-shadow-simulator.umd.min.js';
    document.body.appendChild(script);
    script.addEventListener('load', () => {

        // configure leaflet-shadow-simulator
        const today = new Date();
        const shadeMap = L.shadeMap({
            apiKey: "eyJhbGciOiJIUzI1NiJ9.eyJlbWFpbCI6InRwcGlvdHJvd3NraUBzaGFkZW1hcC5hcHAiLCJjcmVhdGVkIjoxNjYyNDkzMDY2Nzk0LCJpYXQiOjE2NjI0OTMwNjZ9.ovCrLTYsdKFTF6TW3DuODxCaAtGQ3qhcmqj3DWcol5g",
            date: today,
            color: '#01112f',
            opacity: 0.7,
            terrainSource: {
                maxZoom: 15,
                tileSize: 256,
                getSourceUrl: ({ x, y, z }) => `https://s3.amazonaws.com/elevation-tiles-prod/terrarium/${z}/${x}/${y}.png`,
                getElevation: ({ r, g, b, a }) => (r * 256 + g + b / 256) - 32768,
            }
        }).addTo(window.felt.leafletMap);

        // Create time slider control
        const div = document.createElement('div');
        div.innerHTML = `
<div style="position:absolute; bottom:125px; left:50%; width:400px; margin-left:-200px; background-color:white; padding:10px;border-radius:16px;">
<input id="shadow-slider" type="range" min="0" max="1440" style="width:100%" value="${today.getHours() * 60 + today.getMinutes()}" />
<span id="shadow-date">${today.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZoneName: 'long' })}</span>
<span id="shadow-loader" style="float:right">0%</span>
</div>`;
        document.body.append(div);
        const shadowLoader = document.getElementById('shadow-loader');
        shadeMap.on('tileloaded', (loadedTiles, totalTiles) => {
            const percent = Math.floor((loadedTiles / totalTiles) * 100);
            shadowLoader.innerText = (percent >= 100) ? `${totalTiles} tiles loaded` : `loading: ${percent}%`;
        })
        const shadowDate = document.getElementById('shadow-date');
        const shadowSlider = document.getElementById('shadow-slider');
        shadowSlider.addEventListener('input', (e) => {
            const value = parseInt(e.target.value);
            const newDate = new Date(today);
            newDate.setMinutes(value - (Math.floor(value / 60) * 60));
            newDate.setHours(Math.floor(value / 60));
            newDate.setSeconds(0);
            shadowDate.innerText = newDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', timeZoneName: 'long' });
            shadeMap.setDate(newDate);
        });
    })
})();

One thing to note is that Felt exposes their Leaflet map object under the variable window.felt.leafletMap. The shadow simulator needs access to the map object in order to add the shadow layer to the map.

Screen Shot 2022-09-13 at 10.20.52 AM.png

Firefox bookmarklet

For best results you’ll want to minify this code. I used this minifier. Then, create a bookmark in your browser and copy the minified code into the URL field. Finally, open any Felt map and click the bookmark to simulate terrain shadows on the map.

felt-demo.gif

I did notice that Felt incorrectly fires the moveend event while a user is still dragging the map, causing ShadeMap to glitch occasionally :(. Fortunately, there is another Leaflet JS based map I use to plan outdoor adventures and that’s CalTopo. CalTopo exposes their Leaflet map object under the variable window.map.map so you just need to replace window.felt.leafletMap with window.map.map in the example above and you should see this:

Screen Shot 2022-09-13 at 10.38.38 AM.png

Leaflet Shadow Simulator and CalTopo

For updates or answers to questions, find me on Twitter.

If you just want to play with the shadow simulator check out shademap.app.

 
6
Kudos
 
6
Kudos

Now read this

ShadeMap 2021 Wrap Up

Development # A big change happened this fall. Right around the one-year mark I finally succeeded in migrating Shade Map (shademap.app) onto a vector based map. Up until this point I used tiled image maps which downloaded many small,... Continue →