From a612a6c4bfe309bf61f86f4f1026f19823165b00 Mon Sep 17 00:00:00 2001
From: Danijel Schorlemmer <ds@gfz-potsdam.de>
Date: Mon, 20 Jun 2022 15:56:58 +0200
Subject: [PATCH] Implement the export function with optional bounding box
 parameter

---
 exposurelib/database.py | 123 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/exposurelib/database.py b/exposurelib/database.py
index 168e7974..2b2176b8 100644
--- a/exposurelib/database.py
+++ b/exposurelib/database.py
@@ -21,6 +21,8 @@ import sqlite3
 import datetime
 import csv
 import glob
+import shutil
+import os.path
 from exposurelib.utils import get_geom_of_quadkey
 from shapely.geometry import box
 from pygeotile.tile import Tile
@@ -394,6 +396,127 @@ class ExposureDatabase(SpatialiteDatabase):
         self.connection.execute(sql_statement)
         logger.debug("View %s deleted" % name)
 
+    def export(self, export_filepath, bounding_box=None, overwrite=False):
+        """
+        Exports the current database by copying it to the `export_filepath` location.
+        If a bounding box is given, all entities outside the bounding box including dependent
+        data values are removed and the database is compacted to save space.
+
+        Args:
+            export_filepath (str):
+                Filepath to export the database to.
+            bounding_box (dictionary, optional):
+                Dictionary containing the bounding-box coordinates as entries `lon_min`,
+                `lon_max`, `lat_min`, `lat_max`.
+            overwrite (boolean, optional):
+                Flag to allow for overwriting an existing file during export.
+        """
+
+        # Checks if writing is possible
+        if self.database_filepath == export_filepath:
+            raise FileExistsError("Cannot export database to iself. Exiting ...")
+        if os.path.exists(export_filepath) and not overwrite:
+            raise FileExistsError("File %s exists. Exiting ..." % export_filepath)
+        # Copy current database to new location
+        shutil.copyfile(self.database_filepath, export_filepath)
+        if bounding_box is None:
+            return
+
+        # Open the export database
+        try:
+            export_db = ExposureDatabase(export_filepath, self.spatialite_filepath)
+            export_db.connect(init_spatial_metadata=False)
+        except Exception:
+            raise ValueError(
+                "Exposure database %s cannot be created. Exiting ..." % export_filepath
+            )
+
+        # Remove the entities and related datasets outside the bounding box
+        logger.debug("Removing entities outside of bounding box")
+        sql_statement = "DELETE FROM Entity "
+        sql_statement += "WHERE NOT MBRContains(BuildMBR(%f, %f, %f, %f), geom)" % (
+            bounding_box["lon_min"],
+            bounding_box["lat_min"],
+            bounding_box["lon_max"],
+            bounding_box["lat_max"],
+        )
+        export_db.cursor.execute(sql_statement)
+
+        logger.debug("Removing tiles outside of bounding box")
+        sql_statement = """
+            DELETE FROM Tile
+            WHERE quadkey IN
+            (
+                SELECT Tile.quadkey FROM Tile
+                LEFT JOIN Entity ON Tile.quadkey = Entity.quadkey
+                WHERE Entity.id IS NULL
+            )
+        """
+        export_db.cursor.execute(sql_statement)
+
+        logger.debug("Removing buildings outside of bounding box")
+        sql_statement = """
+            DELETE FROM Building
+            WHERE osm_id IN
+            (
+                SELECT Building.osm_id FROM Building
+                LEFT JOIN Entity ON Building.osm_id = Entity.osm_id
+                WHERE Entity.id IS NULL
+            )
+        """
+        export_db.cursor.execute(sql_statement)
+
+        logger.debug("Removing assets outside of bounding box")
+        sql_statement = """
+            DELETE FROM Asset
+            WHERE entity_id IN
+            (
+                SELECT entity_id FROM Asset
+                LEFT JOIN Entity ON Asset.entity_id = Entity.id
+                WHERE Entity.id IS NULL
+            )
+            """
+        export_db.cursor.execute(sql_statement)
+
+        logger.debug("Removing unused taxonomy strings")
+        sql_statement = """
+            DELETE FROM Taxonomy
+            WHERE id IN
+            (
+                SELECT id FROM Taxonomy
+                LEFT JOIN Asset ON Asset.taxonomy_id = Taxonomy.id
+                WHERE Asset.taxonomy_id IS NULL
+            )
+        """
+        export_db.cursor.execute(sql_statement)
+
+        logger.debug("Removing unused damage assessments")
+        sql_statement = """
+            DELETE FROM DamageAssessment
+            WHERE entity_id IN
+            (
+                SELECT entity_id FROM DamageAssessment
+                LEFT JOIN Entity ON DamageAssessment.entity_id = Entity.id
+                WHERE Entity_id IS NULL
+            )
+        """
+        export_db.cursor.execute(sql_statement)
+        sql_statement = """
+            DELETE FROM Damage
+            WHERE assessment_id IN
+            (
+                SELECT assessment_id FROM Damage
+                LEFT JOIN DamageAssessment ON DamageAssessment.id = Damage.assessment_id
+                WHERE DamageAssessment.id IS NULL
+            )
+        """
+        export_db.cursor.execute(sql_statement)
+
+        export_db.connection.commit()
+        logger.debug("Reducing database size")
+        export_db.connection.execute("VACUUM")
+        export_db.close()
+
     def insert_tile_entity(self, quadkey):
         """
         Inserts a tile and its geometry to the `Entity` table. A database trigger checks if the
-- 
GitLab