Open Source Workflows: QGIS2Web and 3D Extrusion Maps


 

 If you use QGIS2Web you might have noticed it lacks the ability to produce 3D extruded layers, even though that symbology type is supported in MapBox GL JS (and MapLibre GL JS).
We can get around that with a rather simple workflow involving using Maputnik with its ability to create and edit MapBox styles.

In order to explain this, I'll first get some OSM data to display in 3D (i.e. buildings, this method is for extruding polygons), if you want to use your own data, all you have to know is that it needs to have the base height (where does the polygon start, in meters above ground, can be 0) in a field and the extrusion height in another field, this should be the total height of the extrusion, i.e. the height in meters of the top of the extruded polygon. If this sounds confusing, I recommend checking out MapBox's example of indoor mapping with their library, in the example we have one layer of extruded polygons with 3 types:

    1. the floor, which has a base height of 0 and extrusion height of 0
    2. the walls which have a base height of 0 and extrusion height of 40 (meters)
    3. the arches which have a base height of 30 and extrusion height of 40, that created these 10 meter high arches.


If you are using your own data you can skip to where we export the map, else you can copy my method for making use of OSM data with QGIS and the QuickOSM plugin to get buildings. What I like to do is take the Buildings preset and add two conditions of my own (marked by the red box), and set a reasonable extent for the query, usually using the canvas and not the search

 

After I get the query results, usually in 2 layers, lines and polygons, I uses the Lines To Polygons processing tool, and copied the resulting polygons into the query layer polygon results layer.
And now when I have one polygon layer of buildings, I used expressions to calculate new fields for base and extrusion height.
This was my expression for the base_height field:

    if( "min_height" IS NOT NULL,
        to_real( "min_height"),
        if( "source:min_height" IS NOT NULL,
            to_real( "source:min_height"),
            0)
        )

 



And this was my expression for the extrusion_height field:

    if( "height" IS NOT NULL,
        to_real( "height"),
        if( "building:levels" IS NOT NULL,
            to_real( "building:levels")*4,
            6)
        )


 
Once we these fields we can create our QGIS2Web map, for our purposes the only change to the default export configuration we need is to use the MapBox GL JS map type at the bottom of the window, then export the map to a folder where you can find it, I also like dropping the coordinate percision since buildings don't really need more than a 7th decimal point.

 


After exporting the web map should open in your default browser, since we exported to MapBox GL JS you can see that the map supports pitching it to look at your data from side, meaning we can now add volume.

You can also see where you saved your map, in case you forgot to set the folder and went with the QGIS2Web default path:


 

In the map folder you can find a mapbox folder and within it you can find the style.js file, open it in a text editor.



 

What I like to do here is create a new file style.json where you can copy and paste the curly brackets that start at line 2 and end at line 44 (or further away, depending on your definitions) into the new file.



If you use Notepad++ it should now notify you that it doesn't like some of the new file, the first one is the data definition which we still need to copy from another file, the second thing it doesn't like is the single quotation marks ( ' ) around the keys and values in the layer paint definition, you can just change those to double quotation marks ( " ) and get on to copying the data.


In the map root directory you can find the data subdirectory, within is should be one file for each layer you exported, in it should be your data for that layer in (possibly minified) GeoJSON file, in my case I only have one layer and it is called json_buildings_1. We can again copy everything after the equal sign and paste it instead of json_buildings_1 in the style.json file.

The file should now look like this (with a very long row of data in line 16 or wherever you pasted your data), you should also probably remove the extra comma at line 42, Maputnik doesn't like it)

You should now open the Maputnik editor, either by installing the project locally or by using the online editor.

In the Maputnik editor you can upload your style.json file


Maputnik won't automaticaly focus and zoom to your data, especially not if you used a global basemap, so after zooming to your layer your editor should look like this:


Now if you like me have a field that has colour information for some polygons, but have null for most of them in that field, we can use a relatively simple expression (yes MapBox (and MapLibre) also use expressions, but mostly for styling layers) in the Paint > Color input field.
Click on the fx button next to the input and replace the expression inside to something like this:

    ["case",
        ["to-boolean",["get","building:colour"]],
        ["get","building:colour"],
    "gray"]

What the expression does is checks if the colour field is not null, 0, NaN or "false" and if it's not uses the colour field, else uses gray.

 

We can now add our extruded polygon layer.

In the top left of screen you have the Add Layer button, in the new window enter whatever ID you want, change the type to Fill extrusion and select the source of your polygon layer.





You can use the same color expression for the Extrusion-Color field input, I also recommend changing the opacity from 1 to make the buildings somewhat transparent.
The Extrusion height and Extrusion base base input fields need to be edited with a much simpler expression, we just need to use the field we calculated that has the base and extrusion heights.
To get a field's value we simply use the expression: 

    ["get", "field_name"]

My final layer configuration looks like this, only four changes from the default configuration:
 

1. added opacity
2. set the colors
3. set the base height from my calced_base field
4. set the extrusion height from my calced_height field.


You can see at the bottom of the last picture the JSON Editor at the bottom of the layer style editing panel. We can copy the entire text in the JSON Editor and we are going to paste it in the style.js file, after the other layers specified there, don't forget to add a comma between the last layer and the new one you are copying, don't forget to save the file.
It's also a good idea to save the updated style.json from Maputnik.



And if you'll open your map in the browser now you can see that you now have an extruded polygons layer on top of your original layer. After saving the file the results should be seen right away when refreshing.



The updated map folder you can publish through any web server or integrated into your website with the help of a web developer within an iframe.

 As a side note, Maputnik is also a great option for styling your GeoServer layers and I recommend installing a local version of it, especially if you using a limited or classified intranet.
You can read more about styling GeoServer layers with Maputnik in one of my other posts - Easy Styling Of Local GeoServer Layer With Maputnik


Comments