3D városmodellek feldolgozása Pythonnal

Dukai Balázs @BalazsDukai, FOSS4G-HU 2019

Tweet #CityJSON

3D geoinformation research group, TU Delft, Netherlands

Az előadás anyagai megtalálhatóak itt: https://github.com/balazsdukai/foss4g2019

3D + város + modell ?

Szemantikus modellek

Hasznosak elemzések elvégzésére

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.

tobb területen...

3d city model applications

...viszont a hangsúly az előállításon áll

számos modell elérhető, de ki használja ezeket? A vizualizáción kívül bármi másra?

open 3d city models

Viszont a 3D városmodellekkel dolgozni nehéz

Az épített környezetünk összetett, és az azt alkotó elemek is összetettek

A szoftvertámogatás hiányos

  • kevés alkalmazás támogatja a 3D városmodelleket

  • ha igen, akkor általában zárt adatmodell és formátum (Esri, FME, Bentley ...)

  • kevés eszköz áll az egyéni felhasználó rendelkezésére

cityjson logo

A CityJSON sarokkövei

  • egyszerű, tehát egyszerűen programozható
  • teljesen nyílt
  • lapos objektum hierarchia
  • a szoftveres alkalmazás elsődleges

GitHub Issues

A CityGML JSON-alapú kódolása

~6x tömörítés a CityGML-hez képest

cityjson paper

Most pedig nézzünk a dolgok mélyére...

Egy üres CityJSON fájl

Egy CityObject

Geometria

  • boundaries : a geometria definíció a pontlistára mutat (inspiráció: Wavefront OBJ)
  • Pontlista a dokumentum gyökerén
  • A pontok nem ismétlődnek (a Simple Features-el ellentétben)
  • semantics : a 'boundary' felületekre mutat
In [1]:
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} 	
  • A CityJSON fájlok csak egyszerű JSON fájlok. Nem kell más mint a Standard Library

  • Viszont mindent meg kell írni az alapoktól kezdve

cjio

(Meglehetősen) stabil CLI

$ cjio city_model.json reproject 2056 export --format glb /out/model.glb

és egy kísérleti API

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

In [2]:
! 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...
In [3]:
! 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
}
In [5]:
! 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
  • A CLI volt először, nem voltak tervek az API-ra

  • A városmodell egészén működik, nincs lehetőség az objektumokat külön feldolgozni

cjio's API

Cél hogy lehetővé tegye az olvas --> elemez --> módosít --> ír iterációt az adatelemzés folyamán

Az objektumokon (CityObject) és azok részein működik

Függvények a leggyakoribb lépésekre

Inspiráció: tidyverse az R programozási nyelvből

CityJSON beolvasása

In [7]:
path = os.path.join('data', 'rotterdam_subset.json')

cm = cityjson.load(path)

print(type(cm))
<class 'cjio.cityjson.CityJSON'>

Objektumok kiválasztása

CityObject kiválasztása típus szerint vagy azonosító szerint.

Note that get_cityobjects() == cm.cityobjects

In [8]:
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)

Városmodellek elemzése

In [9]:
path = os.path.join('data', 'zurich.json')
zurich = cityjson.load(path, transform=True)

Egy függvény...

In [10]:
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

Számoljunk új tulajdonságokat

In [11]:
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
In [13]:
df = zurich.to_dataframe()
df.head()
Out[13]:
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
In [17]:
%matplotlib notebook
model = cluster.DBSCAN(eps=0.2).fit(df_logtransform)

plot_model_results(model, df_logtransform)

Mentsük el a válozásokat CityJSON-ba

In [19]:
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
In [20]:
path_out = os.path.join('data', 'zurich_output.json')
cityjson.save(zurich, path_out)

És nézzük meg QGIS-ben

Egyéb alkalmazások

Online CityJSON megjelenítő

QGIS plugin

Azul

Teljes CityGML <--> CityJSON konvertálás

Köszönöm!

Dukai Balázs

b.dukai@tudelft.nl

@BalazsDukai

Az előadás anyagai: https://github.com/balazsdukai/foss4g2019

cityjson.org

viewer.cityjson.org

QGIS plugin: github.com/tudelft3d/cityjson-qgis-plugin

Azul – CityJSON megjelenítő Mac-en – keresd az AppStore-ban

cjio: github.com/cityjson/cjio & cityjson.github.io/cjio/