From 9bc4dba57118314d3e7d70eec0eba419b7d1dd35 Mon Sep 17 00:00:00 2001 From: Laurens <laurens@gfz-potsdam.de> Date: Thu, 21 Sep 2023 17:05:23 +0200 Subject: [PATCH] Add processing rules --- .gitlab-ci.yml | 1 - building/02_process/geometry/geometry.py | 15 ++++++ building/02_process/geometry/rule.xml | 10 ++++ building/02_process/quadkey/quadkey.py | 23 ++++++++ building/02_process/quadkey/rule.xml | 11 ++++ .../02_process/relation_id/relation_id.py | 18 +++++++ building/02_process/relation_id/rule.xml | 10 ++++ .../stories_and_floorspace/rule.xml | 13 +++++ .../stories_and_floorspace.py | 52 +++++++++++++++++++ .../obm_buildings_upsert.py | 6 +-- .../03_upsert/obm_buildings_upsert/rule.xml | 2 +- building/README.md | 18 ++++++- 12 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 building/02_process/geometry/geometry.py create mode 100644 building/02_process/geometry/rule.xml create mode 100644 building/02_process/quadkey/quadkey.py create mode 100644 building/02_process/quadkey/rule.xml create mode 100644 building/02_process/relation_id/relation_id.py create mode 100644 building/02_process/relation_id/rule.xml create mode 100644 building/02_process/stories_and_floorspace/rule.xml create mode 100644 building/02_process/stories_and_floorspace/stories_and_floorspace.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea42571..51cc9e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,7 +25,6 @@ linters: script: - flake8 --max-line-length=$LINE_LENGTH $SOURCES - black --check --line-length $LINE_LENGTH $SOURCES - - pylint -E $SOURCES release_job: stage: release diff --git a/building/02_process/geometry/geometry.py b/building/02_process/geometry/geometry.py new file mode 100644 index 0000000..bbbc8f0 --- /dev/null +++ b/building/02_process/geometry/geometry.py @@ -0,0 +1,15 @@ +class GeometryRule: + def __call__(self, geometry, *args, **kwargs): + """ + Wrap the WKT formatted geometry of a building in the `ST_GeomFromText()` function + + Args: + geometry (str): + WKT formatted geometry of the building + + Returns: + A dictionary with the WKT formatted geometry of the building wrapped in the + `ST_GeomFromText()` function. + """ + + return {"geometry": f"ST_GeomFromText('{geometry}', 4326)"} diff --git a/building/02_process/geometry/rule.xml b/building/02_process/geometry/rule.xml new file mode 100644 index 0000000..d357c5a --- /dev/null +++ b/building/02_process/geometry/rule.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rule name="GeometryRule"> + <input> + <param type="str">geometry</param> + </input> + <function filepath="geometry.py"/> + <output> + <param type="str">geometry</param> + </output> +</rule> diff --git a/building/02_process/quadkey/quadkey.py b/building/02_process/quadkey/quadkey.py new file mode 100644 index 0000000..a9d3110 --- /dev/null +++ b/building/02_process/quadkey/quadkey.py @@ -0,0 +1,23 @@ +class QuadkeyRule: + def __call__(self, longitude, latitude, *args, **kwargs): + """ + Determine the Quadkey of the tile in which the building centroid is located given its + longitude and latitude. + + Args: + longitude: + Longitude of the building centroid + latitude: + Latitude of the building centroid + + Returns: + A dictionary with the zoom-level 18 Quadkey. + """ + + import mercantile + + if longitude is None or latitude is None: + quadkey = None + else: + quadkey = mercantile.quadkey(mercantile.tile(longitude, latitude, 18)) + return {"quadkey": quadkey} diff --git a/building/02_process/quadkey/rule.xml b/building/02_process/quadkey/rule.xml new file mode 100644 index 0000000..07d368e --- /dev/null +++ b/building/02_process/quadkey/rule.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rule name="QuadkeyRule"> + <input> + <param type="float">longitude</param> + <param type="float">latitude</param> + </input> + <function filepath="quadkey.py"/> + <output> + <param type="str">quadkey</param> + </output> +</rule> diff --git a/building/02_process/relation_id/relation_id.py b/building/02_process/relation_id/relation_id.py new file mode 100644 index 0000000..2ef2a6e --- /dev/null +++ b/building/02_process/relation_id/relation_id.py @@ -0,0 +1,18 @@ +class RelationIDRule: + def __call__(self, relations, *args, **kwargs): + """ + Determine the building relation OSM ID. + + Args: + relations: + List of the attributes of all relations that the building is member of. + + Returns: + OSM ID of the first relation that the building is member of. + """ + + for relation in relations: + osm_id = relation.get("osm_id", None) + if osm_id is not None: + return {"relation_id": osm_id} + return {"relation_id": None} diff --git a/building/02_process/relation_id/rule.xml b/building/02_process/relation_id/rule.xml new file mode 100644 index 0000000..701f24c --- /dev/null +++ b/building/02_process/relation_id/rule.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rule name="RelationIDRule"> + <input> + <param type="list">relations</param> + </input> + <function filepath="relation_id.py"/> + <output> + <param type="int">relation_id</param> + </output> +</rule> diff --git a/building/02_process/stories_and_floorspace/rule.xml b/building/02_process/stories_and_floorspace/rule.xml new file mode 100644 index 0000000..24ce38c --- /dev/null +++ b/building/02_process/stories_and_floorspace/rule.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rule name="StoriesAndFloorspaceRule" category="building"> + <input> + <param type="int">tags</param> + <param type="float">relations</param> + <param type="float">area</param> + </input> + <function filepath="stories_and_floorspace.py"/> + <output> + <param type="float">storeys</param> + <param type="float">floorspace</param> + </output> +</rule> diff --git a/building/02_process/stories_and_floorspace/stories_and_floorspace.py b/building/02_process/stories_and_floorspace/stories_and_floorspace.py new file mode 100644 index 0000000..dbf5bd0 --- /dev/null +++ b/building/02_process/stories_and_floorspace/stories_and_floorspace.py @@ -0,0 +1,52 @@ +class StoriesAndFloorspaceRule: + def __call__(self, tags, relations, area, *args, **kwargs): + """ + Find the `building:levels` tag in the attributes of the building or one of the building + relations and save this as the number of stories. Calculate the floorspace of the + building, based on the footprint size of the building and the number of stories. + + Args: + tags (dict): + Building tags, such as the building levels or building type. + relations (list): + List of the attributes of all relations that the building is member of. + area (float): + Footprint size of the building. + + Returns: + A dictionary with the number of stories and the floorspace of the building. + """ + + from math import ceil + + story_options = [ + self.get_story_tag(building["tags"]) for building in [tags] + relations + ] + + for story_string in story_options: + if story_string is None or story_string == "": + continue + try: + stories = ceil(float(story_string)) + if stories < 1: + raise ValueError("Number of stories cannot be below 1") + floorspace = stories * area + return {"stories": stories, "floorspace": floorspace} + except ValueError: + continue + return {"stories": None, "floorspace": None} + + @staticmethod + def get_story_tag(tags): + """ + Get the number of stories, if the attribute `building:levels` exist + + Args: + tags: + Building tags, such as the building levels or building type. + + Returns: + Number of stories, if the attribute `building:levels` exist, otherwise `None`. + """ + + return tags.get("building:levels", None) diff --git a/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py b/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py index ada6f97..862c8b2 100644 --- a/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py +++ b/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py @@ -6,7 +6,7 @@ class ObmBuildingsUpsert: osm_id, floorspace=None, occupancy=None, - storeys=None, + stories=None, relation_id=None, quadkey=None, *args, @@ -27,7 +27,7 @@ class ObmBuildingsUpsert: The calculated floorspace of the building. occupancy (str): The occupancy type formatted to the GEM occupancy types. - storeys (int): + stories (int): Number of stories in the building. relation_id (int): ID of the OSM relation feature that this building is part of. @@ -44,7 +44,7 @@ class ObmBuildingsUpsert: {geometry}, {self.format_value(floorspace)}, {self.format_value(occupancy)}, - {self.format_value(storeys)}, + {self.format_value(stories)}, {self.format_value(relation_id)}, {self.format_value(quadkey)}, NOW() diff --git a/building/03_upsert/obm_buildings_upsert/rule.xml b/building/03_upsert/obm_buildings_upsert/rule.xml index 4ab035b..8ca9c06 100644 --- a/building/03_upsert/obm_buildings_upsert/rule.xml +++ b/building/03_upsert/obm_buildings_upsert/rule.xml @@ -6,7 +6,7 @@ <param type="int">osm_id</param> <param type="float">floorspace</param> <param type="str">occupancy</param> - <param type="int">storeys</param> + <param type="int">stories</param> <param type="int">relation_id</param> <param type="str">quadkey</param> </input> diff --git a/building/README.md b/building/README.md index 41e1b25..f37ee15 100644 --- a/building/README.md +++ b/building/README.md @@ -10,7 +10,23 @@ all land-use objects that intersect the building. ### Process rules -*Not implemented* +* **Geometry** + +Wraps the WKT formatted geometry of a building in the `ST_GeomFromText()` function + +* **Quadkey** + +Determines the Quadkey tile of a building, based on the latitude and longitude of the centroid. + +* **RelationID** + +Determines the building relation OSM ID. + +* **StoriesAndFloorspace** + +Finds the `building:levels` tag in the attributes of the building or one of the building +relations and saves it as number of stories. Calculates the floorspace of the building, based on the +footprint size of the building and the number of stories. ### Upsert rules -- GitLab