cjio (CityJSON/io)

repository·master·Indexed 40 days ago

https://github.com/cityjson/cjio

A Python-based CLI and API tool for processing, manipulating, and converting CityJSON files. It provides a chainable pipeline of operators for geometric and attribute operations, including CRS reprojection, triangulation, and schema upgrades. The API allows for programmatic creation and modification of CityJSON models, including the management of CityObjects, Geometry, Boundary, and Semantic Surfaces.

Tokens
5.1K
Snippets
18
Records
23
Agent score
100%

What's inside cjio

  1. Chain operators in a pipeline

    master

    One of the core features of cjio is the ability to chain multiple operators together in a single command. The CityJSON model is passed through each operator sequentially, being modified along the way. Operators like info or validate output information to the console but pass the model to the next step in the pipeline.

    Examples:

    # Subset, remove materials, and save
    cjio example.city.json subset --id house12 remove_materials save out.city.json
    
    # Remove textures and show info
    cjio example.city.json remove_textures info
    
    # Upgrade, validate, and save
    cjio example.city.json upgrade validate save new.city.json
    
    # Merge multiple files into one
    cjio myfile.city.json merge '/home/elvis/temp/*.city.json' save all_merged.city.json
    cjio example.city.json subset --id house12 remove_materials save out.city.json
  2. Use cjio via Docker

    master

    To run cjio in a containerized environment, use the tudelft3d/cjio:latest image. You must mount your local data directory to a path inside the container (e.g., /data).

    Run cjio help via Docker:

    docker run --rm -v <local_path_to_files>:/data tudelft3d/cjio:latest cjio --help

    Run a Python script via Docker: If you have a Python script (e.g., gridder.py) that uses the cjio library, you can execute it within the Docker environment by mounting the script and data:

    docker run --rm -v <local_path_to_files>:/data tudelft3d/cjio:latest python gridder.py
    docker run --rm -v <local path where your files are>:/data tudelft3d/cjio:latest cjio --help
  3. Use stdin and stdout with cjio

    master

    Since v0.8, cjio supports reading from and writing to standard input/output streams.

    Reading from stdin: To read from a stream, use the keyword stdin instead of a filename. Currently, cjio only accepts CityJSONL (text sequences of CityJSONFeatures) for reading from stdin. Use the --suppress_msg flag to prevent operator messages from polluting the stream.

    Writing to stdout: To write to stdout, use the keyword stdout. You can pipe CityJSON or CityJSONL files to stdout. Use --suppress_msg to ensure only the data is sent to the stream.

    Examples:

    # Pipe CityJSONL to cjio, remove materials, and save to stdout
    cat mystream.city.jsonl | cjio --suppress_msg stdin remove_materials save stdout 
    
    # Export to JSONL and pipe to less
    cjio --suppress_msg myfile.city.json remove_materials export jsonl stdout | less
    
    # Reproject and export to JSONL via stdout
    cat myfile.city.json | cjio --suppress_msg stdin crs_reproject 7415 export jsonl stdout
    cat mystream.city.jsonl | cjio --suppress_msg stdin remove_materials save stdout
  4. Upgrade CityJSON files to the latest version

    master

    While cjio supports CityJSON v2.0, it can automatically upgrade older files (v1.0 and v1.1) using the upgrade operator.

    To upgrade a file to the latest schema version, use the following syntax:

    cjio old.json upgrade save newfile.city.json
    cjio old.json upgrade save newfile.city.json
  5. Install cjio

    master

    Install the latest release of cjio using pip. Note that cjio requires Python 3.7+.

    To install the core package:

    pip install cjio

    Some commands like export, triangulate, reproject, and validate require extra dependencies. To install them, use:

    pip install 'cjio[export,reproject,validate]'

    Windows Users: If you encounter errors related to pyproj or pyrsistent, you may need to manually download and install wheels (binary packages) for these libraries (e.g., from Gohlke's website) before running pip install cjio.

    pip install cjio
  6. Work with Semantic Surfaces

    master

    Semantic Surfaces are stored in the .surfaces attribute of a Geometry object. They are primarily available in models with LoD >= 2.

    • Filtering: Use geom.get_surfaces(type='...') to retrieve specific surface types (e.g., 'roofsurface').
    • Extracting Boundaries: Use geom.get_surface_boundaries(surface_dict) to efficiently extract the geometric boundaries for a specific surface. This method utilizes the surface_idx property within the surface dictionary.
    # Get all roof surfaces
    roofs = geom.get_surfaces(type='roofsurface')
    
    # Extract boundaries for each roof surface
    roof_boundaries = []
    for r in roofs.values():
        roof_boundaries.append(geom.get_surface_boundaries(r))
  7. How to define Semantic Surfaces in Geometry

    master

    Semantic surfaces allow you to assign types (like WallSurface, GroundSurface, or RoofSurface) to specific parts of a geometry's boundary.

    To define them, you populate the geom.surfaces dictionary. Each entry requires a type and a surface_idx list. The structure of the indices in surface_idx depends on the boundary type:

    • Solid boundary type: The index is composed of [Shell index, Surface index]. (Note: The tutorial example uses [0, 0] style for a single solid).
    • CompositeSolid boundary type: The index is composed of [Solid index, Shell index, Surface index].
    • MultiSurface boundary type: Both the Solid index and Shell index are omitted, leaving only the Surface index.

    Example mapping for a Solid geometry:

    geom.surfaces[0] = {'surface_idx': [[0,0], [0,1]], 'type': 'WallSurface'}
  8. Understand the Geometry and Boundary relationship

    master

    The cjio API uses two primary classes to handle 3D data:

    1. Geometry: Represents the high-level geometry object. It contains metadata like type, lod (Level of Detail), and a semantics_obj which holds semantic surface information. It provides the get_surfaces() method.
    2. Boundary: Represents the geometric boundary. It holds the vertices and the geometry (a nested array of vertex indices).

    When accessing a CityObject, the .geometry attribute returns a Geometry instance, which in turn provides access to its .boundaries (a Boundary instance).

  9. Create a CityJSON model and CityObjects using the API

    master

    To create a new CityJSON model from scratch using the cjio Python API, follow a hierarchical pattern:

    1. Initialize a CityJSON model: Create an empty container using cityjson.CityJSON().
    2. Create CityObject instances: Instantiate CityObject(id='...'). An id is required. You can assign attributes via the .attributes dictionary.
    3. Define Geometry: Instantiate Geometry(type='...', lod=...). Note that for CityJSON 1.0.0, lod must be a numeric value, not a string.
    4. Build Boundaries: Define the geometric shape by appending boundary lists to geom.boundaries.
    5. Define Semantic Surfaces: Map boundary indices to surface types (e.g., WallSurface, GroundSurface, RoofSurface) using the geom.surfaces dictionary.
    6. Assemble: Append the Geometry to the CityObject, and add the CityObject to the CityJSON model using cm.cityobjects[co.id] = co.
    7. Finalize and Save: Before saving, you must index the geometry and update the model using cm.reference_geometry() and cm.add_to_j(). Finally, use cityjson.save(cm, outfile) to write the file.
    from cjio import cityjson
    from cjio.models import CityObject, Geometry
    
    # 1. Create empty model
    cm = cityjson.CityJSON()
    
    # 2. Create CityObject
    co = CityObject(id='1')
    co.attributes = {'some_attribute': 42}
    co.type = 'Building'
    
    # 3. Create Geometry
    geom = Geometry(type='Solid', lod=2)
    
    # 4. Add boundaries (example: a simple list of polygons)
    bdry = [[[(0.0, 0.0, 0.0), (10.0, 0.0, 0.0), (10.0, 0.0, 10.0), (0.0, 0.0, 10.0)]]]
    geom.boundaries.append(bdry)
    
    # 5. Define Semantic Surfaces
    # surface_idx format for Solid: [Shell index, Surface index]
    geom.surfaces[0] = {'surface_idx': [[0, 0]], 'type': 'WallSurface'}
    
    # 6. Assemble
    co.geometry.append(geom)
    cm.cityobjects[co.id] = co
    
    # 7. Finalize and Save
    cityobjects, vertex_lookup = cm.reference_geometry()
    cm.add_to_j(cityobjects, vertex_lookup)
    cm.update_bbox()
    
    cityjson.save(cm, 'output.json')
  10. Modify and save CityJSON models

    master

    To modify Semantic Surfaces, you must follow a specific pattern because the model structure is not directly mutable in a way that preserves integrity:

    1. Extract the surfaces from the geometry.
    2. Modify the surface data.
    3. Re-assign the modified surfaces back to the geometry.
    4. Re-assign the geometry back to the CityObject.
    5. Re-assign the CityObject back to the model's cityobjects dictionary.

    Finally, use cityjson.save() to export the model to a file.

    # 1. Modify surfaces (example: adding an attribute)
    for co_id, co in cm.cityobjects.items():
        new_geoms = []
        for geom in co.geometry:
            if geom.lod >= 2.0:
                roofsurfaces = geom.get_surfaces('roofsurface')
                for i, rsrf in roofsurfaces.items():
                    # Update attributes
                    rsrf['attributes'] = rsrf.get('attributes', {})
                    rsrf['attributes']['cladding'] = 'tiles'
                    geom.surfaces[i] = rsrf
                new_geoms.append(geom)
            else:
                new_geoms.append(geom)
        co.geometry = new_geoms
    
    # 2. Save the model
    cityjson.save(cm, 'output_model.json')
  11. Finalize a CityJSON model before saving

    master

    When building a CityJSON model programmatically, simply adding objects to the model is not enough to make it valid for file export. You must perform a reference geometry pass to index boundaries and create the vertex list, then add these to the internal JSON store.

    Use the following sequence:

    1. cityobjects, vertex_lookup = cm.reference_geometry(): Generates the necessary geometry references and vertex lookups.
    2. cm.add_to_j(cityobjects, vertex_lookup): Adds the processed objects and vertices to the model's internal JSON structure.
    3. cm.update_bbox(): Recalculates the bounding box.
    4. cityjson.save(cm, outfile): Writes the model to disk.
  12. Iterate through CityObject hierarchies

    master

    CityJSON models are hierarchical. You can traverse from a parent CityObject to its children or from a child back to its parents using the following methods on a CityObject instance:

    • get_children(): Returns a list of child CityObjects.
    • get_parents(): Returns a list of parent CityObjects.

    Example of recursive traversal:

    cm = cjio.load("some_model.json")
    buildings = cm.get_cityobjects("building")
    
    for building in buildings:
        # Access immediate children
        children = building.get_children()
        for child in children:
            # Access children of children (recursive)
            grand_children = child.get_children()
    cm = cjio.load("some_model.json")
    buildings = cm.get_cityobjects("building")
    for building in buildings:
        children = building.get_children()