TransWikia.com

Adding data-driven elevation to Mapbox GL JS fill-extrusion-height

Geographic Information Systems Asked by Maximilian on July 6, 2021

I can’t quite figure out how to extract elevation data from a geojson to use with Mapbox fill-extrusion-height.

The geojson looks like this but obviously with a lot more waypoints:

{
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "name": "Businesslauf Spielberg 1st #KingofRedbullRing",
                "type": "9",
            },
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [
                        14.765181,
                        47.220035,
                        725.2
                    ],
                    [
                        14.765127,
                        47.220028,
                        726
                    ],
                    [
                        14.764863,
                        47.219974,
                        726
                    ],
                ]
            }
        }
    ]
},

As you can see every third array item of every item in the coordinates array is elevation data.

I’m using Mapbox as a React Component like this:

import React, { useRef, useEffect, useState } from 'react'
import 'mapbox-gl/dist/mapbox-gl.css'
import mapboxgl from '!mapbox-gl' // eslint-disable-line import/no-webpack-loader-syntax
import turf from '@turf/center'

const MapBox = ({ geojson, height, pitch, zoom, BRBot }) => {
  const mapContainer = useRef(null)
  const map = useRef(null)
  const [lng, setLng] = useState(-70.9)
  const [lat, setLat] = useState(42.35)
  const [zoomLevel, setZoom] = useState(zoom ? zoom : 13)

  useEffect(() => {
    mapboxgl.accessToken = process.env.REACT_APP_MAPBOXGL_ACCESS_TOKEN
    
    if (map.current) {
      map.current.on('move', () => {
        setLng(map.current.getCenter().lng.toFixed(4))
        setLat(map.current.getCenter().lat.toFixed(4))
        setZoom(map.current.getZoom().toFixed(2))
      })
    } else {
      const coords = turf(geojson).geometry.coordinates

      map.current = new mapboxgl.Map({
        container: mapContainer.current,
        style: "mapbox://styles/track-runner/ckqjpfiyl04t118mugkuokdm6",
        center: coords ? coords : [lng, lat],
        zoom: zoomLevel,
        pitch: pitch ? pitch : 0,
        antialias: true,
      })

      map.current.on("load", () => {
        map.current.addSource("track", {
          type: "geojson",
          data: geojson,
        })
  
        map.current.addLayer({
          id: "track",
          type: "fill-extrusion",
          source: "track",
          paint: {
            "fill-extrusion-color": "#ff0000",
            "fill-extrusion-height": ["get", "elevation"],
            "fill-extrusion-opacity": 0.5,
          }
        })
      })
    }
  })

  return (
    <div 
      ref={mapContainer} 
      style={{ height: height, position: 'relative' }} 
      className={BRBot && 'border-radius-bottom'}
    />
  )
}

export default MapBox

My problem is with this line: "fill-extrusion-height": ["get", "elevation"],

If I give "fill-extrusion-height": a number the code runs with no errors and the layer I’ve added with the above json is rendered but this means that the height is the same for every waypoint. I get the same result if I add "elevation": number to the properties object in the geojson file as ["get", "elevation"] is able to extract that number.

However, this is not what I want.

I want each waypoint to have a different height/elevation resulting in a visual representation much like this one: https://www.matt.scot/strava-3d-elevation-profiles/#the-results

I just can’t figure out how Matt has been able to extract elevation data with the line "fill-extrusion-height":["get", "elevation"], because surely that gives him a constant number!

How do I use every elevation number for every waypoint using fill-extrusion-height?

One Answer

It's all about data preparation. You split your linestring in multiple smallest linestrings where you assign elevation to each.

  • For the first one, take the start point from original linestring and midpoint between start and 2nd point, create a new linestring
  • For end point, take the midpoint between last point and previous one, use the last point and create a linestring between last point and created midpoint
  • For intermediate points, calculate the midpoint before and after and make a linestring of midpoint before, point and midpoint after

For the 3 cases, report the third coordinates ( implicit z) as elevation in each feature properties

var mygeojson = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "properties": {
                "name": "Businesslauf Spielberg 1st #KingofRedbullRing",
                "type": "9",
            },
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [
                        14.765181,
                        47.220035,
                        725.2
                    ],
                    [
                        14.765127,
                        47.220028,
                        726
                    ],
                    [
                        14.764863,
                        47.219974,
                        726
                    ],
                ]
            }
        }
    ]
}
var mygeojsonfeatures = mygeojson.features;

var results = [];
for (let j = 0; j < mygeojsonfeatures.length;j++) {
var mycoords = mygeojsonfeatures[j].geometry.coordinates;
  for (let i = 0; i < mycoords.length;i++) {
    let line;
    if (i == 0) {
      line = turf.lineString([
        mycoords[i].slice(0, 2),
        turf.midpoint(turf.point(mycoords[i].slice(0, 2)), turf.point(mycoords[i + 1].slice(0, 2))).geometry.coordinates
      ]);
    } else if (i == mycoords.length - 1) {
      line = turf.lineString([
        turf.midpoint(turf.point(mycoords[i - 1].slice(0, 2)), turf.point(mycoords[i].slice(0, 2))).geometry.coordinates,
        mycoords[i].slice(0, 2)
      ]);
    } else {
      line = turf.lineString([
        turf.midpoint(turf.point(mycoords[i - 1].slice(0, 2)), turf.point(mycoords[i].slice(0, 2))).geometry.coordinates,
        mycoords[i].slice(0, 2),
        turf.midpoint(turf.point(mycoords[i].slice(0, 2)), turf.point(mycoords[i + 1].slice(0, 2))).geometry.coordinates
      ]);
    }
    line.properties = Object.assign({}, mygeojsonfeatures[j].properties);
    line.properties.elevation = mycoords[i][2];
    console.log(mycoords[i][2], line.properties.elevation);
    results.push(line)
  }
}

var featOut = {
    "type": "FeatureCollection",
    "features": results
}

Tested using my browser console at URL https://turfjs.org/docs/

You also may substract to all elevation values the minimum value to avoid getting lines bars from extrusion too high e.g screenshot below for tilt 45° (interactive standalone demo using MapLibre GL JS, the forked version of Mapbox GL JS)

enter image description here

Correct answer by ThomasG77 on July 6, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP