{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Working with 3D city models in Python\n", "\n", "\n", "\n", "**Balázs Dukai** [*@BalazsDukai*](https://twitter.com/balazsdukai), **FOSS4G 2019**\n", "\n", "Tweet #CityJSON\n", "\n", "[3D geoinformation research group, TU Delft, Netherlands](https://3d.bk.tudelft.nl/)\n", "\n", "![](figures/logos.png)\n", "\n", "Repo of this talk: [https://github.com/balazsdukai/foss4g2019](https://github.com/balazsdukai/foss4g2019)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# 3D + city + model ?\n", "![](figures/google_earth.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Probably the most well known 3d city model is what we see in Google Earth. And it is a very nice model to look at and it is improving continuously. However, certain applications require more information than what is stored in such a mesh model. They need to know what does an object in the model represent in the real world." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# Semantic models\n", "![](figures/semantic_model.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "That is why we have semantic models, where for each object in the model we store a label of is meaning.\n", "Once we have labels on the object and on their parts, data preparation becomes more simple. An important property for analytical applications, such as wind flow simulations." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# Useful for urban analysis\n", "\n", "![](figures/cfd.gif)\n", "\n", "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." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "But we can do much more with 3d city models. We can use them to better estimate the energy consumption in buildings, simulate noise in cities or analyse views and shadows. In the Netherlands sunshine is precious commodity, so we like to get as much as we can." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# And many more...\n", "\n", "![3d city model applications](figures/3d_cm_applications.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "There are many open 3d city models available. They come in different formats and quality. However, at our group we are still waiting for the \"year of the 3d city model\" to come. We don't really see mainstream use, apart of visualisation. Which is nice, I belive they can provide much more value than having a nice thing to simply look at." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# ...mostly just production of the models\n", "\n", "many available, but who **uses** them? **For more than visualisation?**\n", "\n", "![open 3d city models](figures/open_cms.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# In truth, 3D CMs are a bit difficult to work with" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Our built environment is complex, and the objects are complex too\n", "\n", "![](figures/assembling_solid.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Software are lagging behind\n", "\n", "+ not many software supports 3D city models\n", "\n", "+ if they do, mostly propietary data model and format\n", "\n", "+ large, *\"eterprise\"*-type applications (think Esri, FME, Bentley ... )\n", "\n", "+ few tools accessible for the individual developer / hobbyist" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "2. GML doesn't help ( *[GML madness](http://erouault.blogspot.com/2014/04/gml-madness.html) by Even Rouault* )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "That is why we are developing CityJSON, which is a data format for 3d city models. Essentially, it aims to increase the value of 3d city models by making it more simple to work with them and lower the entry for a wider audience than cadastral organisations." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "![cityjson logo](figures/cityjson_webpage.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Key concepts of CityJSON" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "+ *simple*, as in easy to implement\n", "+ designed with programmers in mind\n", "+ fully developed in the open\n", "+ flattened hierarchy of objects\n", "+ implementation first\n", "\n", "![GitHub Issues](figures/github_issues.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "CityJSON implements the data model of CityGML. CityGML is an international standard for 3d city models and it is coupled with its GML-based encoding. \n", "\n", "We don't really like GML, because it's verbose, files are deeply nested and large (often several GB). And there are many different ways to do one thing.\n", "\n", "Also, I'm not a web-developer, but I would be surprised if anyone prefers GML over JSON for sending stuff around the web." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "# JSON-based encoding of the CityGML data model\n", "![](figures/citygml_encoding.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "

I just got sent a CityGML file. pic.twitter.com/jnTVoRnVLS

— James Fee (@jamesmfee) June 29, 2016
\n", "\n", "+ files are deeply nested, and large\n", "+ many \"points of entry\"\n", "+ many diff ways to do one thing (GML doesn't help, *[GML madness](http://erouault.blogspot.com/2014/04/gml-madness.html) by Even Rouault* )" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## The CityGML data model\n", "\n", "![](figures/citygml_uml.gif)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Compression ~6x over CityGML\n", "\n", "![](figures/zurich_size.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Compression\n", "| file | CityGML size (original) | CityGML size (w/o spaces) | textures | CityJSON | compression |\n", "| -------- | ----------------------- | ----------------------------- |--------- | ------------ | --------------- | \n", "| [CityGML demo \"GeoRes\"](https://www.citygml.org/samplefiles/) | 4.3MB | 4.1MB | yes | 524KB | 8.0 |\n", "| [CityGML v2 demo \"Railway\"](https://www.citygml.org/samplefiles/) | 45MB | 34MB | yes | 4.3MB | 8.1 |\n", "| [Den Haag \"tile 01\"](https://data.overheid.nl/data/dataset/ngr-3d-model-den-haag) | 23MB | 18MB | no, material | 2.9MB | 6.2 |\n", "| [Montréal VM05](http://donnees.ville.montreal.qc.ca/dataset/maquette-numerique-batiments-citygml-lod2-avec-textures/resource/36047113-aa19-4462-854a-cdcd6281a5af) | 56MB | 42MB | yes | 5.4MB | 7.8 |\n", "| [New York LoD2 (DA13)](https://www1.nyc.gov/site/doitt/initiatives/3d-building.page) | 590MB | 574MB | no | 105MB | 5.5 |\n", "| [Rotterdam Delfshaven](http://rotterdamopendata.nl/dataset/rotterdam-3d-bestanden/resource/edacea54-76ce-41c7-a0cc-2ebe5750ac18) | 16MB | 15MB | yes | 2.6MB | 5.8 |\n", "| [Vienna (the demo file)](https://www.data.gv.at/katalog/dataset/86d88cae-ad97-4476-bae5-73488a12776d) | 37MB | 36MB | no | 5.3MB | 6.8 |\n", "| [Zürich LoD2](https://www.data.gv.at/katalog/dataset/86d88cae-ad97-4476-bae5-73488a12776d) | 3.03GB | 2.07GB | no | 292MB | 7.1 |" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "If you are interested in a more detailed comparison between CityGML and CityJSON you can read our article, its open access." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "![cityjson paper](figures/cityjson_paper.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "And yes, we are guilty of charge." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "![standards](figures/standards.png)\n", "\n", "[https://xkcd.com/927/](https://xkcd.com/927/)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Let's have a look-see, shall we?\n", "![](figures/looksee.gif)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Now let's take a peek under the hood, what's going on in a CityJSON file." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## An empty CityJSON file\n", "\n", "![](figures/cj01.svg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "In a city model we represent the real-world objects such as buildings, bridges, trees as different types of CityObjects. Each CityObject has its \n", "\n", "+ unique ID, \n", "+ attributes,\n", "+ geometry,\n", "+ and it can have children objects or it can be part of a parent object.\n", "\n", "Note however, that CityObject are not nested. Each of them is stored at root and the hierachy represented by linking to object IDs. " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## A CityObject\n", "\n", "![](figures/cj02.svg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Each CityObject has a geometry representation. This geometry is composed of *boundaries* and *semantics*." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Geometry\n", "\n", "+ **boundaries** definition uses vertex indices (inspired by Wavefront OBJ)\n", "+ We have a vertex list at the root of the document\n", "+ Vertices are not repeated (unlike Simple Features)\n", "+ **semantics** are linked to the boundary surfaces\n", "![](figures/cj04.svg)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "This `MulitSurface` has \n", "\n", "5 surfaces \n", "```json\n", "[[0, 3, 2, 1]], [[4, 5, 6, 7]], [[0, 1, 5, 4]], [[0, 2, 3, 8]], [[10, 12, 23, 48]]\n", "```\n", "each surface has only an exterior ring (the first array)\n", "```json\n", "[ [0, 3, 2, 1] ]\n", "```\n", "\n", "The semantic surfaces in the `semantics` json-object are linked to the boundary surfaces. The integers in the `values` property of `surfaces` are the 0-based indices of the surfaces of the boundary." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "There are 16 CityObjects\n", "{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE} \t\n", "{71B60053-BC28-404D-BAB9-8A642AAC0CF4} \t\n", "{6271F75F-E8D8-4EE4-AC46-9DB02771A031} \t\n", "{DE77E78F-B110-43D2-A55C-8B61911192DE} \t\n", "{19935DFC-F7B3-4D6E-92DD-C48EE1D1519A} \t\n", "{953BC999-2F92-4B38-95CF-218F7E05AFA9} \t\n", "{8D716FDE-18DD-4FB5-AB06-9D207377240E} \t\n", "{C6AAF95B-8C09-4130-AB4D-6777A2A18A2E} \t\n", "{72390BDE-903C-4C8C-8A3F-2DF5647CD9B4} \t\n", "{8244B286-63E2-436E-9D4E-169B8ACFE9D0} \t\n", "{87316D28-7574-4763-B9CE-BF6A2DF8092C} \t\n", "{CD98680D-A8DD-4106-A18E-15EE2A908D75} \t\n", "{64A9018E-4F56-47CD-941F-43F6F0C4285B} \t\n", "{459F183A-D0C2-4F8A-8B5F-C498EFDE366D} \t\n", "{237D41CC-991E-4308-8986-42ABFB4F7431} \t\n", "{23D8CA22-0C82-4453-A11E-B3F2B3116DB4} \t\n" ] } ], "source": [ "import json\n", "import os\n", "\n", "path = os.path.join('data', 'rotterdam_subset.json')\n", "with open(path) as fin:\n", " cm = json.loads(fin.read())\n", " \n", "print(f\"There are {len(cm['CityObjects'])} CityObjects\")\n", "\n", "# list all IDs\n", "for id in cm['CityObjects']:\n", " print(id, \"\\t\")" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "+ Working with a CityJSON file is straightforward. One can open it with the standard library and get going.\n", "+ But you need to know the schema well.\n", "+ And you need to write everything from scratch." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "That is why we are developing **cjio**. \n", "\n", "**cjio** is how *we eat what we cook*\n", "\n", "Aims to help to actually work with and analyse 3D city models, and extract more value from them. Instead of letting them gather dust in some governmental repository." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "![cjio](figures/cjio_docs.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## `cjio` has a (quite) stable CLI\n", "\n", "```bash\n", "$ cjio city_model.json reproject 2056 export --format glb /out/model.glb\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "## and an experimental API\n", "\n", "```python\n", "from cjio import cityjson\n", "\n", "cm = cityjson.load('city_model.json')\n", "\n", "cm.get_cityobjects(type='building')\n", "```" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**`pip install cjio`**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "This notebook is based on the develop branch." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "**`pip install git+https://github.com/tudelft3d/cjio@develop`**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# `cjio`'s CLI" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Usage: cjio [OPTIONS] INPUT COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]...\r\n", "\r\n", " Process and manipulate a CityJSON file, and allow different outputs. The\r\n", " different operators can be chained to perform several processing in one\r\n", " step, the CityJSON model goes through the different operators.\r\n", "\r\n", " To get help on specific command, eg for 'validate':\r\n", "\r\n", " cjio validate --help\r\n", "\r\n", " Usage examples:\r\n", "\r\n", " cjio example.json info validate\r\n", " cjio example.json assign_epsg 7145 remove_textures export output.obj\r\n", " cjio example.json subset --id house12 save out.json\r\n", "\r\n", "Options:\r\n", " --version Show the version and exit.\r\n", " --ignore_duplicate_keys Load a CityJSON file even if some City Objects have\r\n", " the same IDs (technically invalid file)\r\n", " --help Show this message and exit.\r\n", "\r\n", "Commands:\r\n", " assign_epsg Assign a (new) EPSG.\r\n", " clean Clean = remove_duplicate_vertices +...\r\n", " compress Compress a CityJSON file, ie stores its...\r\n", " decompress Decompress a CityJSON file, ie remove the...\r\n", " export Export the CityJSON to another format.\r\n", " extract_lod Extract only one LoD for a dataset.\r\n", " info Output info in simple JSON.\r\n", " locate_textures Output the location of the texture files.\r\n", " merge Merge the current CityJSON with others.\r\n", " partition Partition the city model into tiles.\r\n", " remove_duplicate_vertices Remove duplicate vertices a CityJSON file.\r\n", " remove_materials Remove all materials from a CityJSON file.\r\n", " remove_orphan_vertices Remove orphan vertices a CityJSON file.\r\n", " remove_textures Remove all textures from a CityJSON file.\r\n", " reproject Reproject the CityJSON to a new EPSG.\r\n", " save Save the city model to a CityJSON file.\r\n", " subset Create a subset of a CityJSON file.\r\n", " translate Translate the file by its (-minx, -miny,...\r\n", " update_bbox Update the bbox of a CityJSON file.\r\n", " update_textures Update the location of the texture files.\r\n", " upgrade_version Upgrade the CityJSON to the latest version.\r\n", " validate Validate the CityJSON file: (1) against its...\r\n" ] } ], "source": [ "! cjio --help" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[30m\u001b[46mParsing data/rotterdam_subset.json\u001b[0m\r\n", "{\r\n", " \"cityjson_version\": \"1.0\",\r\n", " \"epsg\": 7415,\r\n", " \"bbox\": [\r\n", " 90454.18900000001,\r\n", " 435614.88,\r\n", " 0.0,\r\n", " 91002.41900000001,\r\n", " 436048.217,\r\n", " 18.29\r\n", " ],\r\n", " \"transform/compressed\": true,\r\n", " \"cityobjects_total\": 16,\r\n", " \"cityobjects_present\": [\r\n", " \"Building\"\r\n", " ],\r\n", " \"materials\": false,\r\n", " \"textures\": true\r\n", "}\r\n" ] } ], "source": [ "! cjio data/rotterdam_subset.json info" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[30m\u001b[46mParsing data/rotterdam_subset.json\u001b[0m\n", "\u001b[30m\u001b[46m===== Validation (with official CityJSON schemas) =====\u001b[0m\n", "-- Validating the syntax of the file\n", "\t(using the schemas 1.0.0)\n", "-- Validating the internal consistency of the file (see docs for list)\n", "\t--Vertex indices coherent\n", "\t--Specific for CityGroups\n", "\t--Semantic arrays coherent with geometry\n", "\t--Root properties\n", "\t--Empty geometries\n", "\t--Duplicate vertices\n", "\t--Orphan vertices\n", "\t--CityGML attributes\n", "=====\n", "\u001b[32mFile is valid\u001b[0m\n", "\u001b[31mFile has warnings\u001b[0m\n", "--- WARNINGS ---\n", "WARNING: attributes 'TerrainHeight' not in CityGML schema\n", "\t(16 CityObjects have this warning)\n", "WARNING: attributes 'bron_tex' not in CityGML schema\n", "\t(16 CityObjects have this warning)\n", "WARNING: attributes 'voll_tex' not in CityGML schema\n", "\t(16 CityObjects have this warning)\n", "WARNING: attributes 'bron_geo' not in CityGML schema\n", "\t(16 CityObjects have this warning)\n", "WARNING: attributes 'status' not in CityGML schema\n", "\t(16 CityObjects have this warning)\n", "=====================================\n" ] } ], "source": [ "! cjio data/rotterdam_subset.json validate" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[30m\u001b[46mParsing data/rotterdam_subset.json\u001b[0m\n", "\u001b[30m\u001b[46mSubset of CityJSON\u001b[0m\n", "\u001b[30m\u001b[46mMerging files\u001b[0m\n", "\u001b[30m\u001b[46mReproject to EPSG:2056\u001b[0m\n", "\u001b[?25l [####################################] 100% \u001b[?25h\n", "\u001b[30m\u001b[46mSaving CityJSON to a file /home/balazs/Reports/talk_cjio_foss4g_2019/data/test_rotterdam.json\u001b[0m\n" ] } ], "source": [ "! cjio data/rotterdam_subset.json \\\n", " subset --exclude --id \"{CD98680D-A8DD-4106-A18E-15EE2A908D75}\" \\\n", " merge data/rotterdam_one.json \\\n", " reproject 2056 \\\n", " save data/test_rotterdam.json" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "+ The CLI was first, no plans for API\n", "\n", "+ **Works with whole city model only**\n", "\n", "+ Functions for the CLI work with the JSON directly, passing it along\n", "\n", "+ Simple and effective architecture" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# `cjio`'s API\n", "\n", "Allow *read* --> *explore* --> *modify* --> *write* iteration\n", "\n", "Work with CityObjects and their parts\n", "\n", "Functions for common operations\n", "\n", "Inspired by the *tidyverse* from the R ecosystem" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "import os\n", "from copy import deepcopy\n", "from cjio import cityjson\n", "from shapely.geometry import Polygon\n", "import matplotlib.pyplot as plt\n", "plt.close('all')\n", "from sklearn.preprocessing import FunctionTransformer\n", "from sklearn import cluster\n", "import numpy as np" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "In the following we work with a subset of the 3D city model of Rotterdam\n", "![](figures/rotterdam_subset.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Load a CityJSON" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "The `load()` method loads a CityJSON file into a CityJSON object." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "path = os.path.join('data', 'rotterdam_subset.json')\n", "\n", "cm = cityjson.load(path)\n", "\n", "print(type(cm))" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "## Using the CLI commands in the API\n", "You can use any of the CLI commands on a CityJSON object \n", "\n", "*However,* not all CLI commands are mapped 1-to-1 to `CityJSON` methods\n", "\n", "And we haven't harmonized the CLI and the API yet. " ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-- Validating the syntax of the file\n", "\t(using the schemas 1.0.0)\n", "-- Validating the internal consistency of the file (see docs for list)\n", "\t--Vertex indices coherent\n", "\t--Specific for CityGroups\n", "\t--Semantic arrays coherent with geometry\n", "\t--Root properties\n", "\t--Empty geometries\n", "\t--Duplicate vertices\n", "\t--Orphan vertices\n", "\t--CityGML attributes\n" ] }, { "data": { "text/plain": [ "(True,\n", " False,\n", " [],\n", " [\"WARNING: attributes 'TerrainHeight' not in CityGML schema\",\n", " '\\t(16 CityObjects have this warning)',\n", " \"WARNING: attributes 'bron_tex' not in CityGML schema\",\n", " '\\t(16 CityObjects have this warning)',\n", " \"WARNING: attributes 'voll_tex' not in CityGML schema\",\n", " '\\t(16 CityObjects have this warning)',\n", " \"WARNING: attributes 'bron_geo' not in CityGML schema\",\n", " '\\t(16 CityObjects have this warning)',\n", " \"WARNING: attributes 'status' not in CityGML schema\",\n", " '\\t(16 CityObjects have this warning)'])" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "cm.validate()" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "## Explore the city model\n", "\n", "Print the basic information about the city model. Note that `print()` returns the same information as the `info` command in the CLI." ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"cityjson_version\": \"1.0\",\n", " \"epsg\": 7415,\n", " \"bbox\": [\n", " 90454.18900000001,\n", " 435614.88,\n", " 0.0,\n", " 91002.41900000001,\n", " 436048.217,\n", " 18.29\n", " ],\n", " \"transform/compressed\": true,\n", " \"cityobjects_total\": 16,\n", " \"cityobjects_present\": [\n", " \"Building\"\n", " ],\n", " \"materials\": false,\n", " \"textures\": true\n", "}\n" ] } ], "source": [ "print(cm)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "subslide" } }, "source": [ "## Getting objects from the model\n", "Get CityObjects by their *type*, or a list of types. Also by their IDs. \n", "\n", "Note that `get_cityobjects()` == `cm.cityobjects`" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "buildings = cm.get_cityobjects(type='building')\n", "\n", "# both Building and BuildingPart objects\n", "buildings_parts = cm.get_cityobjects(type=['building', 'buildingpart'])\n", "\n", "r_ids = ['{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}',\n", " '{6271F75F-E8D8-4EE4-AC46-9DB02771A031}']\n", "buildings_ids = cm.get_cityobjects(id=r_ids)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "## Properties and geometry of objects" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"id\": \"{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}\",\n", " \"type\": \"Building\",\n", " \"attributes\": {\n", " \"TerrainHeight\": 3.03,\n", " \"bron_tex\": \"UltraCAM-X 10cm juni 2008\",\n", " \"voll_tex\": \"complete\",\n", " \"bron_geo\": \"Lidar 15-30 punten - nov. 2008\",\n", " \"status\": \"1\"\n", " },\n", " \"children\": null,\n", " \"parents\": null,\n", " \"geometry_type\": [\n", " \"MultiSurface\"\n", " ],\n", " \"geometry_lod\": [\n", " 2\n", " ],\n", " \"semantic_surfaces\": [\n", " \"WallSurface\",\n", " \"RoofSurface\",\n", " \"GroundSurface\"\n", " ]\n", "}\n" ] } ], "source": [ "b01 = buildings_ids['{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}']\n", "print(b01)" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{'TerrainHeight': 3.03,\n", " 'bron_tex': 'UltraCAM-X 10cm juni 2008',\n", " 'voll_tex': 'complete',\n", " 'bron_geo': 'Lidar 15-30 punten - nov. 2008',\n", " 'status': '1'}" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b01.attributes" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "CityObjects can have *children* and *parents*" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b01.children is None and b01.parents is None" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "CityObject geometry is a list of `Geometry` objects. That is because a CityObject can have multiple geometry representations in different levels of detail, eg. a geometry in LoD1 and a second geometry in LoD2." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b01.geometry" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "MultiSurface, lod 2\n" ] } ], "source": [ "geom = b01.geometry[0]\n", "print(\"{}, lod {}\".format(geom.type, geom.lod))" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "### Geometry boundaries and Semantic Surfaces\n", "On the contrary to a CityJSON file, the geometry boundaries are dereferenced when working with the API. This means that the vertex coordinates are included in the boundary definition, not only the vertex indices.\n", "\n", "`cjio` doesn't provide specific geometry classes (yet), eg. MultiSurface or Solid class. If you are working with the geometry boundaries, you need to the geometric operations yourself, or cast the boundary to a geometry-class of some other library. For example `shapely` if 2D is enough." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Vertex coordinates are kept 'as is' on loading the geometry. CityJSON files are often compressed and coordinates are shifted and transformed into integers so probably you'll want to transform them back. Otherwise geometry operations won't make sense." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "pycharm": { "is_executing": false }, "scrolled": true, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[(90988.79100000001, 435638.657, 10.652000000000001),\n", " (90987.429, 435642.77, 10.652000000000001),\n", " (90986.46900000001, 435641.09, 10.652000000000001),\n", " (90985.781, 435640.846, 10.652000000000001),\n", " (90986.801, 435637.955, 10.652000000000001)]" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "transformation_object = cm.transform\n", "\n", "geom_transformed = geom.transform(transformation_object)\n", "\n", "geom_transformed.boundaries[0][0]" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "But it might be easier to transform (decompress) the whole model on load." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"cityjson_version\": \"1.0\",\n", " \"epsg\": 7415,\n", " \"bbox\": [\n", " 90454.18900000001,\n", " 435614.88,\n", " 0.0,\n", " 91002.41900000001,\n", " 436048.217,\n", " 18.29\n", " ],\n", " \"transform/compressed\": false,\n", " \"cityobjects_total\": 16,\n", " \"cityobjects_present\": [\n", " \"Building\"\n", " ],\n", " \"materials\": false,\n", " \"textures\": true\n", "}\n" ] } ], "source": [ "cm_transformed = cityjson.load(path, transform=True)\n", "print(cm_transformed)" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "Semantic Surfaces are stored in a similar fashion as in a CityJSON file, in the `surfaces` attribute of a Geometry object." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{0: {'surface_idx': [[0], [1], [2]], 'type': 'RoofSurface'},\n", " 1: {'surface_idx': [[3]], 'type': 'GroundSurface'},\n", " 2: {'surface_idx': [[4],\n", " [5],\n", " [6],\n", " [7],\n", " [8],\n", " [9],\n", " [10],\n", " [11],\n", " [12],\n", " [13],\n", " [14],\n", " [15],\n", " [16],\n", " [17],\n", " [18],\n", " [19]],\n", " 'type': 'WallSurface'}}" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "geom.surfaces" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "`surfaces` does not store geometry boundaries, just references (`surface_idx`). Use the `get_surface_boundaries()` method to obtain the boundary-parts connected to the semantic surface." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "{0: {'surface_idx': [[0], [1], [2]], 'type': 'RoofSurface'}}" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "roofs = geom.get_surfaces(type='roofsurface')\n", "roofs" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "roof_boundaries = []\n", "for r in roofs.values():\n", " roof_boundaries.append(geom.get_surface_boundaries(r))" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "text/plain": [ "[[[[[579471, 198217, 10652],\n", " [578109, 202330, 10652],\n", " [577149, 200650, 10652],\n", " [576461, 200406, 10652],\n", " [577481, 197515, 10652]]],\n", " [[[580840, 194082, 15211],\n", " [579471, 198217, 15211],\n", " [577481, 197515, 15211],\n", " [576461, 200406, 15211],\n", " [572239, 198909, 15211],\n", " [571839, 200119, 15211],\n", " [571503, 201071, 15211],\n", " [566651, 199359, 15211],\n", " [569801, 190223, 15211],\n", " [573253, 191430, 15211],\n", " [574658, 191922, 15211]]],\n", " [[[565589, 202439, 11036],\n", " [566651, 199359, 11036],\n", " [571503, 201071, 11036],\n", " [571839, 200119, 11036],\n", " [573299, 200640, 11036],\n", " [572089, 204029, 11036],\n", " [570629, 203440, 11036],\n", " [570379, 204150, 11036]]]]]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "roof_boundaries" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "### Assigning attributes to Semantic Surfaces\n", "1. extract the surfaces,\n", "2. make the changes on the surface,\n", "3. overwrite the CityObjects with the changes." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "cm_copy = deepcopy(cm)\n", "new_cos = {}\n", "for co_id, co in cm.cityobjects.items():\n", " new_geoms = []\n", " for geom in co.geometry:\n", " # Only LoD >= 2 models have semantic surfaces\n", " if geom.lod >= 2.0:\n", " # Extract the surfaces\n", " roofsurfaces = geom.get_surfaces('roofsurface')\n", " for i, rsrf in roofsurfaces.items():\n", " # Change the attributes\n", " if 'attributes' in rsrf.keys():\n", " rsrf['attributes']['cladding'] = 'tiles'\n", " else:\n", " rsrf['attributes'] = {}\n", " rsrf['attributes']['cladding'] = 'tiles'\n", " geom.surfaces[i] = rsrf\n", " new_geoms.append(geom)\n", " else:\n", " # Use the unchanged geometry\n", " new_geoms.append(geom)\n", " co.geometry = new_geoms\n", " new_cos[co_id] = co\n", "cm_copy.cityobjects = new_cos" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{\n", " \"id\": \"{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}\",\n", " \"type\": \"Building\",\n", " \"attributes\": {\n", " \"TerrainHeight\": 3.03,\n", " \"bron_tex\": \"UltraCAM-X 10cm juni 2008\",\n", " \"voll_tex\": \"complete\",\n", " \"bron_geo\": \"Lidar 15-30 punten - nov. 2008\",\n", " \"status\": \"1\"\n", " },\n", " \"children\": null,\n", " \"parents\": null,\n", " \"geometry_type\": [\n", " \"MultiSurface\"\n", " ],\n", " \"geometry_lod\": [\n", " 2\n", " ],\n", " \"semantic_surfaces\": [\n", " \"WallSurface\",\n", " \"RoofSurface\",\n", " \"GroundSurface\"\n", " ]\n", "}\n" ] } ], "source": [ "print(cm_copy.cityobjects['{C9D4A5CF-094A-47DA-97E4-4A3BFD75D3AE}'])" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "### Create new Semantic Surfaces\n", "The process is similar as previously. However, in this example we create new SemanticSurfaces that hold the values which we compute from the geometry. The input city model has a single semantic \"WallSurface\", without attributes, for all the walls of a building. The snippet below illustrates how to separate surfaces and assign the semantics to them." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "pycharm": { "is_executing": false, "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "new_cos = {}\n", "\n", "for co_id, co in cm_copy.cityobjects.items():\n", " new_geoms = []\n", " \n", " for geom in co.geometry:\n", " if geom.lod >= 2.0:\n", " max_id = max(geom.surfaces.keys())\n", " old_ids = []\n", " \n", " for w_i, wsrf in geom.get_surfaces('wallsurface').items():\n", " old_ids.append(w_i)\n", " del geom.surfaces[w_i]\n", " boundaries = geom.get_surface_boundaries(wsrf)\n", " \n", " for j, boundary_geometry in enumerate(boundaries):\n", " # The original geometry has the same Semantic for all wall, \n", " # but we want to divide the wall surfaces by their orientation, \n", " # thus we need to have the correct surface index\n", " surface_index = wsrf['surface_idx'][j]\n", " new_srf = {\n", " 'type': wsrf['type'],\n", " 'surface_idx': surface_index\n", " }\n", " \n", " for multisurface in boundary_geometry:\n", " # Do any operation here\n", " x, y, z = multisurface[0]\n", " if j % 2 > 0:\n", " orientation = 'north'\n", " else:\n", " orientation = 'south'\n", " \n", " # Add the new attribute to the surface \n", " if 'attributes' in wsrf.keys():\n", " wsrf['attributes']['orientation'] = orientation\n", " else:\n", " wsrf['attributes'] = {}\n", " wsrf['attributes']['orientation'] = orientation\n", " \n", " new_srf['attributes'] = wsrf['attributes']\n", " \n", " # if w_i in geom.surfaces.keys():\n", " # del geom.surfaces[w_i]\n", " \n", " max_id = max_id + 1\n", " geom.surfaces[max_id] = new_srf\n", " \n", " new_geoms.append(geom)\n", " \n", " else:\n", " # If LoD1, just add the geometry unchanged\n", " new_geoms.append(geom)\n", " \n", " co.geometry = new_geoms\n", " new_cos[co_id] = co\n", " \n", "cm_copy.cityobjects = new_cos" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Analysing CityModels\n", "\n", "![](figures/zurich.png)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "In the following I show how to compute some attributes from CityObject geometry and use these attributes as input for machine learning. For this we use the LoD2 model of Zürich.\n", "\n", "Download the Zürich data set from https://3d.bk.tudelft.nl/opendata/cityjson/1.0/Zurich_Building_LoD2_V10.json" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "pycharm": { "is_executing": false }, "slideshow": { "slide_type": "subslide" } }, "outputs": [], "source": [ "path = os.path.join('data', 'zurich.json')\n", "zurich = cityjson.load(path, transform=True)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## A simple geometry function" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Here is a simple geometry function that computes the area of the groundsurface (footprint) of buildings in the model. It also show how to cast surfaces, in this case the ground surface, to Shapely Polygons." ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "def compute_footprint_area(co):\n", " \"\"\"Compute the area of the footprint\"\"\"\n", " footprint_area = 0\n", " for geom in co.geometry:\n", " \n", " # only LoD2 (or higher) objects have semantic surfaces\n", " if geom.lod >= 2.0:\n", " footprints = geom.get_surfaces(type='groundsurface')\n", " \n", " # there can be many surfaces with label 'groundsurface'\n", " for i,f in footprints.items():\n", " for multisurface in geom.get_surface_boundaries(f):\n", " for surface in multisurface:\n", " \n", " # cast to Shapely polygon\n", " shapely_poly = Polygon(surface)\n", " footprint_area += shapely_poly.area\n", " \n", " return footprint_area" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "## Compute new attributes" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Then we need to loop through the CityObjects and update add the new attributes. Note that the `attributes` CityObject attribute is just a dictionary.\n", "\n", "Thus we compute the number of vertices of the CityObject and the area of is footprint. Then we going to cluster these two variables. This is completely arbitrary excercise which is simply meant to illustrate how to transform a city model into machine-learnable features." ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "for co_id, co in zurich.cityobjects.items():\n", " co.attributes['nr_vertices'] = len(co.get_vertices())\n", " co.attributes['fp_area'] = compute_footprint_area(co)\n", " zurich.cityobjects[co_id] = co" ] }, { "cell_type": "markdown", "metadata": { "pycharm": { "name": "#%% md\n" }, "slideshow": { "slide_type": "skip" } }, "source": [ "It is possible to export the city model into a pandas DataFrame. Note that only the CityObject attributes are exported into the dataframe, with CityObject IDs as the index of the dataframe. Thus if you want to export the attributes of SemanticSurfaces for example, then you need to add them as CityObject attributes.\n", "\n", "The function below illustrates this operation." ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "pycharm": { "name": "#%%\n" }, "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def assign_cityobject_attribute(cm):\n", " \"\"\"Copy the semantic surface attributes to CityObject attributes.\n", " Returns a copy of the citymodel.\n", " \"\"\"\n", " new_cos = {}\n", " cm_copy = deepcopy(cm)\n", " for co_id, co in cm.cityobjects.items():\n", " for geom in co.geometry:\n", " for srf in geom.surfaces.values():\n", " if 'attributes' in srf:\n", " for attr,a_v in srf['attributes'].items():\n", " if (attr not in co.attributes) or (co.attributes[attr] is None):\n", " co.attributes[attr] = [a_v]\n", " else:\n", " co.attributes[attr].append(a_v)\n", " new_cos[co_id] = co\n", " cm_copy.cityobjects = new_cos\n", " return cm_copy" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "scrolled": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
creationDateGeomtypenr_verticesfp_areaclassHerkunftQualitaetStatusFileCreationDateRegionGebaeudeStatus
UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde32017-01-231.02465.209763NaNNaNNaNNaNNaNNaN
UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae502017-01-23NaN00.000000BB01EE_LB_20071.02012-02-232.01.0
UUID_a4a09780-153f-4385-ad19-3a92a6c4eec42017-01-231.03820.784309NaNNaNNaNNaNNaNNaN
UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba9342017-01-23NaN00.000000BB07EE_LB_20071.02012-02-232.01.0
UUID_bb1835bc-7437-453f-ac08-885de0503aaa2017-01-231.08769.363823NaNNaNNaNNaNNaNNaN
\n", "
" ], "text/plain": [ " creationDate Geomtype nr_vertices \\\n", "UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde3 2017-01-23 1.0 24 \n", "UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae50 2017-01-23 NaN 0 \n", "UUID_a4a09780-153f-4385-ad19-3a92a6c4eec4 2017-01-23 1.0 38 \n", "UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba934 2017-01-23 NaN 0 \n", "UUID_bb1835bc-7437-453f-ac08-885de0503aaa 2017-01-23 1.0 87 \n", "\n", " fp_area class Herkunft \\\n", "UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde3 65.209763 NaN NaN \n", "UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae50 0.000000 BB01 EE_LB_2007 \n", "UUID_a4a09780-153f-4385-ad19-3a92a6c4eec4 20.784309 NaN NaN \n", "UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba934 0.000000 BB07 EE_LB_2007 \n", "UUID_bb1835bc-7437-453f-ac08-885de0503aaa 69.363823 NaN NaN \n", "\n", " QualitaetStatus FileCreationDate \\\n", "UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde3 NaN NaN \n", "UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae50 1.0 2012-02-23 \n", "UUID_a4a09780-153f-4385-ad19-3a92a6c4eec4 NaN NaN \n", "UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba934 1.0 2012-02-23 \n", "UUID_bb1835bc-7437-453f-ac08-885de0503aaa NaN NaN \n", "\n", " Region GebaeudeStatus \n", "UUID_93fc5bae-4446-4336-9ff8-6679ebfdfde3 NaN NaN \n", "UUID_c9884c4e-1cac-47f5-b88b-6fb074c0ae50 2.0 1.0 \n", "UUID_a4a09780-153f-4385-ad19-3a92a6c4eec4 NaN NaN \n", "UUID_ba0bb815-5276-4e35-b4c1-878cbf6ba934 2.0 1.0 \n", "UUID_bb1835bc-7437-453f-ac08-885de0503aaa NaN NaN " ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df = zurich.to_dataframe()\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "In order to have a nicer distribution of the data, we remove the missing values and apply a log-transform on the two variables. Note that the `FuntionTransformer.transform` transforms a DataFrame to a numpy array that is ready to be used in `scikit-learn`. The details of a machine learning workflow is beyond the scope of this tutorial however." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "df_subset = df[df['Geomtype'].notnull() & df['fp_area'] > 0.0].loc[:, ['nr_vertices', 'fp_area']]\n", "transformer = FunctionTransformer(np.log, validate=True)\n", "df_logtransform = transformer.transform(df_subset)" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "scrolled": true, "slideshow": { "slide_type": "skip" } }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "fig = plt.figure()\n", "ax = fig.add_subplot(1, 1, 1)\n", "ax.scatter(df_logtransform[:,0], df_logtransform[:,1], alpha=0.3, s=1.0)\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "source": [ "def plot_model_results(model, data):\n", " fig = plt.figure()\n", " ax = fig.add_subplot(1, 1, 1)\n", " colormap = np.array(['lightblue', 'red', 'lime', 'blue','black'])\n", " ax.scatter(data[:,0], data[:,1], c=colormap[model.labels_], s=10, alpha=0.5)\n", " ax.set_xlabel('Number of vertices [log]')\n", " ax.set_ylabel('Footprint area [log]')\n", " plt.title(f\"DBSCAN clustering with estimated {len(set(model.labels_))} clusters\")\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "notes" } }, "source": [ "Since we transformed our DataFrame, we can fit any model in `scikit-learn`. I use DBSCAN because I wanted to find the data points on the fringes of the central cluster." ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "scrolled": false, "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "data": { "application/javascript": [ "/* Put everything inside the global mpl namespace */\n", "window.mpl = {};\n", "\n", "\n", "mpl.get_websocket_type = function() {\n", " if (typeof(WebSocket) !== 'undefined') {\n", " return WebSocket;\n", " } else if (typeof(MozWebSocket) !== 'undefined') {\n", " return MozWebSocket;\n", " } else {\n", " alert('Your browser does not have WebSocket support. ' +\n", " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", " 'Firefox 4 and 5 are also supported but you ' +\n", " 'have to enable WebSockets in about:config.');\n", " };\n", "}\n", "\n", "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", " this.id = figure_id;\n", "\n", " this.ws = websocket;\n", "\n", " this.supports_binary = (this.ws.binaryType != undefined);\n", "\n", " if (!this.supports_binary) {\n", " var warnings = document.getElementById(\"mpl-warnings\");\n", " if (warnings) {\n", " warnings.style.display = 'block';\n", " warnings.textContent = (\n", " \"This browser does not support binary websocket messages. \" +\n", " \"Performance may be slow.\");\n", " }\n", " }\n", "\n", " this.imageObj = new Image();\n", "\n", " this.context = undefined;\n", " this.message = undefined;\n", " this.canvas = undefined;\n", " this.rubberband_canvas = undefined;\n", " this.rubberband_context = undefined;\n", " this.format_dropdown = undefined;\n", "\n", " this.image_mode = 'full';\n", "\n", " this.root = $('
');\n", " this._root_extra_style(this.root)\n", " this.root.attr('style', 'display: inline-block');\n", "\n", " $(parent_element).append(this.root);\n", "\n", " this._init_header(this);\n", " this._init_canvas(this);\n", " this._init_toolbar(this);\n", "\n", " var fig = this;\n", "\n", " this.waiting = false;\n", "\n", " this.ws.onopen = function () {\n", " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", " fig.send_message(\"send_image_mode\", {});\n", " if (mpl.ratio != 1) {\n", " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", " }\n", " fig.send_message(\"refresh\", {});\n", " }\n", "\n", " this.imageObj.onload = function() {\n", " if (fig.image_mode == 'full') {\n", " // Full images could contain transparency (where diff images\n", " // almost always do), so we need to clear the canvas so that\n", " // there is no ghosting.\n", " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", " }\n", " fig.context.drawImage(fig.imageObj, 0, 0);\n", " };\n", "\n", " this.imageObj.onunload = function() {\n", " fig.ws.close();\n", " }\n", "\n", " this.ws.onmessage = this._make_on_message_function(this);\n", "\n", " this.ondownload = ondownload;\n", "}\n", "\n", "mpl.figure.prototype._init_header = function() {\n", " var titlebar = $(\n", " '
');\n", " var titletext = $(\n", " '
');\n", " titlebar.append(titletext)\n", " this.root.append(titlebar);\n", " this.header = titletext[0];\n", "}\n", "\n", "\n", "\n", "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "\n", "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", "\n", "}\n", "\n", "mpl.figure.prototype._init_canvas = function() {\n", " var fig = this;\n", "\n", " var canvas_div = $('
');\n", "\n", " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", "\n", " function canvas_keyboard_event(event) {\n", " return fig.key_event(event, event['data']);\n", " }\n", "\n", " canvas_div.keydown('key_press', canvas_keyboard_event);\n", " canvas_div.keyup('key_release', canvas_keyboard_event);\n", " this.canvas_div = canvas_div\n", " this._canvas_extra_style(canvas_div)\n", " this.root.append(canvas_div);\n", "\n", " var canvas = $('');\n", " canvas.addClass('mpl-canvas');\n", " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", "\n", " this.canvas = canvas[0];\n", " this.context = canvas[0].getContext(\"2d\");\n", "\n", " var backingStore = this.context.backingStorePixelRatio ||\n", "\tthis.context.webkitBackingStorePixelRatio ||\n", "\tthis.context.mozBackingStorePixelRatio ||\n", "\tthis.context.msBackingStorePixelRatio ||\n", "\tthis.context.oBackingStorePixelRatio ||\n", "\tthis.context.backingStorePixelRatio || 1;\n", "\n", " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", "\n", " var rubberband = $('');\n", " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", "\n", " var pass_mouse_events = true;\n", "\n", " canvas_div.resizable({\n", " start: function(event, ui) {\n", " pass_mouse_events = false;\n", " },\n", " resize: function(event, ui) {\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " stop: function(event, ui) {\n", " pass_mouse_events = true;\n", " fig.request_resize(ui.size.width, ui.size.height);\n", " },\n", " });\n", "\n", " function mouse_event_fn(event) {\n", " if (pass_mouse_events)\n", " return fig.mouse_event(event, event['data']);\n", " }\n", "\n", " rubberband.mousedown('button_press', mouse_event_fn);\n", " rubberband.mouseup('button_release', mouse_event_fn);\n", " // Throttle sequential mouse events to 1 every 20ms.\n", " rubberband.mousemove('motion_notify', mouse_event_fn);\n", "\n", " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", "\n", " canvas_div.on(\"wheel\", function (event) {\n", " event = event.originalEvent;\n", " event['data'] = 'scroll'\n", " if (event.deltaY < 0) {\n", " event.step = 1;\n", " } else {\n", " event.step = -1;\n", " }\n", " mouse_event_fn(event);\n", " });\n", "\n", " canvas_div.append(canvas);\n", " canvas_div.append(rubberband);\n", "\n", " this.rubberband = rubberband;\n", " this.rubberband_canvas = rubberband[0];\n", " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", " this.rubberband_context.strokeStyle = \"#000000\";\n", "\n", " this._resize_canvas = function(width, height) {\n", " // Keep the size of the canvas, canvas container, and rubber band\n", " // canvas in synch.\n", " canvas_div.css('width', width)\n", " canvas_div.css('height', height)\n", "\n", " canvas.attr('width', width * mpl.ratio);\n", " canvas.attr('height', height * mpl.ratio);\n", " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", "\n", " rubberband.attr('width', width);\n", " rubberband.attr('height', height);\n", " }\n", "\n", " // Set the figure to an initial 600x600px, this will subsequently be updated\n", " // upon first draw.\n", " this._resize_canvas(600, 600);\n", "\n", " // Disable right mouse context menu.\n", " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", " return false;\n", " });\n", "\n", " function set_focus () {\n", " canvas.focus();\n", " canvas_div.focus();\n", " }\n", "\n", " window.setTimeout(set_focus, 100);\n", "}\n", "\n", "mpl.figure.prototype._init_toolbar = function() {\n", " var fig = this;\n", "\n", " var nav_element = $('
');\n", " nav_element.attr('style', 'width: 100%');\n", " this.root.append(nav_element);\n", "\n", " // Define a callback function for later on.\n", " function toolbar_event(event) {\n", " return fig.toolbar_button_onclick(event['data']);\n", " }\n", " function toolbar_mouse_event(event) {\n", " return fig.toolbar_button_onmouseover(event['data']);\n", " }\n", "\n", " for(var toolbar_ind in mpl.toolbar_items) {\n", " var name = mpl.toolbar_items[toolbar_ind][0];\n", " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", " var image = mpl.toolbar_items[toolbar_ind][2];\n", " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", "\n", " if (!name) {\n", " // put a spacer in here.\n", " continue;\n", " }\n", " var button = $('