3D Buildings on worldmap

Hi there.

i was checking the index3d.html in node red and after i have done my research i have found out that i require an API key from mapbox.
i have created the API Key and created as well a style in mapbox, and i have added them in the index3d.html, however when i would like to access it the file still shows me a blank page, i have even tried to remove the api key in order for me to receive the alert shown in the code that will tell me that i need to insert the API key but with no luck, after a little bit of reading i saw that someone was able to do it by creating a new API key, i have done that and refreshed the old API key as well with no luck.

can anyone provide an help in my case?

@dceejay could i kindly request your help on this matter?

Yes you can ask but will have to wait until I am back in front of a screen.

thanks for your reply.

as i have mentioned earlier i have an issue in displaying the map in the 3D view.

i have went through the instructions provided:
1- created an account in mapbox
2- created an API key
3- edited the index3d.html file and added the API key and un-commented the line underneath it "var mbstyle = 'mapbox://styles/mapbox/streets-v10';"

however when i open the index3d.html in the browser using https://localhost:1880/worldmap/index3d.html i still get a blank page.

moreover i have created a style in mapbox and added the style in the code but again with no luck.

i dont know what i am doing wrong as i am always reaching a dead-end

the index3d.html code is the following, appreciate your help on this matter :

<!DOCTYPE html>
<html>
<head>
    <title>Node-RED 3D Map all the Things</title>
    <meta charset='utf-8' />
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no'/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <script src="leaflet/sockjs.min.js"></script>
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/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>

// TO MAKE THE MAP APPEAR YOU MUST ADD YOUR ACCESS TOKEN FROM https://account.mapbox.com
// This needs to be set as an environment variable MAPBOXGL_TOKEN available to the Node-RED session on your server
mapboxgl.accessToken = '';

var people = {};
var mbstyle = 'mapbox://styles/mapbox/streets-v10';
// var mbstyle = 'mapbox://styles/mapbox/light-v10';

fetch('/-worldmap3d-key')
    .then(response => response.json())
    .then(data => {
        mapboxgl.accessToken = data.key;
        if (mapboxgl.accessToken === "") {
            alert("To make the map appear you must add your Access Token from https://account.mapbox.com by setting the MAPBOXGL_TOKEN environment variable on your server.");
            return;
        }

        var map = new mapboxgl.Map({
            container: 'map',
            style: mbstyle,
            center: [-1.3971, 51.0259],
            zoom: 16,
            pitch: 40,
            bearing: 20,
            attributionControl: true
        });

        map.on('load', function() {
            var layers = map.getStyle().layers;
            var firstSymbolId;
            for (var i = 0; i < layers.length; i++) {
                if (layers[i].type === 'symbol') {
                    firstSymbolId = layers[i].id;
                    break;
                }
            }

            /// Add the base 3D buildings layer
            map.addLayer({
                'id': '3d-buildings',
                'source': 'composite',
                'source-layer': 'building',
                'filter': ['==', 'extrude', 'true'],
                'type': 'fill-extrusion',
                'minzoom': 15,
                'paint': {
                    'fill-extrusion-color': '#ddd',
                    'fill-extrusion-height': [
                        "interpolate", ["linear"], ["zoom"],
                        15, 0, 15.05, ["get", "height"]
                    ],
                    'fill-extrusion-base': [
                        "interpolate", ["linear"], ["zoom"],
                        15, 0, 15.05, ["get", "min_height"]
                    ],
                    'fill-extrusion-opacity': .4
                }
            }, firstSymbolId);

            // ---- Connect to the Node-RED Events Websocket --------------------

            var connect = function() {
                ws = new SockJS(location.pathname.split("index")[0] + 'socket');
                ws.onopen = function() {
                    console.log("CONNECTED");
                    // if (!inIframe) {
                    //     document.getElementById("foot").innerHTML = "<font color='#494'>"+pagefoot+"</font>";
                    // }
                    ws.send(JSON.stringify({action:"connected"}));
                };
                ws.onclose = function() {
                    console.log("DISCONNECTED");
                    // if (!inIframe) {
                    //     document.getElementById("foot").innerHTML = "<font color='#900'>"+pagefoot+"</font>";
                    // }
                    setTimeout(function() { connect(); }, 2500);
                };
                ws.onmessage = function(e) {
                    var data = JSON.parse(e.data);
                    //console.log("GOT",data);
                    if (Array.isArray(data)) {
                        //console.log("ARRAY");
                        for (var prop in data) {
                            if (data[prop].command) { doCommand(data[prop].command); delete data[prop].command; }
                            if (data[prop].hasOwnProperty("name")) { setMarker(data[prop]); }
                            else { console.log("SKIP A",data[prop]); }
                        }
                    }
                    else {
                        if (data.command) { doCommand(data.command); delete data.command; }
                        if (data.hasOwnProperty("name")) { setMarker(data); }
                        else { console.log("SKIP",data); }
                    }
                };
            }
            console.log("CONNECT TO",location.pathname + 'socket');
            connect();

            var doCommand = function(c) {
                console.log("CMD",c);
                // Add our own overlay geojson layer if necessary
                if (c.hasOwnProperty("map") && c.map.hasOwnProperty("geojson") && c.map.hasOwnProperty("overlay")) {
                    addGeo(c.map.overlay,c.map.geojson);
                }
                var clat,clon;
                if (c.hasOwnProperty("lat")) { clat = c.lat; }
                if (c.hasOwnProperty("lon")) { clon = c.lon; }
                if (clat && clon) { map.setCenter([clon,clat]); }
                if (c.hasOwnProperty("zoom")) { map.setZoom(c.zoom); }
                if (c.hasOwnProperty("pitch")) { map.setPitch(c.pitch); }
                if (c.hasOwnProperty("bearing")) { map.setBearing(c.bearing); }
            }

            var addGeo = function(o,g) {
                map.addLayer({
                    'id': o,
                    'type': 'fill-extrusion',
                    'source': {
                        'type': 'geojson',
                        'data': g
                    },
                    'paint': {
                        // https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions
                        'fill-extrusion-color': ['get', 'color'],
                        'fill-extrusion-height': ['get', 'height'],
                        'fill-extrusion-base': ['get', 'base_height'],
                        'fill-extrusion-opacity': 0.5
                    }
                });
            }

            var setMarker = function(d) {
                if (d.hasOwnProperty("area")) { return; }  // ignore areas for now.
                console.log("DATA",d);
                if (people.hasOwnProperty(d.name)) {
                    map.getSource(d.name).setData(getPoints(d)); // Just update existing marker
                }
                else {  // it's a new thing
                    people[d.name] = d;
                    map.addLayer({
                        'id': d.name,
                        'type': 'fill-extrusion',
                        'source': {
                            'type': 'geojson',
                            'data': getPoints(d)
                        },
                        'paint': {
                            // https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions
                            'fill-extrusion-color': ['get', 'color'],
                            'fill-extrusion-height': ['get', 'height'],
                            'fill-extrusion-base': ['get', 'base_height'],
                            'fill-extrusion-opacity': 1
                        }
                    },firstSymbolId);
                }
            }

            var lookupColor = function(s) {
                var c = s.charAt(1);
                if (c === "F") { return "#79DFFF"; }
                if (c === "N") { return "#A4FFA3"; }
                if (c === "U") { return "#FFFF78"; }
                if (c === "H") { return "#FF7779"; }
                if (c === "S") { return "#FF7779"; }
            }

            // create the points for the marker and return the geojson
            var getPoints = function(p) {
                var fac = 0.000007;   // basic size for bock icon in degrees....
                var thing = "";
                if (p.hasOwnProperty("icon")) {
                    if (p.icon.indexOf("male") !== -1) { thing = "person"; }
                }
                p.SDIC = p.SIDC || p.sidc;
                if (p.hasOwnProperty("SIDC")) {
                    if (p.SIDC.indexOf("SFGPU") !== -1) { thing = "person"; }
                    if (p.SIDC.indexOf("SFGPUC") !== -1) { thing = "block"; }
                    if (p.SIDC.indexOf("GPEV") !== -1) { thing = "block"; }
                    p.iconColor = lookupColor(p.SIDC);
                }
                var t = p.type || thing;
                var base = p.height || 0;
                if (t === "person") { tall = 3; }                       // person slightly tall and thin
                else if (t === "bar") { base = 0; tall = p.height; }    // bar from ground to height
                else if (t === "block") { fac = fac * 4; tall = 5; }    // block large and cube
                else { tall = 2; fac = fac * 2; }                       // else small cube
                //console.log({p},{t},{fac},{base},{tall});
                var sin = 1;
                var cos = 0;
                p.hdg = Number(p.hdg || p.heading);
                if (p.hasOwnProperty("hdg") && !isNaN(p.hdg)) {
                    sin = Math.sin((90 - p.hdg) * Math.PI / 180);
                    cos = Math.cos((90 - p.hdg) * Math.PI / 180);
                }
                var dx = 1 * cos - 1 * sin;
                var dy = 1 * sin + 1 * cos;
                var d = {
                    "type": "Feature",
                    "properties": {
                        "name": p.name,
                        "type": t,
                        "color": p.iconColor || "#910000",
                        "height": base + tall,
                        "base_height": base
                    },
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [
                            [
                                [ p.lon + (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dy) ],
                                [ p.lon - (fac * dy ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dx) ],
                                [ p.lon - (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat - (fac * dy) ],
                                [ p.lon + (fac * dy ) / Math.cos( Math.PI / 180 * p.lat ), p.lat - (fac * dx) ],
                                [ p.lon + (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dy) ],
                            ]
                        ]
                    }
                }
                return d;
            }

            document.addEventListener ("keydown", function (ev) {
                if (ev.ctrlKey  &&  ev.altKey  &&  ev.code === "Digit3") {
                    ws.close();
                    //window.onbeforeunload = null;
                    window.location.href = "index.html";
                }
            });

});

})
.catch(error => { console.log("Unable to fetch MAPBOXGL_TOKEN.",error)} );

</script>
</body>
</html>

And have you set your key into the environment variable as requested ?
on linux etc ... export MAPBOXGL_TOKEN=your_big_long_token then start node-red in that same session.

Dear Dceejay.

I'm afraid i did not do that, can you guide me on how i can make it?

I am using linux mint as os hosting node red

i know that i have to run the command that you have provided in your earlier answer but i dont know what is the correct path required to run this command in shall i do it in /home/user/.nodered? or in /etc/?

it doesn't matter as long as you then launch node-red from the same console (ie using the same environment)

Dear dceejay

i have performed the command as instructed above as per the below screenshot


as you see i can echo it so the export process has been performed successfully, and my index3d.html looks like the following image:

however the result of the following is still the same as per the below image:

is there is anything wrong that i have done so far? (never mind about the token published i can change it later on)

Sorry out for dinner. Back late tomorrow

sure no problem, this could wait till tomorrow

Bon apetit

If I run (on linux)

export MAPBOXGL_TOKEN=pl.ey..... your token
node-red

with the index3d.html file as shipped (no edits) - it works fine for me.

1 Like

Can you please tell me exactly where you have ran the export command? Maybe show some screenshots that i can follow?

That would be really appreciated as i am following all steps correctly but still getting the blank page

I ran them exactly as posted above

cj : ~/Projects/node-red (dev) $ export MAPBOXGL_TOKEN=pk.eyJ1IjoiY29uZmxpY3QiLCJhIjoiY2wwYzN1bjZ6MDNyZzNjcDJyamkxN3Y2ciJ9.s2MS0tiYJrGJlJl_REieqg
cj : ~/Projects/node-red (dev) $ node-red test3d.json

NOTE I was using flow named test3d.json just for testing this... nothing in there apart from a worldmap node.

Which version of the worldmap node are you using ? - Have you undone any edits you had to the index3d.html file ?

Hey Dceejay.

i am using worldmap version 2.27.3

i am using the original index3d.html file that came with it... all i did is that i have added my token to it.

any settings that i should be doing in the worldmap node? like changing the web path to "/worldmap/index3d.html" or shall i keep it "/worldmap"???

What do you mean you added your token to it ? you don't edit the file at all - you only need to do the export.
No nothing to do in the worldmap node config itself...
Yes you have to point your browser to /worldmap/index3d.html

Does just browsing to the normal 2d map work at /worldmap ?

yes using the web path /worldmap browse me to the normal 2d map.

i have a backup copy of the index3d.html that i will be using now and i will perform the export again and let you know the findings.

Dear dceejay.

the below steps have been done from scratch:

Step 1: i have revert back to the original file of index3d.html as requested (no edits whatsoever), didnt event add any letter.

<!DOCTYPE html>
<html>
<head>
    <title>Node-RED 3D Map all the Things</title>
    <meta charset='utf-8' />
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no'/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <script src="leaflet/sockjs.min.js"></script>
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v2.6.1/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>

// TO MAKE THE MAP APPEAR YOU MUST ADD YOUR ACCESS TOKEN FROM https://account.mapbox.com
// This needs to be set as an environment variable MAPBOXGL_TOKEN available to the Node-RED session on your server
mapboxgl.accessToken = '';

var people = {};
var mbstyle = 'mapbox://styles/mapbox/streets-v10';
// var mbstyle = 'mapbox://styles/mapbox/light-v10';

fetch('/-worldmap3d-key')
    .then(response => response.json())
    .then(data => {
        mapboxgl.accessToken = data.key;
        if (mapboxgl.accessToken === "") {
            alert("To make the map appear you must add your Access Token from https://account.mapbox.com by setting the MAPBOXGL_TOKEN environment variable on your server.");
            return;
        }

        var map = new mapboxgl.Map({
            container: 'map',
            style: mbstyle,
            center: [-1.3971, 51.0259],
            zoom: 16,
            pitch: 40,
            bearing: 20,
            attributionControl: true
        });

        map.on('load', function() {
            var layers = map.getStyle().layers;
            var firstSymbolId;
            for (var i = 0; i < layers.length; i++) {
                if (layers[i].type === 'symbol') {
                    firstSymbolId = layers[i].id;
                    break;
                }
            }

            /// Add the base 3D buildings layer
            map.addLayer({
                'id': '3d-buildings',
                'source': 'composite',
                'source-layer': 'building',
                'filter': ['==', 'extrude', 'true'],
                'type': 'fill-extrusion',
                'minzoom': 15,
                'paint': {
                    'fill-extrusion-color': '#ddd',
                    'fill-extrusion-height': [
                        "interpolate", ["linear"], ["zoom"],
                        15, 0, 15.05, ["get", "height"]
                    ],
                    'fill-extrusion-base': [
                        "interpolate", ["linear"], ["zoom"],
                        15, 0, 15.05, ["get", "min_height"]
                    ],
                    'fill-extrusion-opacity': .4
                }
            }, firstSymbolId);

            // ---- Connect to the Node-RED Events Websocket --------------------

            var connect = function() {
                ws = new SockJS(location.pathname.split("index")[0] + 'socket');
                ws.onopen = function() {
                    console.log("CONNECTED");
                    // if (!inIframe) {
                    //     document.getElementById("foot").innerHTML = "<font color='#494'>"+pagefoot+"</font>";
                    // }
                    ws.send(JSON.stringify({action:"connected"}));
                };
                ws.onclose = function() {
                    console.log("DISCONNECTED");
                    // if (!inIframe) {
                    //     document.getElementById("foot").innerHTML = "<font color='#900'>"+pagefoot+"</font>";
                    // }
                    setTimeout(function() { connect(); }, 2500);
                };
                ws.onmessage = function(e) {
                    var data = JSON.parse(e.data);
                    //console.log("GOT",data);
                    if (Array.isArray(data)) {
                        //console.log("ARRAY");
                        for (var prop in data) {
                            if (data[prop].command) { doCommand(data[prop].command); delete data[prop].command; }
                            if (data[prop].hasOwnProperty("name")) { setMarker(data[prop]); }
                            else { console.log("SKIP A",data[prop]); }
                        }
                    }
                    else {
                        if (data.command) { doCommand(data.command); delete data.command; }
                        if (data.hasOwnProperty("name")) { setMarker(data); }
                        else { console.log("SKIP",data); }
                    }
                };
            }
            console.log("CONNECT TO",location.pathname + 'socket');
            connect();

            var doCommand = function(c) {
                console.log("CMD",c);
                // Add our own overlay geojson layer if necessary
                if (c.hasOwnProperty("map") && c.map.hasOwnProperty("geojson") && c.map.hasOwnProperty("overlay")) {
                    addGeo(c.map.overlay,c.map.geojson);
                }
                var clat,clon;
                if (c.hasOwnProperty("lat")) { clat = c.lat; }
                if (c.hasOwnProperty("lon")) { clon = c.lon; }
                if (clat && clon) { map.setCenter([clon,clat]); }
                if (c.hasOwnProperty("zoom")) { map.setZoom(c.zoom); }
                if (c.hasOwnProperty("pitch")) { map.setPitch(c.pitch); }
                if (c.hasOwnProperty("bearing")) { map.setBearing(c.bearing); }
            }

            var addGeo = function(o,g) {
                map.addLayer({
                    'id': o,
                    'type': 'fill-extrusion',
                    'source': {
                        'type': 'geojson',
                        'data': g
                    },
                    'paint': {
                        // https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions
                        'fill-extrusion-color': ['get', 'color'],
                        'fill-extrusion-height': ['get', 'height'],
                        'fill-extrusion-base': ['get', 'base_height'],
                        'fill-extrusion-opacity': 0.5
                    }
                });
            }

            var setMarker = function(d) {
                if (d.hasOwnProperty("area")) { return; }  // ignore areas for now.
                console.log("DATA",d);
                if (people.hasOwnProperty(d.name)) {
                    map.getSource(d.name).setData(getPoints(d)); // Just update existing marker
                }
                else {  // it's a new thing
                    people[d.name] = d;
                    map.addLayer({
                        'id': d.name,
                        'type': 'fill-extrusion',
                        'source': {
                            'type': 'geojson',
                            'data': getPoints(d)
                        },
                        'paint': {
                            // https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions
                            'fill-extrusion-color': ['get', 'color'],
                            'fill-extrusion-height': ['get', 'height'],
                            'fill-extrusion-base': ['get', 'base_height'],
                            'fill-extrusion-opacity': 1
                        }
                    },firstSymbolId);
                }
            }

            var lookupColor = function(s) {
                var c = s.charAt(1);
                if (c === "F") { return "#79DFFF"; }
                if (c === "N") { return "#A4FFA3"; }
                if (c === "U") { return "#FFFF78"; }
                if (c === "H") { return "#FF7779"; }
                if (c === "S") { return "#FF7779"; }
            }

            // create the points for the marker and return the geojson
            var getPoints = function(p) {
                var fac = 0.000007;   // basic size for bock icon in degrees....
                var thing = "";
                if (p.hasOwnProperty("icon")) {
                    if (p.icon.indexOf("male") !== -1) { thing = "person"; }
                }
                p.SDIC = p.SIDC || p.sidc;
                if (p.hasOwnProperty("SIDC")) {
                    if (p.SIDC.indexOf("SFGPU") !== -1) { thing = "person"; }
                    if (p.SIDC.indexOf("SFGPUC") !== -1) { thing = "block"; }
                    if (p.SIDC.indexOf("GPEV") !== -1) { thing = "block"; }
                    p.iconColor = lookupColor(p.SIDC);
                }
                var t = p.type || thing;
                var base = p.height || 0;
                if (t === "person") { tall = 3; }                       // person slightly tall and thin
                else if (t === "bar") { base = 0; tall = p.height; }    // bar from ground to height
                else if (t === "block") { fac = fac * 4; tall = 5; }    // block large and cube
                else { tall = 2; fac = fac * 2; }                       // else small cube
                //console.log({p},{t},{fac},{base},{tall});
                var sin = 1;
                var cos = 0;
                p.hdg = Number(p.hdg || p.heading);
                if (p.hasOwnProperty("hdg") && !isNaN(p.hdg)) {
                    sin = Math.sin((90 - p.hdg) * Math.PI / 180);
                    cos = Math.cos((90 - p.hdg) * Math.PI / 180);
                }
                var dx = 1 * cos - 1 * sin;
                var dy = 1 * sin + 1 * cos;
                var d = {
                    "type": "Feature",
                    "properties": {
                        "name": p.name,
                        "type": t,
                        "color": p.iconColor || "#910000",
                        "height": base + tall,
                        "base_height": base
                    },
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [
                            [
                                [ p.lon + (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dy) ],
                                [ p.lon - (fac * dy ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dx) ],
                                [ p.lon - (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat - (fac * dy) ],
                                [ p.lon + (fac * dy ) / Math.cos( Math.PI / 180 * p.lat ), p.lat - (fac * dx) ],
                                [ p.lon + (fac * dx ) / Math.cos( Math.PI / 180 * p.lat ), p.lat + (fac * dy) ],
                            ]
                        ]
                    }
                }
                return d;
            }

            document.addEventListener ("keydown", function (ev) {
                if (ev.ctrlKey  &&  ev.altKey  &&  ev.code === "Digit3") {
                    ws.close();
                    //window.onbeforeunload = null;
                    window.location.href = "index.html";
                }
            });

});

})
.catch(error => { console.log("Unable to fetch MAPBOXGL_TOKEN.",error)} );

</script>
</body>
</html>

Step 2: exported the token in using the terminal as in the image below:
image

Step 3: my flow is called Map so i ran the command node-red Map.json as explained earlier

after running the command i got this reply

so that error means Node-RED is already running... - you need to stop the existing version node-red-stop then start it again in this session - node-red Map.json so that is picks up the environment with the key.

unfortunately same result... its so frustrating at this moment as i see that i am unable to make it work no matter how easy you show it in your replies...

sometimes the command node-red Map.json is opening a new session of the editor showing a completely new flow (empty), and when i run only node-red it works with the regular editor but gives as well same result.

i know that this is a long shot for me but i am asking out of desperation at this point... could we schedule a session where i could share my live screen with you so you could see what is happening in real time? if this is not possible then i understand however i think i will then give up on this matter.

Which same result ? Port in use ? If so then you haven’t stopped the already running one.

We can try to link up tomorrow sometime