# 3dfier lifts Dutch topographic polygons into a watertight 3D city surface

> 3dfier is a C++ tool from TU Delft that takes 2D GIS datasets, reads elevation from a LAS or LAZ point cloud, and reconstructs a 3D city model as a single digital surface model, with buildings integrated into the terrain and no intersecting triangles. The output is CityGML, CityJSON, OBJ, STL, PostGIS or CSV, and the class semantics in the input decide the shape of everything.

**tudelft3d/3dfier** — The open-source tool for creating 3D models

- Repository: https://github.com/tudelft3d/3dfier
- Website: http://tudelft3d.github.io/3dfier
- Stars: 639 · Forks: 116
- Language: C++
- License: GPL-3.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tudelft3d-3dfier

## What 3dfier actually produces: one surface, not many solids

3dfier is described as the open-source tool for creating 3D models, and the mechanism is more specific than that description suggests. It takes 2D GIS datasets, for example topographical datasets, and 3dfies them, in the sense of making them three-dimensional, by lifting every polygon to 3D.

The elevation comes from a point cloud, with LAS and LAZ supported at the moment, and the semantics of every polygon are used to perform the lifting. Water polygons are extruded to horizontal polygons, buildings to LOD1 blocks, roads as smooth surfaces, and so on.

The part that separates this from a set of extrusions is the next sentence. Every polygon is triangulated with a constrained Delaunay triangulation, and the lifted polygons are stitched together so that one digital surface model is constructed. The stated aim is to obtain one DSM that is error-free, meaning no intersecting triangles and no holes, so that the surface is watertight, with buildings integrated into the surface. That surface is then used as input in simulation software, for instance.

That design goal explains most of the engineering decisions. A watertight surface with buildings integrated into it is what a flood simulation, a noise propagation model or an urban wind study needs, and it is much harder to produce than a collection of separate building solids. The stitching and the constrained triangulation are where the difficulty lives, and where the library earns its place.

The input and output identity is preserved. The ID of each polygon is preserved, and there is a 1-to-1 mapping between the input and the output, which matters for a pipeline that has to join the 3D model back to 2D attributes.

## Seven input classes decide the shape of the model

The configuration is a YAML file, and the README notes that an example is provided in /resources/config_files/myconfig.yml. Any 2D input can be used, and each class in it must be mapped to one of a fixed set of class names:

  1. Building
  1. Terrain
  1. Road
  1. Water
  1. Forest
  1. Bridge
  1. Separation (used for walls and fences)

The Separation class is the one worth pausing on, because it shows what the model has to represent. Walls and fences are not area features, so they are given their own class, and the lifting for them is presumably a vertical extrusion rather than a surface, which is a different kind of geometry in the same triangulation.

The README is also candid that it is possible to define new classes, but that doing so would require a bit of programming. That is the honest boundary of the tool. Seven class names cover a surprisingly wide range of urban datasets, and anything outside them, a light rail alignment, a waterway with a different geometry, a piece of infrastructure with a footprint that is not a polygon, needs code.

The consequence for a user is that the mapping step in the config file is where the work happens, and it is a semantic decision rather than a mechanical one. A polygon in your source data that means something other than the seven things will not be lifted correctly, and the tool will not tell you so at the config level, because the class name is what drives the behaviour and a wrong mapping is a valid config that produces a wrong model.

## Point cloud classification is a precondition, not a nicety

The README has a dedicated section on the LAS and LAZ point cloud, and it is worth reading before anything else because the tool depends on the classification being present.

The expectation is that the LAS or LAZ is classified according to the ASPRS Standard LIDAR Point Classes v1.4, and at a minimum these should be defined: class 0-1 for created, never classified and/or unclassified, class 2 for ground, and classes 3-5 for vegetation.

Then comes the sentence that tells you why it matters. If the vegetation is not classified or not filtered out, then buildings might be taller and there might be artefacts in the terrain.

That is the failure mode to plan for. A building's height is the difference between the roof elevation and the ground elevation, and if the point cloud has vegetation mixed into the ground class, the ground surface is raised under the building and the building grows. The artefact is silent, it is spatial rather than per-object, and it is exactly the kind of error that survives into a simulation.

So the point cloud is not just an elevation source here, it is a classified source, and the classification quality propagates directly into the height of every building in the output. The tool does the lifting; it does not fix a bad classification.

## Output formats, and the one that loses the most

The output formats are listed as OBJ, CityGML, CityJSON, CSV for buildings only, PostGIS and STL. The choice matters more than usual here, because the formats are not equivalent in what they preserve.

CityGML and CityJSON are the semantic formats, and they are what a 3D city model workflow would normally want, because they carry the object model rather than just the geometry. OBJ and STL are geometry only, which is the right choice if the consumer is a renderer or a simulation model and the attributes live elsewhere. PostGIS puts the result in a database, which is the natural target if you want to query the buildings.

The CSV case is the one the README qualifies. It is for buildings only, with the ID and the height, the ground plus roof, output in a tabular format. So a CSV consumer gets a list of building heights and identifiers and nothing about the terrain, the roads or the water, which is a reasonable thing to want and a much smaller thing than a full model.

The theme: the ID and height of a building is the smallest useful output, and the more of the model you want to keep, the more you need a format that carries semantics rather than triangles. Choosing the output format is therefore a decision about the downstream consumer, and it should be made before the run rather than after, because the 1-to-1 ID mapping only pays off in a format that has somewhere to put the ID.

The project asks for feedback if you use it, and for a scientific citation. The paper is Ledoux, Biljecki, Dukai, Kumar, Peters, Stoter and Commandeur, 3dfier: automatic reconstruction of 3D city models, in the Journal of Open Source Software, 6(57), 2866, with a BibTeX entry in the README.

## Running 3dfier on the example data, and in Docker

There are prebuilt binaries for Windows and Mac OS X on the releases page, and the README recommends unzipping the archive somewhere easy to find rather than in the browser's download folder. To test quickly, an example dataset is published as a release tagged example_data, which you unzip into the 3dfier folder.

The example data is a small part of the BGT datasets, the 2D 1:1k topographic datasets of the Netherlands, together with a part of the AHN3 LIDAR dataset. The resulting model in OBJ form is in example_data/output/test_area.obj, and there is an open data website with 3D models of a few Dutch cities generated with 3dfier.

The command line is short. On Windows you open a command line, browse to the folder where you extracted the files, and run 3dfier myconfig.yml -o output.ext. On Mac OS X and Linux the equivalent is $ ./3dfier myconfig.yml --OBJ output.obj.

The Docker route exists to give as much freedom as possible for the vector data source, since vector data is read by GDAL/OGR. The invocation is:

    $ docker run --rm --name 3dfier -v <local path where your files are>:/data tudelft3d/3dfier:<tag> 3dfier <name of config file> <... 3dfier parameters>

All the input data has to live under the mounted local path, and the config file has to reference the input data relative to that path, so either the data and the config are moved into the mounted directory and its subdirectories, or the mount is set to the lowest common ancestor of everything needed. The README adds the constraint that the mounted path needs to be writable by any user, otherwise the output is not saved.

On the example data set on Linux, the sequence is:

    $ cd 3dfier/example_data
    $ docker run --rm -it -v 3dfier/example_data:/data tudelft3d/3dfier:latest 3dfier testarea_config_unix.yml --OBJ test.obj

The configuration is YAML and has to be valid for the parser to read it, and config files can be schema validated using YAML Lint.

## Where 3dfier is the wrong tool

Three boundaries, and the first is a data problem rather than a software one.

The first is that it needs both halves of the data, correctly prepared. A 2D planar partition and a classified point cloud covering the same area, with the point cloud classified to ASPRS standards. A project with only vector data, or with a point cloud from a source that does not classify vegetation, cannot use it as is. The alternative in that situation is a footprint extrusion from the vector data plus a separate height source, which produces a model with the semantics of the input but none of the watertight surface, and that is a genuinely different product.

The second is the scope of the class model. Seven classes, and a new class needs programming. Datasets that model urban structure at a finer granularity, whether that means individual kerbs, street furniture, rail, or building subtypes, will hit the boundary. The tool is built around a national topographic dataset and a national airborne lidar dataset, and its class vocabulary matches them.

The third is the workflow shape. 3dfier is a command line tool with a YAML configuration, not a service and not a plugin in a GIS. The configuration is a file you edit, the run is a process, and the output is files. For a team that wants a repeatable, versioned, reproducible conversion of a fixed data set, that is exactly right, and the configuration file is the versionable artefact that describes the model. For a team that wants to look at options interactively, or to run the conversion on demand from a service, the file-based model is a step back from a desktop GIS.

The alternative approach, and it is a reasonable one, is to use the GIS software you already have. Most of the same operations, footprint extrusion, surface interpolation from lidar, a DEM for the terrain, are available in a desktop or server GIS with a raster and vector toolchain, and the outputs go straight into a spatial database. The difference in approach is that 3dfier is built around one specific goal, a single watertight DSM with the buildings integrated into it, and it does that with a constrained triangulation and a stitching step that general purpose tools do not attempt by default. If your downstream consumer is a simulation that needs a closed surface, that specialisation is the reason to use it.

## Conclusion

Adopt 3dfier if you have 2D topographic data and a classified point cloud and you need a 3D city model that is watertight rather than a pile of separate extrusions, because the stitching and the semantic class handling are the substance of the project. Do not adopt it if your elevation source is not a classified LAS or LAZ point cloud, since the README expects ASPRS classification and warns that unclassified vegetation makes buildings appear taller and puts artefacts in the terrain. Verify first that your point cloud has the ground and vegetation classes defined, that every class in your input maps to one of the seven supported class names or that you are prepared to add one, that your vector input is a planar partition, and that the 1-to-1 ID mapping you rely on survives in your chosen output format, since the README notes that CSV output is limited to buildings.

## FAQ

### What does 3dfier do with 2D GIS data?

It lifts every polygon to 3D using elevation from a LAS or LAZ point cloud and the semantics of the polygon class, then triangulates with a constrained Delaunay triangulation and stitches the lifted polygons into one digital surface model. The aim is a surface with no intersecting triangles and no holes, with buildings integrated into it.

### Which output formats does 3dfier support?

OBJ, CityGML, CityJSON, CSV, PostGIS and STL. The README notes that CSV is for buildings only, with the ID and the height, the ground plus roof, in a tabular format, and that the ID of each polygon is preserved with a 1-to-1 mapping between input and output.

### What point cloud classification does 3dfier expect?

LAS or LAZ classified according to the ASPRS Standard LIDAR Point Classes v1.4, with at minimum classes 0-1 for unclassified, 2 for ground and 3-5 for vegetation defined. The README warns that if vegetation is not classified or filtered out, buildings might be taller and there might be artefacts in the terrain.

### How do I run 3dfier from the command line?

On Windows you open a command line, browse to the folder with the extracted files and run 3dfier myconfig.yml -o output.ext, and on Mac OS X and Linux the equivalent is $ ./3dfier myconfig.yml --OBJ output.obj. The lifting options are configured in the YAML file, with an example at /resources/config_files/myconfig.yml.

### Can I run 3dfier in Docker?

Yes. The image is based on Alpine and reads vector data through GDAL/OGR, and the run mounts a local directory to /data. All input data and the config file references must be under that mounted path, the path has to be writable by any user or the output is not saved, and the repository ships a 3dfier.dockerfile.

## Sources

- [License: GPL-3.0](https://github.com/tudelft3d/3dfier/blob/master/LICENSE)
- [Project website](http://tudelft3d.github.io/3dfier)
- [README](https://github.com/tudelft3d/3dfier/blob/master/README.md)
- [Releases](https://github.com/tudelft3d/3dfier/releases)
- [tudelft3d/3dfier on GitHub](https://github.com/tudelft3d/3dfier)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tudelft3d-3dfier
