5

I have seen this example online which does data-driven building extrusion but doesn't provide the code at all.

I would very much like to achieve the same thing. I have a geojson file with some kind of attribute that I would like to map onto the building's height. Would you know how that is possible?

I have considered the recommended alternative: doing 3D extrusions on circles that are already generated based on my data. The code on this blog post is not provided and so I sued the code this SO post.

The code goes like this:

<html>
<head>
    <meta charset='utf-8' />
    <title>Display buildings in 3D</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.48.0/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.css' rel='stylesheet' />
    <script src='https://npmcdn.com/@turf/turf/turf.min.js'></script> 
    <style>
        body {
          margin: 0;
          padding: 0;
        }

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

<div id='map'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoicXVlMzIxNiIsImEiOiJjaWhxZmMxMDUwMDBzdXhsdWh0ZDkyMzVqIn0.sz3lHuX9erctIPE2ya6eCw';

var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v9',
  center: [8.538961, 47.372476],
  zoom: 16,
  pitch: 40,
  hash: true
});

var url = 'http://127.0.0.1:62940/test2.json';

mapboxgl.accessToken = 'pk.eyJ1IjoicXVlMzIxNiIsImEiOiJjaWhxZmMxMDUwMDBzdXhsdWh0ZDkyMzVqIn0.sz3lHuX9erctIPE2ya6eCw';

var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v9',
  center: [8.538961, 47.372476],
  zoom: 16,
  pitch: 40,
  hash: true
});

map.on('load', function() {

  map.addLayer({
    'id': 'extrusion',
    'type': 'fill-extrusion',
    "source": {
      "type": "geojson",
      "data": {
        "type": "FeatureCollection",
        "features": []
      }
    },
    'paint': {
      'fill-extrusion-color': '#00f',
      'fill-extrusion-height': ['get', 'frequency'],
      'fill-extrusion-base': 0,
      'fill-extrusion-opacity': 0.9
    }
  });

  map.addLayer({
    "id": "total",
    'type': 'circle',
    'paint': {
      'circle-radius': {
        'base': 1.75,
        'stops': [
          [12, 2],
          [22, 180]
        ]
      },
      'circle-color': '#ff7770'
    },
    "source": {
      "type": "geojson",
      "data": {
        "type": "FeatureCollection",
        "features": [{
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [8.538961, 47.372476]
            },
            "properties": {
              "frequency": 100
            }
          },
          {
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [8.539961, 47.372476]
            },
            "properties": {
              "frequency": 44
            }
          }
        ]
      }
    }
  });


  map.on('sourcedata', function(e) {
    if (e.sourceId !== 'total') return
    if (e.isSourceLoaded !== true) return

    var data = {
      "type": "FeatureCollection",
      "features": []
    }
    e.source.data.features.forEach(function(f) {
      var object = turf.centerOfMass(f)
      var center = object.geometry.coordinates
      var radius = 10;
      var options = {
        steps: 16,
        units: 'meters',
        properties: object.properties
      };
      data.features.push(turf.circle(center, radius, options))
    })
    map.getSource('extrusion').setData(data);
  })
});


</script>

So this works just fine.

However, when I try to get the same thing with a local geojson file that contains the exact same data, it does not work at all.

Here is my json:

{"type": "FeatureCollection", "features": [{"id": 1, "type": "Feature", "properties": {"frequency":44}, "geometry": {"type": "Point", "coordinates": [8.538961, 47.372476]}}, {"id": 2, "type": "Feature", "properties": {"frequency":200}, "geometry": {"type": "Point", "coordinates": [8.539961, 47.372476]}}]}

And here is my code:

<html>
<head>
    <meta charset='utf-8' />
    <title>Display buildings in 3D</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.48.0/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.48.0/mapbox-gl.css' rel='stylesheet' />
    <script src='https://npmcdn.com/@turf/turf/turf.min.js'></script> 
    <style>
        body {
          margin: 0;
          padding: 0;
        }

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

<div id='map'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoicXVlMzIxNiIsImEiOiJjaWhxZmMxMDUwMDBzdXhsdWh0ZDkyMzVqIn0.sz3lHuX9erctIPE2ya6eCw';

var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v9',
  center: [8.538961, 47.372476],
  zoom: 16,
  pitch: 40,
  hash: true
});

var url = 'http://127.0.0.1:62940/test2.json';

mapboxgl.accessToken = 'pk.eyJ1IjoicXVlMzIxNiIsImEiOiJjaWhxZmMxMDUwMDBzdXhsdWh0ZDkyMzVqIn0.sz3lHuX9erctIPE2ya6eCw';

var url = 'http://127.0.0.1:62940/test2.json';

var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v9',
  center: [8.538961, 47.372476],
  zoom: 16,
  pitch: 40,
  hash: true
});

map.on('load', function() {

  map.addLayer({
    'id': 'extrusion',
    'type': 'fill-extrusion',
    "source": {
      "type": "geojson",
      "data": {
        "type": "FeatureCollection",
        "features": []
      }
    },
    'paint': {
      'fill-extrusion-color': '#00f',
      'fill-extrusion-height': ['get', 'frequency'],
      'fill-extrusion-base': 0,
      'fill-extrusion-opacity': 0.9
    }
  }); 

 map.addSource("data", {
        type: "geojson",
        data: url,
    });

  map.addLayer({
    "id": "total",
    'type': 'circle',
    'paint': {
      'circle-radius': {
        'base': 1.75,
        'stops': [
          [12, 2],
          [22, 180]
        ]
      },
      'circle-color': '#ff7770'
    },
    "source": "data",
    /*"source": {
      "type": "geojson",
      "data": {
        "type": "FeatureCollection",
        "features": [{
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [8.538961, 47.372476]
            },
            "properties": {
              "frequency": 100
            }
          },
          {
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": [8.539961, 47.372476]
            },
            "properties": {
              "frequency": 44
            }
          }
        ]
      }
    }*/
  });


  map.on('sourcedata', function(e) {
    if (e.sourceId !== 'total') return
    if (e.isSourceLoaded !== true) return

    var data = {
      "type": "FeatureCollection",
      "features": []
    }
    e.source.data.features.forEach(function(f) {
      var object = turf.centerOfMass(f)
      var center = object.geometry.coordinates
      var radius = 10;
      var options = {
        steps: 16,
        units: 'meters',
        properties: object.properties
      };
      data.features.push(turf.circle(center, radius, options))
    })
    map.getSource('extrusion').setData(data);
  })
});


</script>

I guess that there is something I did not understand in the callback done to process the data with turf, but I just can't figure what and I don't find a lot of mapbox examples to go with the documentation to help.

Here is the expected output:image_expected output

And here is my output:my_output

Any help will be appreciated.

LBes
  • 3,366
  • 1
  • 32
  • 66

1 Answers1

2

Since you added a remote geojson file, you need to change the checks and the way you get and process the data:

  map.on('sourcedata', function(e) {

    // if (e.sourceId !== 'total') return
    if (e.sourceId !== 'data') return
    if (e.isSourceLoaded !== true) return

    var data = {
      "type": "FeatureCollection",
      "features": []
    }

    // e.source.data.features.forEach(function(f) {
    map.querySourceFeatures('data').forEach(function(f) {
      var object = turf.centerOfMass(f)
      var center = object.geometry.coordinates
      var radius = 10;
      var options = {
        steps: 16,
        units: 'meters',
        properties: object.properties
      };
      data.features.push(turf.circle(center, radius, options))
    })
    map.getSource('extrusion').setData(data);
  })

[ https://jsfiddle.net/vd2crsob/ ]

stdob--
  • 28,222
  • 5
  • 58
  • 73
  • Thanks so much, I thought I tried that, but tired me must have skipped it. One more question though. How would you make the radius of the extrusion to adapt to the radius of the circle if you have something of the like for the circles: "paint': { // make circles larger as the user zooms from z12 to z22 'circle-radius': { 'base': 1.75, 'stops': [[9, 5], [13,10], [22, 2]] }," – LBes Sep 17 '18 at 19:38
  • As a concept, you can calculate to translate the radius in pixels to the radius in meters: https://jsfiddle.net/Lawxqvr6/ – stdob-- Sep 17 '18 at 21:51
  • that works like a charm. Being new to mapbox (and JS partially) I'm not quite sure I understand though. Would you mind adding some quick explanations? PS: forgot to accept the answer, so here now it is accepted. – LBes Sep 17 '18 at 22:25
  • @LBes This `map.getLayer('total').paint.get('circle-radius').evaluate()` return current circle-radius for layer. And if you know the geographical center of the circle, you first translate its coordinates into screen pixels, transfer the point by the radius value along the Y-axis, reverse the projection to geographical coordinates, and get the radius in meters. – stdob-- Sep 17 '18 at 22:35
  • ok I see I think. Thank you very much for the help. I really appreciate it. Somehow the mapbox documentation remains quite obscure for me still. Especially since most of the example are based on old versions. There's not enough of them out there. – LBes Sep 17 '18 at 22:54
  • isn't the impact on performance kind of crazy though with that method. I have close to a million points in my real json file and it seems to kill my computer. – LBes Sep 17 '18 at 22:58
  • I guess the problem is that I then call this recomputing of the radius for every single point in my geojson and not only on the visible one, hence the super heavy computation right? I'm sure that there is a way to solve this using supercluster, but as my other mapbox question today pointed out, I haven't figured out how to make it work yet – LBes Sep 17 '18 at 23:10
  • If I manage to compute for all circles their radius based on the current zoom value, isn't their a way in the fill-extrusion layer to just use that value ? That would make the whole thing less heavy in term of computations right? – LBes Sep 17 '18 at 23:20
  • @LBes Yes, of course, you can calculate the radius once for all points for each event. And you can extrude only those circles that are visible on the screen - you can use the map.queryrenderedfeatures function - https://www.mapbox.com/mapbox-gl-js/api#map#queryrenderedfeatures – stdob-- Sep 17 '18 at 23:34
  • alright that should help with performance I guess. Because the whole thing was pretty slow otherwise. Thank you very much for that. Are you working with mapbox very often? You seem to know a lot about it and its underlying mechanisms. – LBes Sep 17 '18 at 23:48
  • @LBes Yes, in the current project there are a lot of pieces with maps. Good luck! – stdob-- Sep 18 '18 at 00:09
  • even with that solution it seems that the process is too slow. I guess a nice way to do it would be to probably precompute for all point the extrusion value and then only change the radius on zoom events. – LBes Sep 20 '18 at 14:42
  • Ok I created a new question as this is getting way too specific for comments only, and it is also a different question. For readers who might encounter the same issue, you can head to: https://stackoverflow.com/questions/52429753/dynamically-change-the-radius-of-circle-based-extrusion-with-mapbox – LBes Sep 20 '18 at 16:47