Skip to content
Snippets Groups Projects
Commit ca750c99 authored by Laurens Oostwegel's avatar Laurens Oostwegel
Browse files

Add height from the GHSL characteristics type

parent 4ebb5bef
No related branches found
No related tags found
1 merge request!14Resolve "Add height from GHSL characteristics"
Pipeline #78842 passed
#!/usr/bin/env python3
# Copyright (c) 2024:
# Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see http://www.gnu.org/licenses/.
class GHSLCharacteristicsInformation:
def __call__(self, database, geometry_wkt, *args, **kwargs):
"""
Intersect the building geometry with the GHSL characteristics layer and return the type
with the largest overlap.
Args:
database (PostGISDatabase):
GHSL source database.
geometry_wkt (string):
The WKT representation of the geometry of the building.
Returns:
A dictionary with the GHSL characteristics type exhibiting the largest overlap with
the building.
"""
# Get the total area of the intersection between a GHSL type and the building.
get_area_function = f"""
SUM(
ST_Area(
ST_Intersection(geom, ST_Buffer(ST_GeomFromText('{geometry_wkt}', 4326),0)
), True)
)
"""
# Select the type with the largest overlap with the building.
sql_statement = f"""
SELECT type
FROM (
SELECT type, {get_area_function} AS area
FROM ghsl_characteristics
WHERE geom && ST_GeomFromText('{geometry_wkt}', 4326)
GROUP BY type
) AS ghsl_area
ORDER BY area DESC
LIMIT 1
"""
database.cursor.execute(sql_statement)
result = database.cursor.fetchone()
if result:
return {"ghsl_characteristics_type": result[0]}
else:
return {"ghsl_characteristics_type": None}
<?xml version="1.0" encoding="UTF-8" ?>
<rule name="GHSLCharacteristicsInformation" category="building">
<input>
<param type="PostGISDatabase">database</param>
<param type="string">geometry_wkt</param>
</input>
<function filepath="ghsl_characteristics_information.py"/>
<dependencies>
<dependency name="ObmBuildingsInformation"/>
</dependencies>
<output>
<param type="dict">ghsl_characteristics_type</param>
</output>
</rule>
...@@ -43,7 +43,7 @@ class ObmBuildingsInformation: ...@@ -43,7 +43,7 @@ class ObmBuildingsInformation:
hstore_to_matrix(obp.tags) AS tags, hstore_to_matrix(obp.tags) AS tags,
ST_X(ST_Centroid(geometry)) AS longitude, ST_X(ST_Centroid(geometry)) AS longitude,
ST_Y(ST_Centroid(geometry)) AS latitude, ST_Y(ST_Centroid(geometry)) AS latitude,
ST_AsText(geometry) AS geometry, ST_AsText(geometry) AS geometry_wkt,
ST_Area(geometry, True) AS area ST_Area(geometry, True) AS area
FROM osm_building_polygons AS obp FROM osm_building_polygons AS obp
WHERE obp.osm_id = {key} WHERE obp.osm_id = {key}
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
class HeightAndFloorspaceRule: class HeightAndFloorspaceRule:
def __call__(self, tags, area, *args, **kwargs): def __call__(self, tags, area, ghsl_characteristics_type=None, *args, **kwargs):
""" """
Find the `building:levels` tag in the attributes of the building or one of the building 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 relations and save this as the number of stories. Calculate the floorspace of the
...@@ -29,6 +29,9 @@ class HeightAndFloorspaceRule: ...@@ -29,6 +29,9 @@ class HeightAndFloorspaceRule:
Building tags, such as the building levels or building type. Building tags, such as the building levels or building type.
area (float): area (float):
Footprint size of the building. Footprint size of the building.
ghsl_characteristics_type (int):
The GHSL characteristics type with the largest overlap with the building.
Returns: Returns:
A dictionary with the number of stories and the floorspace of the building. A dictionary with the number of stories and the floorspace of the building.
...@@ -56,6 +59,12 @@ class HeightAndFloorspaceRule: ...@@ -56,6 +59,12 @@ class HeightAndFloorspaceRule:
"floorspace": floorspace, "floorspace": floorspace,
} }
# If no information can be retrieved from OSM, we use the height estimation from GHSL
# and do not estimate any floorspace.
ghsl_height = self.get_stories_from_ghsl(ghsl_characteristics_type)
if ghsl_height:
return {"height": ghsl_height, "floorspace": None}
return {"height": None, "floorspace": None} return {"height": None, "floorspace": None}
def get_stories_and_floorspace_from_osm(self, tags: dict, area: float): def get_stories_and_floorspace_from_osm(self, tags: dict, area: float):
...@@ -158,6 +167,33 @@ class HeightAndFloorspaceRule: ...@@ -158,6 +167,33 @@ class HeightAndFloorspaceRule:
return f"HHT:{height:.2f}" return f"HHT:{height:.2f}"
def get_stories_from_ghsl(self, ghsl_characteristics_type: int):
"""
Get the GEM Taxonomy `height` attribute, based on the type in the GHSL
characteristics layer that has the largest overlap with the building.
Args:
ghsl_characteristics_type (int):
The GHSL characteristics type with the largest overlap with the building.
Returns:
A string formatted according to the `height` attribute in the GEM Taxonomy.
"""
ghsl_type_map = {
11: "HBET:1-2", # res_3
12: "HBET:1-2", # res_3_6
13: "HBET:1-5", # res_6_15
14: None, # res_15_30
15: None, # res_30
21: "HBET:1-2", # nonres_3
22: "HBET:1-2", # nonres_3_6
23: "HBET:1-5", # nonres_6_15
24: None, # nonres_15_30
25: None, # nonres_30
}
return ghsl_type_map.get(ghsl_characteristics_type, None)
def tag_to_float(self, tags: dict, tag_key: str): def tag_to_float(self, tags: dict, tag_key: str):
""" """
Try to parse a tag as a floating point value. If the value cannot be parsed, return Try to parse a tag as a floating point value. If the value cannot be parsed, return
......
...@@ -70,6 +70,24 @@ def test_height_and_floorspace_rule(height_and_floorspace_rule): ...@@ -70,6 +70,24 @@ def test_height_and_floorspace_rule(height_and_floorspace_rule):
"H:5+HBEX:1+HHT:3.00", # Expected return height. "H:5+HBEX:1+HHT:3.00", # Expected return height.
1620.0, # Expected return floorspace. 1620.0, # Expected return floorspace.
], ],
# Check with building level tag and GHSL.
[
{"tags": {"building:levels": "5"}, "area": 100.0, "ghsl_characteristics_type": 11},
"H:5", # Expected return height.
450.0, # Expected return floorspace.
],
# Check with building height tag and GHSL.
[
{"tags": {"height": "6"}, "area": 100.0, "ghsl_characteristics_type": 11},
"HHT:6.00", # Expected return height.
None, # Expected return floorspace.
],
# Check with only GHSL.
[
{"tags": {}, "area": 100.0, "ghsl_characteristics_type": 11},
"HBET:1-2", # Expected return height.
None, # Expected return floorspace.
],
] ]
for building_information, correct_height, correct_floorspace in test_list: for building_information, correct_height, correct_floorspace in test_list:
...@@ -147,7 +165,7 @@ def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule): ...@@ -147,7 +165,7 @@ def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule):
def test_get_height_from_osm(height_and_floorspace_rule): def test_get_height_from_osm(height_and_floorspace_rule):
""" """
Test the function `tag_to_float` of the `HeightAndFloorspaceRule`. Test the function `get_height_from_osm` of the `HeightAndFloorspaceRule`.
""" """
rule_function = height_and_floorspace_rule.function.get_height_from_osm rule_function = height_and_floorspace_rule.function.get_height_from_osm
...@@ -182,6 +200,35 @@ def test_get_height_from_osm(height_and_floorspace_rule): ...@@ -182,6 +200,35 @@ def test_get_height_from_osm(height_and_floorspace_rule):
assert height == correct_height, message assert height == correct_height, message
def test_get_stories_from_ghsl(height_and_floorspace_rule):
"""
Test the function `get_stories_from_ghsl` of the `HeightAndFloorspaceRule`.
"""
rule_function = height_and_floorspace_rule.function.get_stories_from_ghsl
test_list = [
# Check a valid type.
[11, "HBET:1-2"],
# Check a type that doesn't return a value.
[25, None],
# Check a type that doesn't exist.
[1, None],
# Check the absence of a value.
[None, None],
]
for ghsl_characteristics_type, correct_height in test_list:
height = rule_function(ghsl_characteristics_type)
# Check the height attribute.
message = (
f"The `height` attribute is not correct. Expected `{correct_height}` but estimated "
f"`{height}` using {ghsl_characteristics_type} as input."
)
assert height == correct_height, message
def test_tag_to_float(height_and_floorspace_rule): def test_tag_to_float(height_and_floorspace_rule):
""" """
Test the function `tag_to_float` of the `HeightAndFloorspaceRule. Test the function `tag_to_float` of the `HeightAndFloorspaceRule.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment