CityJSONSeq (CityJSON Text Sequences)

Table of contents

  1. CityJSONSeq specifications
  2. CityJSONFeature
  3. Streaming 3D cities with CityJSONSeq
  4. CityJSONSeq examples
  5. Software to process CityJSONSeq
    1. cjseq: CityJSON <=> CityJSONSeq
    2. Validating a CityJSONSeq
      1. With the online validator
      2. Locally with cjval
    3. cjseqview: a small viewer for CityJSONSeq
  6. Scientific article about CityJSONSeq

CityJSON Text Sequence—CityJSONSeq for short—is a format based on JSON Text Sequences and CityJSON, inspired by GeoJSON Text Sequences. The idea is to decompose a (often large) CityJSON dataset into its features (eg each building, each bridge, each road, etc.), to create several JSON objects (of type CityJSONFeature), and stream/store them in a JSON Text Sequence (for instance ndjson – newline delimited JSON).

CityJSONSeq specifications

We follow the specifications of ndjson – newline delimited JSON and we add 2 constraints (#4 and #5):

  1. each JSON Object must conform to the JSON Data Interchange Format specifications and be written as a UTF-8 string;
  2. each JSON Object must be followed by a new-line (LF: '\n') character, and it may be preceded by a carriage-return (CR: '\r');
  3. a JSON Object must not contain the new-line or carriage-return characters;
  4. the first JSON Object must be of type 'CityJSON' (see below for details);
  5. the following JSON Objects must be of type 'CityJSONFeature';

Suggested convention: we recommend using the extension .city.jsonl when saving the JSON Objects to a file.

CityJSONFeature

A CityJSONFeature object represents one feature in a CityJSON object, for instance a "Building" (with eventually its children "BuildingPart" and/or "BuildingInstallation"). The idea is to decompose a large area into each of its features, and each feature is a stored as a CityJSONFeature. Each feature is independent and has its own list of vertices (which is thus local).

See the full specifications for a CityJSONFeature.

{
  "type": "CityJSONFeature",
  "id": "id-1", 
  "CityObjects": {
    "id-1": {
      "type": "Building", 
      "attributes": { 
        "roofType": "gabled roof"
      },
      "children": ["mypart"],
      "geometry": [...]
    },
    "mypart": {
      "type": "BuildingPart", 
      "parents": ["id-1"],
      "children": ["mybalcony"],
      "geometry": [...]
    },
    "mybalcony": {
      "type": "BuildingInstallation", 
      "parents": ["mypart"],
      "geometry": [...]
    }
  },
  "vertices": [...]
}

Streaming 3D cities with CityJSONSeq

To be able to reconstruct the features and geo-reference them, some properties are necessary, eg "transform", the CRS or "geometry-templates". Thus those need to be known by the client/software parsing the stream.

The first JSON Object should therefore be of type "CityJSON" and contain the necessary information. Notice that the properties "CityObjects" and "vertices" are mandatory (for the JSON Object to be valid) but should be respectively an empty JSON object and an empty array. Here’s one example:

{"type":"CityJSON","version":"2.0","transform": {"scale":[1.0,1.0,1.0],"translate": [0.0, 0.0, 0.0]},"metadata":{"referenceSystem":"https://www.opengis.net/def/crs/EPSG/0/7415"},"CityObjects":{},"vertices":[]}

The subsequent JSON Objects must all be of type "CityJSONFeature", which means a CityJSONSeq with 3 features looks like this one (it has 4 lines):

{"type":"CityJSON","version":"2.0","transform": {"scale":[1.0,1.0,1.0],"translate": [0.0, 0.0, 0.0]},"metadata":{"referenceSystem":"https://www.opengis.net/def/crs/EPSG/0/7415"},"CityObjects":{},"vertices":[]}
{"type":"CityJSONFeature","id":"a","CityObjects":{...},"vertices":[...]} 
{"type":"CityJSONFeature","id":"b","CityObjects":{...},"vertices":[...]} 
{"type":"CityJSONFeature","id":"c","CityObjects":{...},"vertices":[...]} 

CityJSONSeq examples

dataset CityJSONSeq file description
3DBAG 3dbag_b2.city.jsonl 2 buildings randomly selected from the 3DBAG, LoD2.2 only
Montréal montréal_b4.city.jsonl 4 buildings randomly selected from the Montréal dataset

Software to process CityJSONSeq

cjseq: CityJSON <=> CityJSONSeq

The software cjseq allows us to convert between CityJSON and CityJSONSeq, and vice-versa. cjseq has at the moment 3 commands:

  1. cat: CityJSON ==> CityJSONSeq
  2. collect: CityJSONSeq ==> CityJSON
  3. filter: to filter a stream based on feature type, bounding box, distance to a location, etc.

We can create a CityJSONSeq stream (with the first line containing the metadata) this way:

cjseq cat -f myfile.city.json > myfile.city.jsonl

And conversely convert a stream to a CityJSON file:

cat myfile.city.jsonl | cjseq collect > myfile.city.json

Validating a CityJSONSeq

With the online validator

The official schema-validator of CityJSON accepts CityJSONSeq files, if they are structured as above (3dbag_b2.city.jsonl and montréal_b4.city.jsonl are two examples).

You can just drop those files and the validator will indicate, per line, if the CityJSONFeature are valid, or not.

Locally with cjval

The official schema-validator of CityJSON (called cjval) can validate CityJSONSeq streams. Each line is individually validated and errors reported:

cjseq cat -f myfile.city.json | cjval --verbose
1   ✅ [1st-line for metadata] 
2   ✅ [NL.IMBAG.Pand.0503100000019581]
3   ❌ [NL.IMBAG.Pand.0503100000014639] Additional properties are not allowed ('CityObject' was unexpected) [path:] "CityObjects" is a required property [path:] |
4   ✅ [NL.IMBAG.Pand.0503100000005139]
...
61  🟡 [NL.IMBAG.Pand.0503100000020017] Vertex (185470, 295278, 4110) duplicated | Vertex #24 is unused |
62  ✅ [NL.IMBAG.Pand.0503100000034978]

cjseqview: a small viewer for CityJSONSeq

The cjseqview GitHub repository has more details.

It reads a CityJSONSeq from stdin.

cat ./data/b2.city.jsonl | python ./src/cjseqview.py
cat NYC.city.jsonl | cjseq filter --random 10 | python ./src/cjseqview.py`

Scientific article about CityJSONSeq

Ledoux H, Stavropoulou G, and Dukai B (2024). Streaming CityJSON datasets. Proceedings 3D GeoInfo 2024, (ISPRS volume XLVIII-4/W11-2024), pp. 57–63, Vigo (Spain)