How to: Extract building heights from LiDAR data and make 3D buildings

Screen Shot 2015-09-25 at 11.19.00

The Environment Agency recently released their LiDAR as Open Data meaning it is now free to use and without restrictions. You can read a bit more about that here.

For those unfamiliar, LiDAR, which stands for Light Detection and Ranging, is a technology which involves firing a laser at a feature (usually the ground from a plane) and analysing the reflected light. The timing and pattern of the reflected light returned can be used to determine distance and type of feature scanned. A typical LiDAR system can scan hundreds of thousands of points a second producing incredibly detailed models of terrain, buildings, cars or whatever else they are pointed at.

The LiDAR data released by EA comes in 2 flavours:

  • DSM – Digital Surface Model
  • DTM – Digital Terrain Model

The DSM is exactly what you see on the surface of the earth with your own eyes; so depending on the resolution, this will include all man made features such as buildings, cars, infrastructure and so on as well as all the natural features such as vegetation.
The DTM is derived from the DSM where all the surface features (buildings, trees etc) have been stripped out leaving just the bare terrain.

Each product has it’s own uses, but one of the benefits of having access to both is the ability to extract the surface features by comparing the two datasets.

This is what I set out to do using some data I downloaded for the Bath area – it was surprisingly straightforward.

This is the end result:

Full screen version

For the step by step details…

You will need:

1) Download LiDAR data (both DSM and DTM) from here.  You will notice that depending on the area you click, different resolution products are available, e.g 25cm, 50cm, 1m, 2m. This is the distance between sampled points so the smaller the distance, the more detailed and the larger (file size) of the dataset. I went with 1m, the highest resolution for my area.

2) Extract the DTM and DSM zip files into separate folders. The contents will be a bunch of .asc files. These represent smaller tiles which make up the larger tile you downloaded. If you open one of these files in a text editor you will notice a bit of metadata at the top explaining where the data is located, resolution etc and then followed by a long list of values which are essentially just spot heights in meters.

3) In QGIS go to Raster > Misc > Build Virtual Raster (Catalog) and select all your DSM asc files and set an output file name. This will produce a .vrt which essentially creates a a single large virtual dataset from your individual .asc files.


Once complete, the VRT will be loaded onto the map and look something like this. (If prompted for a Coordinate Reference System (CRS), you will need to select EPSG27700, aka British National Grid.) You can see buildings as well as natural terrain features. Depending on your area, you may have gaps in LiDAR coverage like I do in the south east corner. Note that the bright white areas are water features – water absorbs LiDAR so no light is reflected and therefore no values can be determined.

Screen Shot 2015-09-23 at 20.22.22


4) Repeat the above process to create a DTM .vrt file.

5) The next step is to subtract the DTM from the DSM which will produce a new dataset containing the elevation of the surface features. To do this goto Raster > Raster Calculator and enter the following expression:

“combined_DSM_1m@1” – “combined_DTM_1m@1”

Obviously substitute with your vrt layer names and also set the Output CRS to EPSG27700/British National Grid

Screen Shot 2015-09-23 at 20.28.41

This will produce a new layer which really highlights the surface features – even things like individual trees and field/garden boundaries are visible in my 1m dataset. If you are using 50 or 25cm data you should be able to pick out even finer details.

In this example we can see the parapet walls on the roof of The Circus and vegetation in the gardens

Screen Shot 2015-09-25 at 10.11.54


6) This step is optional –  we can view these surface features in 3D using the Qgis2threejs plugin (You can install it by going to Plugins > Manage and Install Plugins and searching for Qgis2threejs – you may need to restart QGIS after installing)

Here we have Bath Abbey – not bad from 1m LiDAR.

Screen Shot 2015-09-23 at 20.56.04


We can also drape aerial imagery on top of this model to produce a poor man’s version of Google and Apple’s 3D buildings layers.

Click the images below to see the full 3D interactive versions (Each one is around 8mb so may take a moment to load and requires a WebGL compatible browser)

Screen Shot 2015-09-23 at 21.30.24

Screen Shot 2015-09-25 at 11.19.26

Screen Shot 2015-09-25 at 11.19.00

7) As you can see from the above images, the buildings are not very well defined due to the 1m resolution of the LiDAR. We can get more discrete building footprints from another Open Data product, OS Open Map Local. Download the relevant tile in ESRI shape format from here.

8) Once complete, unzip and open the XX_Building.shp file in QGIS, setting the CRS to EPSG27700/British National Grid.

9) Since the building layer probably covers an area much larger than your LiDAR data we will clip the buildings to the LiDAR extents. Create a new vector layer by clicking this button on the left: Screen Shot 2015-09-25 at 10.22.35Then select Polygon, set CRS to EPSG27700/BNG and hit ok and save somewhere.

Screen Shot 2015-09-25 at 10.22.20


10) With your new layer selected, select the edit button (yellow pencil) then the add feature (green blob with yellow *) Screen Shot 2015-09-25 at 10.27.10Zoom out until you can see all of your LiDAR data and then draw a rough polygon around your LiDAR extents by clicking each corner. To close the shape, right click the last point. Once done, press the save button above (blue disk with red pencil).

11)  Now we can clip the buildings dataset to the polygon you just created by going to Vector > Geoprocessing Tools > Clip

Screen Shot 2015-09-25 at 10.33.06

12) We now need to calculate the height for each of the building footprints in your area. For this we need to install the Zonal Statistics plugin by going to Plugins > Manage and Install Plugins

13) Once installed, run the plugin by going to Raster > Zonal Statistics. Enter your relevant layers and calculate the mean.

Screen Shot 2015-09-25 at 10.37.05

14) Done!

We now have building footprints with an average height attribute.

From here you can use your building heights for whatever you want, I decided to render them into simple 3D buildings to display on top of OpenStreetMap.
While the building footprints from Ordnance Survey are pretty much up to date, the LiDAR data appears to be at least 6-7 years old (you can tell this by the lack of a few recent housing and shopping developments). This meant there are a few newer building footprints which don’t appear in the LiDAR so have no or very low heights. Therefore I applied a filter to my dataset to remove all building footprints with a height less than 1m.

Full screen version

The process to create these 3D buildings is actually pretty straightforward – I used Mapbox’s TileMill but you could also use Studio.

Import your building footprint shapefile into TileMill and use the ‘building-height’ cartoCSS style along with the column name containing your heights. Then apply a fill colour.

Here’s the full CartoCSS I used

     building-height: '[mean]';

Once you’re happy with how it looks, hit ‘Export’ and ‘upload’ to your Mapbox account.

Posted in GIS
26 comments on “How to: Extract building heights from LiDAR data and make 3D buildings
  1. Toneman1984 says:

    Hey the link to the Lidar no longer works. Can you provide a working link?
    Thanks a lot!

  2. Phil Knight says:

    Great post. I’d love to give this a go.

  3. Ollie Brown says:

    Hi Nadnerb, thanks a lot for the post! I’ve created the DSM_combined, DTM_combined, and DSM_minus_DTM layers, and have installed the Qgis2threejs plugin. However, when I go to run the 3D viewer, the dialogue box says “The unit of current CRS is degrees, so terrain may not appear well.”, and the map comes out flat in my web browser. Any idea why this might be?

  4. Interested of Yeovil says:

    Hi Brendan … an excellent post. I’m particularly interested in getting the slope of the roof of a building. I tried adding the _max and _min attributes with the Zonal Statistics, but _min usually comes out as zero, whereas I would be looking for the height to the lowest part of the roof. The ultimate goal would be to calculate the volume of the roof. Cheers. Charles.

    • brendan says:

      Sorry I seemed to have missed your comment.

      I think this would be quite tricky to do as the OS building footprints are generalisations and therefore include non-building areas. e.g a row of detached houses may be grouped together into 1 polygon and look like a terrace. This means you will get 0m (or ground level elevation points) which is probably what you are experiencing. You could perhaps remove the top and bottom x% of values and then get the min/max of the remaining points to determine slope. Depending on the resolution of your LiDAR, you may find these results are no good anyway.

      There are tools out there that can help to identify rooftops and slope from just LiDAR data, but it needs to be high-res.

  5. Barra says:

    Why are some buildings rendered weirdly in 2.5D Tilemill?

    I am making the building-heights, and it works, but some times the back of the building is white and some times grey.

    I do not understand where the problem comes from.

    Any idea?

    • brendan says:

      Hi Barra,

      I’m not sure why that is happening. Perhaps there is an issue with the way the flat building polygons are created?
      Maybe you should compare 2 building footprints with/without this error and see if there is any difference in how they are stored. e.g do they start and end at the same node? Are there overlapping regions or intersects?
      Also, try re-digitising a problem building and see if that helps.

      Other than that I’m not sure, perhaps there is a rendering issue with TileMill, so you could try using Mapbox Studio.

  6. Karina says:

    Thank you for posting this tutorial — it’s most helpful! I just can’t figure out how to drape aerial imagery on top of the model? Could you share the steps I should take? Many thanks, Karina

    • brendan says:

      If you have an aerial image layer open it should be the default option when you export via the qgis2threejs plugin.

      You need to supply your own aerial image layer though as it’s not part of the plugin itself. (there are plugins out there for that too – e.g the openlayers plugin)

  7. lute play says:

    Hi i dont get step 13 , what do you mean by relevant layers , also if the building footprints show us where buildings are and we are using dsm- dtm to figure out the heights of buildings how do we get this information from zonal stats so we know which building on the footprint has what height and how is attribute assigned to them , i get really lost at step 13 i dont get how things click together . help :*(

    • brendan says:

      “Relevant layers” means the DTM/DSM and the building layers – whatever you called them. The zonal stats tool takes each building footprint and calculates the mean of the elevation points that are within the polygon. It will add a new attribute to the footprint table which contains the mean.

      The building footprints are the 2D outlines/polygons representing buildings, you’ll need to find them for whichever area you are working in. Within the UK you can get them from Ordnance Survey. For other countries you will have to search for available data.

      • lute play says:

        Is the average height the actual height of the building or litterarly the average height of buildings in the area?

        • brendan says:

          It’s the average elevation of the LiDAR points that are located within the building footprint, so it’s not really the actual height of the building but will give you a rough approximation. You could use the maximum value in Zonal Stats but bear in mind there are sometimes some erroneous points in the data which will throw out your heights. Also, if anything is overhanging a building (even a single leaf on a tree or a satellite dish or aerial that is hit by LiDAR) then your max building height will take on this value. You could of course clean up the data beforehand but for simplicity I just went with mean.

          Footprints are included in OS Open Map Local which you can find here:

          For the aerial imagery I used the QGIS OpenLayers plugin.

          Not a problem, hope you enjoy following the guide/learning QGIS.

          • lute play says:

            Hi i was just looking through the blog for more things to learn and came across the bath alert map you did , i was just wondering how you managed to make something like that so far all i can think of is geocoding a point on a map via Qgis such as a postcode , then you would perform a spatial query of a radius around that area and if any vector layer / data containing all the information of crime / email services ect ect , is within this radius then the postcode is notified.

            Though am not sure how you managed to link all of this into an emailing service so that the spatial query actually leads to emails and information being sent to someone rather than just pointing out how many things in the vicinity are in range or how people could pick and choose what they wanted to be informed about within their radius.

            As always all the best
            and keep the good work up

          • brendan says:

            Thanks. I can’t take all the credit for that project as I mainly focused on the mapping elements.

            Essentially the user puts in a postcode which is turned into a point, they then select the radius they want alerts for and submit it. The request gets sent to various APIs to return the results on the map but it also gets logged in a database. Every month, the server looks at all the alert requests in the database, gathers the data and sends out the emails using MailChimp/Mandrill.

            So yeah, you’ve basically described how the GIS elements are working and then the other features rely on various external APIs and services.

  8. brendan says:

    For some reason all comments on this post have been lost when I moved web host… any ideas anyone?

  9. nadnerb says:

    Disqus seems to have lost all the comments on this post when I migrated to a new web host so I’ve reverted to WordPress comments for now – sorry about that.

Leave a Reply