CityJSONSeq (CityJSON Text Sequences)

Table of contents

  1. CityJSONSeq specifications
  2. CityJSONFeature
  3. Streaming 3D cities with CityJSONSeq
  4. Reading and writing CityJSONSeq with cjio
  5. CityJSONSeq examples
  6. Validating a stream
    1. With the online validator
    2. Locally with cjfval
  7. cjfview: a small viewer for CityJSONSeq files

The 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 file 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 for handling CityJSON:

  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 are of type 'CityJSONFeature';

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


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

Since we want to have access to some properties, eg "transform" and the CRS, 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. One example would be:

{"type":"CityJSON","version":"2.0","transform": {"scale":[1.0,1.0,1.0],"translate": [0.0, 0.0, 0.0]},"metadata":{"referenceSystem":""},"CityObjects":{},"vertices":[]}

The subsequent JSON Objects must all be of type "CityJSONFeature", which means a CityJSONSeq with 3 features could look like this one:

{"type":"CityJSON","version":"2.0","transform": {"scale":[1.0,1.0,1.0],"translate": [0.0, 0.0, 0.0]},"metadata":{"referenceSystem":""},"CityObjects":{},"vertices":[]}

Reading and writing CityJSONSeq with cjio

The software cjio allows us to read and write CityJSONSeq from stdin/stdout (standard input/output streams).

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

cjio --suppress_msg export jsonl stdout

Observe that the different operators of cjio output messages/information, and those will get in the stdout stream. To avoid this, add the flag --suppress_msg when reading the file.

That stream can be saved to a file:

cjio --suppress_msg export jsonl

A CityJSONSeq stream/file can be compiled to a CityJSON file by reading it from stdin:

cat | cjio stdin info save

CityJSONSeq examples

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

Validating a stream

With the online validator

The official schema-validator of CityJSON accepts CityJSONSeq files, if they are structured as above ( and montré 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 cjfval

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

cjio --suppress_msg export jsonl stdout | cjfval --verbose
l.1 ✅
l.2 ❌ {"attributes":{"function":"something"},"geometry":[{"boundaries":[[[[0,1,2,3]],[[4,5,0,3]],[[5,6,1,0]],[[6,7,2,1]],[[3,2,7,4]],[[7,6,5,4]]]],"lod":"1","type":"Solid"}],"type":"+99999GnericCityObject"} is not valid under any of the given schemas [path:/CityObjects/id-1] |
l.3 ✅
l.4 🟡 Vertex (0, 1000, 0) duplicated | Vertex #8 is unused |
l.5 ✅
l.6 ✅

cjfview: a small viewer for CityJSONSeq files

The cjfview GitHub repository has more details.

It reads a CityJSONSeq file from stdin.

cat ./data/ | python ./src/
cjio --suppress_msg subset --random 5 export jsonl stdout | python ./src/