Files
-
- 3 MB
- Download
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)
StringConcatenator Transformer Documentation
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.