Balázs Dukai @BalazsDukai, FOSS4G 2019
Tweet #CityJSON
3D geoinformation research group, TU Delft, Netherlands
Repo of this talk: https://github.com/balazsdukai/foss4g2019
García-Sánchez, C., van Beeck, J., Gorlé, C., Predictive Large Eddy Simulations for Urban Flows: Challenges and Opportunities, Building and Environment, 139, 146-156, 2018.
many available, but who uses them? For more than visualisation?
not many software supports 3D city models
if they do, mostly propietary data model and format
large, "eterprise"-type applications (think Esri, FME, Bentley ... )
few tools accessible for the individual developer / hobbyist
import json
import os
path = os.path.join('data', 'rotterdam_subset.json')
with open(path) as fin:
cm = json.loads(fin.read())
print(f"There are {len(cm['CityObjects'])} CityObjects")
# list all IDs
for id in cm['CityObjects']:
print(id, "\t")
There are 16 CityObjects {C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE} {71B60053-BC28-404D-BAB9-8A642AAC0CF4} {6271F75F-E8D8-4EE4-AC46-9DB02771A031} {DE77E78F-B110-43D2-A55C-8B61911192DE} {19935DFC-F7B3-4D6E-92DD-C48EE1D1519A} {953BC999-2F92-4B38-95CF-218F7E05AFA9} {8D716FDE-18DD-4FB5-AB06-9D207377240E} {C6AAF95B-8C09-4130-AB4D-6777A2A18A2E} {72390BDE-903C-4C8C-8A3F-2DF5647CD9B4} {8244B286-63E2-436E-9D4E-169B8ACFE9D0} {87316D28-7574-4763-B9CE-BF6A2DF8092C} {CD98680D-A8DD-4106-A18E-15EE2A908D75} {64A9018E-4F56-47CD-941F-43F6F0C4285B} {459F183A-D0C2-4F8A-8B5F-C498EFDE366D} {237D41CC-991E-4308-8986-42ABFB4F7431} {23D8CA22-0C82-4453-A11E-B3F2B3116DB4}
cjio
has a (quite) stable CLI¶$ cjio city_model.json reproject 2056 export --format glb /out/model.glb
from cjio import cityjson
cm = cityjson.load('city_model.json')
cm.get_cityobjects(type='building')
pip install cjio
pip install git+https://github.com/tudelft3d/cjio@develop
cjio
's CLI¶! cjio --help
Usage: cjio [OPTIONS] INPUT COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]... Process and manipulate a CityJSON file, and allow different outputs. The different operators can be chained to perform several processing in one step, the CityJSON model goes through the different operators. To get help on specific command, eg for 'validate': cjio validate --help Usage examples: cjio example.json info validate cjio example.json assign_epsg 7145 remove_textures export output.obj cjio example.json subset --id house12 save out.json Options: --version Show the version and exit. --ignore_duplicate_keys Load a CityJSON file even if some City Objects have the same IDs (technically invalid file) --help Show this message and exit. Commands: assign_epsg Assign a (new) EPSG. clean Clean = remove_duplicate_vertices +... compress Compress a CityJSON file, ie stores its... decompress Decompress a CityJSON file, ie remove the... export Export the CityJSON to another format. extract_lod Extract only one LoD for a dataset. info Output info in simple JSON. locate_textures Output the location of the texture files. merge Merge the current CityJSON with others. partition Partition the city model into tiles. remove_duplicate_vertices Remove duplicate vertices a CityJSON file. remove_materials Remove all materials from a CityJSON file. remove_orphan_vertices Remove orphan vertices a CityJSON file. remove_textures Remove all textures from a CityJSON file. reproject Reproject the CityJSON to a new EPSG. save Save the city model to a CityJSON file. subset Create a subset of a CityJSON file. translate Translate the file by its (-minx, -miny,... update_bbox Update the bbox of a CityJSON file. update_textures Update the location of the texture files. upgrade_version Upgrade the CityJSON to the latest version. validate Validate the CityJSON file: (1) against its...
! cjio data/rotterdam_subset.json info
Parsing data/rotterdam_subset.json
{
"cityjson_version": "1.0",
"epsg": 7415,
"bbox": [
90454.18900000001,
435614.88,
0.0,
91002.41900000001,
436048.217,
18.29
],
"transform/compressed": true,
"cityobjects_total": 16,
"cityobjects_present": [
"Building"
],
"materials": false,
"textures": true
}
! cjio data/rotterdam_subset.json \
subset --exclude --id "{CD98680D-A8DD-4106-A18E-15EE2A908D75}" \
merge data/rotterdam_one.json \
reproject 2056 \
save data/test_rotterdam.json
Parsing data/rotterdam_subset.json Subset of CityJSON Merging files Reproject to EPSG:2056 [####################################] 100% Saving CityJSON to a file /home/balazs/Reports/talk_cjio_foss4g_2019/data/test_rotterdam.json
The CLI was first, no plans for API
Works with whole city model only
Functions for the CLI work with the JSON directly, passing it along
Simple and effective architecture
cjio
's API¶Allow read --> explore --> modify --> write iteration
Work with CityObjects and their parts
Functions for common operations
Inspired by the tidyverse from the R ecosystem
path = os.path.join('data', 'rotterdam_subset.json')
cm = cityjson.load(path)
print(type(cm))
<class 'cjio.cityjson.CityJSON'>
Get CityObjects by their type, or a list of types. Also by their IDs.
Note that get_cityobjects()
== cm.cityobjects
buildings = cm.get_cityobjects(type='building')
# both Building and BuildingPart objects
buildings_parts = cm.get_cityobjects(type=['building', 'buildingpart'])
r_ids = ['{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}',
'{6271F75F-E8D8-4EE4-AC46-9DB02771A031}']
buildings_ids = cm.get_cityobjects(id=r_ids)
path = os.path.join('data', 'zurich.json')
zurich = cityjson.load(path, transform=True)
def compute_footprint_area(co):
"""Compute the area of the footprint"""
footprint_area = 0
for geom in co.geometry:
# only LoD2 (or higher) objects have semantic surfaces
if geom.lod >= 2.0:
footprints = geom.get_surfaces(type='groundsurface')
# there can be many surfaces with label 'groundsurface'
for i,f in footprints.items():
for multisurface in geom.get_surface_boundaries(f):
for surface in multisurface:
# cast to Shapely polygon
shapely_poly = Polygon(surface)
footprint_area += shapely_poly.area
return footprint_area
for co_id, co in zurich.cityobjects.items():
co.attributes['nr_vertices'] = len(co.get_vertices())
co.attributes['fp_area'] = compute_footprint_area(co)
zurich.cityobjects[co_id] = co
df = zurich.to_dataframe()
df.head()
creationDate | Geomtype | nr_vertices | fp_area | class | Herkunft | QualitaetStatus | FileCreationDate | Region | GebaeudeStatus | |
---|---|---|---|---|---|---|---|---|---|---|
UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde3 | 2017-01-23 | 1.0 | 24 | 65.209763 | NaN | NaN | NaN | NaN | NaN | NaN |
UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae50 | 2017-01-23 | NaN | 0 | 0.000000 | BB01 | EE_LB_2007 | 1.0 | 2012-02-23 | 2.0 | 1.0 |
UUID_a4a09780-153f-4385-ad19-3a92a6c4eec4 | 2017-01-23 | 1.0 | 38 | 20.784309 | NaN | NaN | NaN | NaN | NaN | NaN |
UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba934 | 2017-01-23 | NaN | 0 | 0.000000 | BB07 | EE_LB_2007 | 1.0 | 2012-02-23 | 2.0 | 1.0 |
UUID_bb1835bc-7437-453f-ac08-885de0503aaa | 2017-01-23 | 1.0 | 87 | 69.363823 | NaN | NaN | NaN | NaN | NaN | NaN |
%matplotlib notebook
model = cluster.DBSCAN(eps=0.2).fit(df_logtransform)
plot_model_results(model, df_logtransform)
for co_id, co in zurich.cityobjects.items():
if co_id in df_subset.index:
ml_results = dict(df_subset.loc[co_id])
else:
ml_results = {'nr_vertices': 'nan', 'fp_area': 'nan', 'dbscan': 'nan'}
new_attrs = {**co.attributes, **ml_results}
co.attributes = new_attrs
zurich.cityobjects[co_id] = co
path_out = os.path.join('data', 'zurich_output.json')
cityjson.save(zurich, path_out)
Balázs Dukai
b.dukai@tudelft.nl
@BalazsDukai
Repo of this talk: https://github.com/balazsdukai/foss4g2019
QGIS plugin: github.com/tudelft3d/cityjson-qgis-plugin
Azul – CityJSON viewer on Mac – check the AppStore