Creating a Textured CityGML Model

Sydney Dombowsky
Sydney Dombowsky
  • Updated

Introduction

This example creates a simple, textured CityGML model from our Interopolis 3DS model utilized in the previous example. CityGML is an OGC standardized GML application schema for representing 3D cities and landscapes. It is a hierarchical representation with very strict object types, and it can represent many levels of a city model in a single file through levels of detail. For example, you can have building models and building interiors with furniture in a single model on different levels of detail.

In the previous example, Adding Different Textures to a 3D City Model, we took a 3DS model of Interopolis and added different textures to the roof and walls, writing out to a 3D PDF. Now, we will take that same completed workspace and modify it to create a textured CityGML model.

The starting workspace (3ds-to-3dpdf-textures_2025_1.fmwt), complete workspace (TexturedCityGMLModel.fmwt), and source data can be downloaded from the Files section.

Step-by-Step Instructions

Part 1: Add Writer and Create IDs

In the first part of this tutorial, we will add some essential transformers that will allow us to write the data to a CityGML file. This process includes:

  • Adding a CityGML Writer and importing schema files
  • Removing the existing GeometryCoercer that groups objects together
  • Creating a unique ID for each feature using a StringConcatenator
  • Removing geometries using a GeometryRemover

 

1. Open the Workspace Template

Download 3ds-to-3dpdf-textures_2025_1.fmwt as your beginning workspace from the Files section and open it in FME Workbench. This is the completed workspace of the previous example. We will build upon this to create a textured CityGML model.
 

2. Add a CityGML Writer

Delete the Adobe PDF Writer and add a CityGML Writer. Set the following parameters in the Add Writer dialog:

  • Format: OGC CityGML
  • Dataset: /buildings.gml
  • Feature Type Definition: Import from Dataset…

Click the Parameters button to set the proper character encoding type:

  • Character Encoding: Unicode 8-bit (utf-8)

Click OK. An Import Writer Feature Types dialog will open. Click the Parameters button and set the following:

  •   Format: OGC CityGML 3

For older versions of FME that do not support writing to CityGML 3.0, set the format to OGC CityGML and click OK without adding application schema files. The use of schema files is not required for previous versions of CityGML as they are internally recognized.

Click the Parameters button and add the application schema files:

  • Application Schema: /appearance.xsd, /building.xsd, /core.xsd
    • Choose the three schema files from the source data to set the schema
  • Restrict Feature Types To Dataset: No

Click OK. When prompted to Select Feature Types, select the following:

  • Feature Type List: Building, RoofSurface, WallSurface

   

Click OK to add the Writer to your workspace.

 

3. Remove the GeometryCoercer

Next, remove the last GeometryCoercer (GeometryCoercer_2), as we no longer want to group all the objects together as a single mesh. CityGML’s data model will allow us to keep our roofs and walls separated as children of a building object.
 

4. Create a Unique ID with a StringConcatenator

Every CityGML feature needs a unique ID. We are going to use the BuildingID attribute, which keeps track of which features are part of which building, to create IDs for our ‘Building’ feature type. CityGML feature IDs cannot begin with numbers, so we will use the StringConcatenator to prepend the string “GML_” before the building ID. Connect the Aggregator output port to the StringConcatenator.

In the StringConcatenator parameters, name the new attribute:

  • New Attribute: gml_id

Under String Parts, enter the first row:

  • String Parts: Constant
  • String Value: GML_

Set the second row:

  • String Parts: Attribute Value
  • String Value: BuildingID
    • Use the drop-down button to select the attribute from the list
String Parts String Value
Constant GML_
Attribute Value BuildingID

 

5. Remove Geometries with a GeometryRemover

We don’t actually want to write any geometries to the CityGML Building feature type. Instead, we just want to create an empty high-level class that will become the parent ID attribute for the roof and wall surfaces. This ID will allow us to group roof and wall surfaces of a building together if we wish to. We will remove geometries by connecting a GeometryRemover to the StringConcatenator.

Connect the GeometryRemover Output port to the Building feature type.

 

Part 2: Create a Customer Transformer

In this part, we will create a custom transformer that does the following:

  • Groups walls for each building using an Aggregator
  • Creates CityGML attributes using an AttributeCreator
  • Copies the geometry type to a geometry trait using a GeometryPropertySetter
  • Creates unique IDs using a UUIDGenerator and a StringConcatenator to create a gml unique ID
  • Define a parent ID using a StringConcatenator
     

6. Aggregate Walls by Building ID

We need to group all of the walls for each building into a single MultiSurface geometry. Add an Aggregator and connect the AppearanceSetter Output port (AppearanceSetter_2 that is connected to the wall texture jpeg) to the Aggregator Input port.

Open the Aggregator parameters to set enable group processing:

  • Group Processing: Enabled
  • Group By: BuildingID
  • Complete Groups: When All Features Received


 

7. Create CityGML Attributes

CityGML features need to have their level of detail and geometry type defined. In FME, this information is stored in Geometry Traits. Before we can add a trait to the geometries, we need to create attributes using the AttributeCreator

Add an AttributeCreator and connect the new Aggregator (Aggregator_2) Aggregate port to the AttributeCreator Input port. Open the parameters and create the following attributes:

Attribute 1:

  • Attribute Name: citygml_level_of_detail
  • Value: 2
    • This attribute will be used to set our wall features to be in a level of detail 2

Attribute 2: 

  • Attribute Name: citygml_lod_name
  • Value: lod2MultiSurface

Attribute 3:

  • Attribute Name: citygml_feature_role
  • Value: boundedBy
    • This attribute defines the relationship between the feature and its parent
Attribute Name Value
citygml_level_of_detail 2
citygml_lod_name lod2MultiSurface
citygml_feature_role boundedBy

 

8. Copy the Geometry Type Attribute to a Geometry Trait

Now that we have stored the geometry type in an attribute, we need to copy it to a geometry trait using the GeometryPropertySetter. Geometry traits are similar to attributes but are stored in the geometry, rather than alongside it. 

Connect the AttributeCreator Output port the a new GeometryPropertySetter Input port and set the source attribtue in the parameter dialog:

  • Source Atttributes: citygml_lod_name
    • Click the ellipsis to select the previously created attribute

 

9. Create Unique IDs

CityGML requires every feature to have a unique ID. 

Add a UniqueIdentifierGenerator and connect its Input port to the GeometryPropertySetter Set port.

This transformer will create a unique ID and assign it to an attribute called _ulid.
 

10. Create Unique ID Attribute With Non-Number Prefix

The UniqueIdentifierGenerator creates unique IDs that begin with numbers, which are invalid in CityGML. Similar to the building class above, we will use a StringConcatenator to add “GML_” to the beginning of each ID. 

Add a StringConcatenator and connect its Input port to the UniqueIdentifierGenerator Output port. In the Parameters dialog, name the new attribute: 

  • New Attribute: gml_id

Set the first string part:

  • String Type: Constant
  • String Value: GML_

Set the second string part:

  • String Type: Attribute Value
  • String Value: _ulid
    • Use the drop-down to select the attribute created from the UniqueIdentifierGenerator
String Type String Value
Constant GML_
Attribute Value _ulid

11. Define the Parent ID Using Another StringConcatenator

We need to define the parent feature’s ID, so that FME can properly build the hierarchy in the CityGML model. We need to replicate the gml_id that we created for the building class above. 

Add another StringConcatenator and connect it to the previous StringConcatenator (StringConcatenator_2). In the Parameters dialog, name the new attribute: 

  • New Attribute: gml_parent_id

Set the first string part:

  • String Type: Constant
  • String Value: GML_

Set the second string part:

  • String Type: Attribute Value
  • String Value: BuildingID
String Type String Value
Constant GML_
Attribute Value BuildingID

12. Create Custom Transformer

Ensure that the Wall texture AppearanceSetter OUTPUT port is connected to the Aggregator_2. Select all the transformers from Step 6 (Aggregator_2) to 11 (StringConcatenator_3). Right click and select Create Custom Transformer. Fill out the parameters in the Create Custom Transformer dialog:

  • Transformer Name: CityGMLBuilder
  • Category: 3D

Click OK. You will now see it in the tab at the top ribbon next to the tab Main. Here you will find the transformers that make up the custom transformer.

Open the parameters for the Input, Aggregator_2_Input and ensure the BuildingID attribute is selected:

  • External Attributes to Expose: BuildingID

This step is important because the last StringConcatenator of the CityGMLBuilder depends on this attribute as part of its parameters. Since the attribute was created outside the custom transformer, the StringConcatenator doesn’t recognize the attribute as a valid parameter until the External Attribute has been exposed.

You may notice that your transformer has no output port. To add an output port, right-click the canvas, and select Insert Transformer Output. Connect the Output to the String_Concatenator_3 Output port.

The custom transformer workspace will look like this:

 

Part 3: Use Custom Transformer and Write to CityGML

We will now use the custom transformer we created in the main workspace, duplicate it so we can apply it to both the WallSurface and RoofSurface feature types, format output attributes, then run the workspace to write data to CityGML.

Back in the Main tab, check the parameters for the CityGMLBuilder, and ensure that the attribute BuildingID is also selected as an attribute.

  • User Paramerers:
    • BuildingID: BuildingID

Connect the CityGMLBuilder Output port to the WallSurface feature type in the Main workspace. 

13. Duplicate the CityGMLBuilder

Right-click the CityGMLBuilder and click Duplicate to make a second CityGMLBuilder in the workspace. 

Connect the AppearanceSetter (connected to the Roof texture jpeg) Output port to the CityGMLBuilder Input port. Connect the CityGMLBuilder Output port to the RoofSurface feature type:

14. Clean Output Attributes

We should clean up the attributes in our feature types before running the workspace. 

Open the WallSurface feature type parameters and, in the User Attributes tab, remove all the attributes except for citygml_feature_role by selecting records and clicking the minus (-) button at the bottom of the window.

If citygml_feature_role is not shown in the attribute list, change the Attribute Definition to Automatic which should leave three attributes remaining (one of which will be citygml_feature_role. Then, change the Attribute Definition back to Manual and remove the two other attributes, only leaving citygml_feature_role.

Open the Format Attributes tab, make sure that citygml_lod_name, gml_id, and gml_parent_id are exposed. You should see the following attributes listed under the WallSurface feature type. These are the attributes that will be written to the CityGML file.

Repeat this process for the RoofSurface feature type.

 

For the Building feature type, remove all User Attributes. Make sure that gml_id is exposed in the Format Attributes and uncheck citygml_lod_name and gml_parent_id under Format Attributes.

This should leave us with only one attribute, gml_id.

15. Run Workspace and View Output

Click the green Run button in the Toolbar to run your workspace. Once the translation has finished, view the data by selecting the output feature type and clicking the View Written Data button:

This will open the Data Preview window, where a graphic will show the visual representation of the data: 

 

If viewing using the FZKViewer, you will need to add a Reprojector after the source reader feature type, interopolis. Reproject it to LL84. This step is necessary because the data is in the Texas State Plane coordinate system. Since this coordinate system is not recognized in the FZKViewer, we need to reproject it into a coordinate system that it does recognize so that we can view it. If you wish to view the original textures in the FZKViewer, go to Display in the menu bar, then Textures, and select ‘From Entity’.

 

Additional Resources

Adding Different Textures to a 3D City Model (previous example)

Writing CityGML from FME

StringConcatenator Transformer Documentation

Using Custom Transformers

 

Data Attribution

Data used in this article originates from open data made available by the City of Austin, Texas. It contains data licensed under the Public Domain Dedication License - City of Austin.

 

Was this article helpful?

We're sorry to hear that.

Please tell us why.

As of January 14th, 2026, comments on knowledge base articles have been closed. To make sure questions don’t get missed and to enable more community support, we’ve moved discussions to the FME Community. If you have a question or a comment about this article, please create a new post or create a support ticket.