From 05912aa5fb610655b6ae4a9242c84d13f537b9ba Mon Sep 17 00:00:00 2001
From: Laurens Oostwegel <laurens@gfz-potsdam.de>
Date: Thu, 21 Sep 2023 15:10:04 +0200
Subject: [PATCH] Add the obm_buildings rule

---
 .../obm_buildings_information.py}             |  2 +-
 .../rule.xml                                  |  4 +-
 .../obm_buildings_upsert.py                   | 86 +++++++++++++++++++
 .../03_upsert/obm_buildings_upsert/rule.xml   | 15 ++++
 building/README.md                            |  6 +-
 5 files changed, 108 insertions(+), 5 deletions(-)
 rename building/01_select/{osm_replication/osm_replication.py => obm_buildings_information/obm_buildings_information.py} (99%)
 rename building/01_select/{osm_replication => obm_buildings_information}/rule.xml (68%)
 create mode 100644 building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py
 create mode 100644 building/03_upsert/obm_buildings_upsert/rule.xml

diff --git a/building/01_select/osm_replication/osm_replication.py b/building/01_select/obm_buildings_information/obm_buildings_information.py
similarity index 99%
rename from building/01_select/osm_replication/osm_replication.py
rename to building/01_select/obm_buildings_information/obm_buildings_information.py
index 23e08c9..1937757 100644
--- a/building/01_select/osm_replication/osm_replication.py
+++ b/building/01_select/obm_buildings_information/obm_buildings_information.py
@@ -17,7 +17,7 @@
 # along with this program. If not, see http://www.gnu.org/licenses/.
 
 
-class ObmBuildingInformation:
+class ObmBuildingsInformation:
     def __call__(self, database, key, *args, **kwargs):
         """
         Get the information of one building, including the attributes of the building, the
diff --git a/building/01_select/osm_replication/rule.xml b/building/01_select/obm_buildings_information/rule.xml
similarity index 68%
rename from building/01_select/osm_replication/rule.xml
rename to building/01_select/obm_buildings_information/rule.xml
index 3824351..346c6b0 100644
--- a/building/01_select/osm_replication/rule.xml
+++ b/building/01_select/obm_buildings_information/rule.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<rule name="ObmBuildingInformation" category="building">
+<rule name="ObmBuildingsInformation" category="building">
     <input>
         <param type="PostGISDatabase">database</param>
         <param type="int">key</param>
     </input>
-    <function filepath="osm_replication.py"/>
+    <function filepath="obm_buildings_information.py"/>
     <output>
         <param type="dict">building_information</param>
     </output>
diff --git a/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py b/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py
new file mode 100644
index 0000000..ada6f97
--- /dev/null
+++ b/building/03_upsert/obm_buildings_upsert/obm_buildings_upsert.py
@@ -0,0 +1,86 @@
+class ObmBuildingsUpsert:
+    def __call__(
+        self,
+        database,
+        geometry,
+        osm_id,
+        floorspace=None,
+        occupancy=None,
+        storeys=None,
+        relation_id=None,
+        quadkey=None,
+        *args,
+        **kwargs,
+    ):
+        """
+        Insert or update a building in the `obm_buildings` table.
+
+        Args:
+            database (PostGISDatabase):
+                Exposure database.
+            geometry (str):
+                WKT formatted geometry of the building, wrapped in the `ST_GeomFromText()`
+                function.
+            osm_id (int):
+                OSM ID of the building.
+            floorspace (float):
+                The calculated floorspace of the building.
+            occupancy (str):
+                The occupancy type formatted to the GEM occupancy types.
+            storeys (int):
+                Number of stories in the building.
+            relation_id (int):
+                ID of the OSM relation feature that this building is part of.
+            quadkey (str):
+                Quadkey of the tile that the building is located in.
+        """
+
+        sql_statement = f"""
+            INSERT INTO obm_buildings
+                (osm_id, geometry, floorspace, occupancy, storeys, relation_id, quadkey,
+                last_update)
+            VALUES (
+                {osm_id},
+                {geometry},
+                {self.format_value(floorspace)},
+                {self.format_value(occupancy)},
+                {self.format_value(storeys)},
+                {self.format_value(relation_id)},
+                {self.format_value(quadkey)},
+                NOW()
+                )
+            ON CONFLICT (osm_id) DO UPDATE
+                SET geometry = excluded.geometry,
+                    floorspace = excluded.floorspace,
+                    occupancy = excluded.occupancy,
+                    storeys = excluded.storeys,
+                    relation_id = excluded.relation_id,
+                    quadkey = excluded.quadkey,
+                    last_update = excluded.last_update
+            """
+
+        database.cursor.execute(sql_statement)
+        database.connection.commit()
+
+    @staticmethod
+    def format_value(value):
+        """
+        Format values correctly to be used in an SQL statement.
+
+        Args:
+            value (object):
+                The value that needs to be formatted.
+
+        Returns:
+            Correctly formatted value.
+        """
+
+        # None values should be `NULL` in PostgreSQL
+        if value is None:
+            return "NULL"
+        # If a value is a string, it should be in quotation marks
+        elif isinstance(value, str):
+            return f"'{value}'"
+        # Else return a string representation of the value
+        else:
+            return str(value)
diff --git a/building/03_upsert/obm_buildings_upsert/rule.xml b/building/03_upsert/obm_buildings_upsert/rule.xml
new file mode 100644
index 0000000..4ab035b
--- /dev/null
+++ b/building/03_upsert/obm_buildings_upsert/rule.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<rule name="ObmBuildingsUpsert" category="building">
+    <input>
+        <param type="PostGISDatabase">database</param>
+        <param type="str">geometry</param>
+        <param type="int">osm_id</param>
+        <param type="float">floorspace</param>
+        <param type="str">occupancy</param>
+        <param type="int">storeys</param>
+        <param type="int">relation_id</param>
+        <param type="str">quadkey</param>
+    </input>
+    <function filepath="obm_buildings_upsert.py"/>
+    <output />
+</rule>
diff --git a/building/README.md b/building/README.md
index eebbb72..41e1b25 100644
--- a/building/README.md
+++ b/building/README.md
@@ -2,7 +2,7 @@
 
 ### Select rules
 
-* **osm_replication**
+* **ObmBuildingsInformation**
 
 Get the information of one building, including the attributes of the building, the attributes of
 all relations that the building is member of, the attributes of all POIs inside the building and
@@ -14,4 +14,6 @@ all land-use objects that intersect the building.
 
 ### Upsert rules
                                   
-*Not implemented*
+* **ObmBuildingsUpsert**
+
+Insert or update a building in the `obm_buildings` table.
-- 
GitLab