Mapping Configuration Guide
The Cassiopeia mapping configuration system transforms data from various sources (JSON, CSV, GeoJSON, GBFS, GTFS) into NGSI-LD compliant entities. You define configuration files that specify how fields from source data map to target attributes.
Configuration File Format
Supported Formats
The system supports both JSON5 and JSON. JSON5 is recommended for ease of use. JSON5 allows:
-
Comments
-
Trailing commas
-
Unquoted keys
-
Additional syntax conveniences
See the JSON5 specification for details.
Templates and Data Mapping
How Templates Work
Templates use double curly braces {{ }} to extract data from source files. The text inside the braces refers to field names in your source data.
Example: For JSON data:
{
"station_id": "ST123",
"name": "Central Station",
"lat": 45.4642,
"lon": 9.1900
}
In your mapping configuration:
-
{{ station_id }}→ extracts"ST123" -
{{ name }}→ extracts"Central Station" -
{{ lat }}→ extracts45.4642 -
{{ lon }}→ extracts9.1900
Finding Field Names
To determine which field names to use in templates:
-
Inspect your source data — Open the JSON, CSV, or GeoJSON file and examine its structure
-
Use exact field names — Match the names from your data
-
Use dot notation for nested data — For example,
{{ properties.station_name }}
CSV: If headers are STATION_ID,NAME,LATITUDE,LONGITUDE, use {{ STATION_ID }}, {{ NAME }}, and so on.
CSV with spaces or special characters: For headers like "Località di intervento" or "Tot posti", use bracket notation:
-
{{ this['Località di intervento'] }} -
{{ this['Tot posti'] }}
GeoJSON: For a structure with properties and geometry:
-
{{ properties.BIKE_SH }}— bike station name -
{{ properties.STALLI }}— number of stalls -
{{ geometry }}— geometry data
Template Examples
// Simple field extraction
"{{ station_id }}"
// String concatenation
"Station-{{ id }}"
// Multiple fields
"{{ street }} {{ number }}, {{ city }}"
// Default values
"{{ bike_id | default(value=vehicle_id) }}"
Advanced Template Usage
The mapping system uses the Tera templating engine for complex transformations.
Conditional Logic
category: {
source: "{% if properties.tipo == 'RESIDENTI/PUBBLICI' or properties.tipo == 'PUBBLICI/RESIDENTI' %}publicPrivate{% elif properties.tipo == 'PUBBLICI' %}public{% elif properties.tipo == 'AUTORIMESSA CONVENZIONATA' %}onlyWithPermit{% endif %}",
type: "Property",
transformation: "array",
}
Array Sources
occupancyDetectionType: {
source: ["none", "manual"],
type: "Property",
transformation: "array",
}
Template Syntax Reference
The system uses the Tera templating engine. Common features:
Variable access:
* {{ variable }} — Print a variable
* {{ object.property }} — Access object properties
* {{ array.0 }} — Access array elements
* {{ object['key'] }} — Access properties with dynamic keys
Conditionals:
* {% if condition %} … {% endif %}
* {% if condition %} … {% else %} … {% endif %}
* {% if condition %} … {% elif condition %} … {% endif %}
Filters:
* {{ value | filter_name }}
* {{ value | filter_name(param="value") }}
* Common filters: upper, lower, capitalize, trim, default, length
Operators:
* Comparison: ==, !=, >, <, >=, ⇐
* Logical: and, or, not
* Arithmetic: +, -, *, /, %
Data Model Configuration
{
dataModel: "BikeHireDockingStation"
}
The dataModel field specifies which NGSI-LD data model your entities follow. It serves two purposes:
-
Validation — If a matching schema exists, output is validated against it
-
Type definition — Defines the entity type for downstream systems
Use FIWARE Smart Data Models when possible (for example, BikeHireDockingStation, WeatherStation, TrafficFlowObserved). Custom strings such as MyCustomSensorType are also supported.
Identity Configuration
The identity section defines how entities are uniquely identified:
identity: {
entityName: "{{ station_id }}",
urn: {
site: "milano",
service: "mobility",
group: "bikeshare"
}
}
Entity Name
-
entityName— A template that creates the base identifier for each entity -
Does not need to be unique — the system appends incrementing numbers for duplicates (for example,
sensor-1,sensor-2)
Examples:
entityName: "{{ station_id }}"
entityName: "Station-{{ id }}"
entityName: "sensor-{{ device_id }}"
entityName: "static-sensor" // Becomes "static-sensor-1", "static-sensor-2", etc.
URN Components
The optional urn section adds hierarchical structure:
-
site — Physical location or site identifier (for example,
milano,london) -
service — Service category or domain (for example,
mobility,environment,traffic) -
group — Specific group within the service (for example,
bikeshare,parking,weather)
Use URN components to separate different entity groups, especially when multiple datasets share the same entity types.
Valid URN Combinations
Supported combinations (in order):
-
No components:
dataModel:entityId -
Site only:
dataModel:site:entityId -
Site + Service:
dataModel:site:service:entityId -
Site + Service + Group:
dataModel:site:service:group:entityId
Invalid combinations (service without site, group without site and service, or non-sequential parts) will cause errors.
URN Output Examples
With entityName: "{{ station_id }}":
-
Without URN:
urn:ngsi-ld:BikeHireDockingStation:ST123 -
Site only:
urn:ngsi-ld:BikeHireDockingStation:milano:ST123 -
Site + Service:
urn:ngsi-ld:BikeHireDockingStation:milano:mobility:ST123 -
All parts:
urn:ngsi-ld:BikeHireDockingStation:milano:mobility:bikeshare:ST123
Attribute Types
Each attribute defines how source data is transformed into an NGSI-LD attribute.
Attribute Structure
attributeName: {
source: "template or value",
type: "AttributeType",
transformation: "DataType",
target: { ... },
properties: { ... },
mappings: { ... }
}
-
source — How to extract data (required)
-
type — NGSI-LD attribute type (required)
-
transformation — Data type conversion (optional)
-
target — For relationships (optional)
-
properties — For relationships or nested attributes (optional)
-
mappings — For complex object attributes (optional)
Core Attribute Types
Property
Simple key-value properties:
name: {
source: "{{ station_name }}",
type: "Property",
transformation: "string"
}
capacity: {
source: "{{ max_bikes }}",
type: "Property",
transformation: "integer"
}
Relationship
Links to a single other entity:
regionId: {
source: "{{ region_id }}",
type: "Relationship",
target: { entity: "GbfsRegion" }
}
operator: {
source: "{{ operator_code }}",
type: "Relationship",
target: {
entity: "TransportOperator",
idPattern: "operator-{{ operator_code }}"
}
}
ListRelationship
Arrays of relationships with optional metadata:
// Dictionary input: { "dott_scooter": 10, "lime_bike": 5 }
vehicleCapacity: {
source: "{{ vehicle_capacity }}",
type: "ListRelationship",
target: { entity: "GbfsVehicleType" },
properties: {
count: {
source: "{{ context }}",
type: "Property",
transformation: "integer"
}
}
}
// Array input: [{ "vehicle_type_id": "scooter", "count": 10 }]
vehicleTypesAvailable: {
source: "{{ vehicle_types_available }}",
type: "ListRelationship",
target: {
entity: "GbfsVehicleType",
sourceField: "vehicle_type_id"
},
properties: {
count: {
source: "{{ count }}",
type: "Property",
transformation: "integer"
}
}
}
Synthetic Entities
Synthetic entities create related entities on-the-fly when they do not exist in source data. Use them for reference data such as organizations or operators.
owner: {
source: "Comune di Milano",
type: "ListRelationship",
targetObjectType: "Organization",
syntheticEntity: {
dataModel: "Organization",
identity: { entityName: "ComuneDiMilano" },
attributes: {
name: {
source: "Comune di Milano",
type: "Property",
transformation: "string"
},
contactEmail: {
source: "info@comune.milano.it",
type: "Property",
transformation: "string"
}
}
}
}
Inside syntheticEntity, {{ this }} refers to the current entity. Synthetic entities are created once per unique identifier.
Accessing Fields with Special Characters
Use this['field_name'] when field names contain spaces or special characters:
name: {
source: "Rastrelliere - {{ this['LOCALITA DI INTERVENTO'] | default(value='Unknown') }}",
type: "Property",
transformation: "string"
}
totalSlotNumber: {
source: "{{ this['Tot posti'] }}",
type: "Property",
transformation: "integer"
}
Complex Objects with Mappings
Use mappings to create nested objects from flat source data:
address: {
type: "Property",
transformation: "object",
mappings: {
streetAddress: {
source: "{{ street_name }} {{ street_number }}",
type: "Property",
transformation: "string"
},
addressLocality: {
source: "{{ city }}",
type: "Property",
transformation: "string"
},
addressCountry: {
source: "IT",
type: "Property",
transformation: "string"
}
}
}
Transformations
Primitive types: string, integer, float, boolean, array, object
Specialized types: datetime, date, time
Geometry types: point, multipoint, linestring, polygon, multilinestring, multipolygon
Mapping Requirements by Data Format
Custom formats (require user-created mappings): CSV, JSON, GeoJSON
Standard formats (pre-made mappings available): GBFS, GTFS
Pre-made mappings exist for common GBFS and GTFS entity types. For custom structures or additional fields, copy and modify these mappings.
Best Practices
-
Use simple templates when possible for better performance
-
Specify appropriate
transformationfor type safety -
Use nested
mappingsfor complex objects -
Apply default values for optional fields:
{{ field | default(value="unknown") }} -
Use URN components consistently across related entities