Skip to content

Commit

Permalink
Merge pull request #24 from tudelft3d/gio-dev
Browse files Browse the repository at this point in the history
Merge for version 0.8.3
  • Loading branch information
gioagu authored Aug 10, 2023
2 parents 63dd0f9 + 59155a6 commit 1209729
Show file tree
Hide file tree
Showing 12 changed files with 349 additions and 133 deletions.
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ The plugin allows to connect to local or remote instances of the free and open-s
As semantic 3D city models tend to be huge datasets and are generally best managed in spatial databases, the main idea behind the development of this plugin is to facilitate access and use of [CityGML](https://en.wikipedia.org/wiki/CityGML)/[CityJSON](https://www.cityjson.org/) data for those practitioners that lack a deep knowledge of the international standard [OCG CityGML data model](https://www.ogc.org/standards/citygml), and/or have limited experience with SQL/Spatial-RDBMSs in general.
The plugin consists of a server-side part (written in PL/pgSQL) and a client-side part (written in Python).

The client-side part offers at the moment three GUI-based tools:
The client-side part offers, at the moment, three GUI-based tools:
- The **Layer Loader**, to load and interact with data in the 3D City Database directly from QGIS
- The **Bulk Deleter**, to delete features from the database, either at all at once, or by means of spatial and feature-related filters.
- The **QGIS Package Administration**, to install the server-side part of the plugin, as well as to set up database user access and user privileges.
- The **QGIS Package Administrator**, to install the server-side part of the plugin, as well as to set up database user access and user privileges.

In particular, the **Layer Loader** offers following functionalities:
- All CityGML modules are supported (Building, Bridge, Tunnel, Vegetation, Terrain, etc.)
Expand All @@ -27,7 +27,7 @@ In particular, the **Layer Loader** offers following functionalities:
- Support for CityGML enumerations and codelists
- All layer geometries are 3D: they can be visualised both in 2D and in 3D (Please be aware that 3D visualisation in QGIS 3D map is still a bit unstable...).

Further details, and a user guide, can be found in the \user_guide subfolder of the plugin installation directory (see file "[3DCityDB-Tools_UserGuide.pdf](user_guide/3DCityDB-Tools_UserGuide_0.8.2.pdf)").
Further details, and a user guide, can be found in the \user_guide subfolder of the plugin installation directory (see file "[3DCityDB-Tools_UserGuide.pdf](user_guide/3DCityDB-Tools_UserGuide_0.8.3.pdf)").

Some datasets for testing purposes are available, too, and are contained in the \test_datasets subfolder.

Expand All @@ -36,28 +36,34 @@ Some datasets for testing purposes are available, too, and are contained in the

# Requirements

The plugin has been developed using [QGIS](https://www.qgis.org/nl/site/forusers/download.html) 3.22 LTR and works best with it. Our tests so far show that it works with any QGIS version >= 3.22, including the recently released version 3.28 LTR. Please note that support and further development will focus only on LTR versions.
The plugin has been developed using [**QGIS**](https://www.qgis.org/en/site/forusers/download.html) **3.22 LTR** and **3.28 LTR**. Please note that support and further development will focus only on LTR versions.

The server-side part of the plugin requires PostgreSQL version >= 10.
The server-side part of the plugin requires PostgreSQL version >= 10 and PostGIS version >= 2.

Otherwise, only a working instance of the 3D City Database is required. The currently supported version of the [3DCityDB](https://github.com/3dcitydb) is the 4.x. To set up the 3D City Database and import (or export) CityGML/CityJSON data from/to it, we heartily recommend to use the free and open-source, Java-based [Importer-Exporter](https://github.com/3dcitydb/importer-exporter). Alternatively, the [3D City Database Suite](https://github.com/3dcitydb/3dcitydb-suite/releases) already ships with all necessary software tools. Further information can be found [here](https://3dcitydb-docs.readthedocs.io/en/latest/).

# Installation

The easiest way to install the plug-in is via the [QGIS Plugins repository](https://plugins.qgis.org/plugins/citydb-tools/), or directly from QGIS. Just look for the 3DCityDB-Tools plug-in!

Alternatively, you can download the plug-in zip file from here and install it manually. Please refer to the installation steps explained in the documentation, which also contains details on how to set up the server-side part of the plug-in.

# Developers

The plugin is currently developed by:
- [Giorgio Agugiaro](mailto:[email protected])
- [Konstantinos Pantelios](mailto:[email protected])

with contributions by:
- [Tendai Mbwanda]([email protected])
- [Tendai Mbwanda](mailto:[email protected])

and with additional suggestions and feedback by Camilo León-Sánchez (TU Delft), Claus Nagel and Zhihang Yao (Virtual City Systems GmbH).
and with additional suggestions and feedback by Camilo León-Sánchez (TU Delft), Claus Nagel and Zhihang Yao (VirtualCitySystems GmbH).

# Future

Besides further testing and debugging, there are a number of improvements that we are thinking of, such as:
- Support for appearances (at least for X3D Materials, if possible)
- Support of ADEs (e.g. the Energy ADE, to start with) (Currently being investigated in an on-going [MSc Geomatics thesis at TU Delft](https://3d.bk.tudelft.nl/education/#theses))
- Support for ADEs (Preliminary work on the Energy ADE has been already carried out in a [MSc Geomatics thesis at TU Delft](https://repository.tudelft.nl/islandora/object/uuid%3A6786ac5c-b61d-4e17-8501-e3cf2c7a9577))
- Testing and initial support for the 3DCityDB v. 5.0 (and therefore CityGML 3.0)
- ...the sky is the limit...

Expand Down
2 changes: 1 addition & 1 deletion cdb4/gui_admin/ui/cdb4_admin_dialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
</size>
</property>
<property name="windowTitle">
<string>QGIS Package Administration</string>
<string>QGIS Package Administrator</string>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
Expand Down
43 changes: 40 additions & 3 deletions cdb4/gui_loader/functions/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ def fetch_codelist_lookup_config(dlg: CDB4LoaderDialog, codelist_set_name: str)
dlg.conn.rollback()


def fetch_codelist_set_names(dlg: CDB4LoaderDialog) -> list:
def fetch_CityGML_codelist_set_names(dlg: CDB4LoaderDialog) -> list:
"""SQL query that retrieves the codelist set names to fill the codelist selection box
* :returns: the unique names in table codelist_lookup_config
Expand Down Expand Up @@ -615,8 +615,45 @@ def fetch_codelist_set_names(dlg: CDB4LoaderDialog) -> list:

except (Exception, psycopg2.Error) as error:
gen_f.critical_log(
func=fetch_codelist_set_names,
func=fetch_CityGML_codelist_set_names,
location=FILE_LOCATION,
header=f"Retrieving codelist set names from table '{dlg.USR_SCHEMA}.codelist_lookup_config'",
header=f"Retrieving CityGML codelist set from table '{dlg.USR_SCHEMA}.codelist_lookup_config'",
error=error)
dlg.conn.rollback()

# def fetch_ADE_codelist_set_names(dlg: CDB4LoaderDialog) -> list:
# """SQL query that retrieves the codelist set names to fill the codelist selection box

# * :returns: the unique names in table codelist_lookup_config
# :rtype: list of tuples
# """
# query = pysql.SQL("""
# SELECT DISTINCT name FROM {_usr_schema}.codelist_lookup_config
# WHERE ade_prefix = {_ade_prefix}
# ORDER BY name;
# """).format(
# _usr_schema = pysql.Identifier(dlg.USR_SCHEMA),
# _ade_prefix = pysql.Identifier(dlg.ADE_PREFIX)
# )

# try:
# # with dlg.conn.cursor(cursor_factory=NamedTupleCursor) as cur:
# with dlg.conn.cursor() as cur:
# cur.execute(query)
# res = cur.fetchall()
# dlg.conn.commit()

# codelist_set_names = [elem[0] for elem in res]

# if not codelist_set_names:
# codelist_set_names = []

# return codelist_set_names

# except (Exception, psycopg2.Error) as error:
# gen_f.critical_log(
# func=fetch_ADE_codelist_set_names,
# location=FILE_LOCATION,
# header=f"Retrieving ADE codelist set from table '{dlg.USR_SCHEMA}.codelist_lookup_config'",
# error=error)
# dlg.conn.rollback()
17 changes: 14 additions & 3 deletions cdb4/gui_loader/functions/tab_conn_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,21 @@ def check_layers_status(dlg: CDB4LoaderDialog) -> bool:
# Also enables the settings tab
# Fill the combo box with the codelist selection

codelist_set_names: list = sql.fetch_codelist_set_names(dlg)
CityGML_codelist_set_names: list = sql.fetch_CityGML_codelist_set_names(dlg)
# print("Initializing combo box with:", codelist_set_names)
if codelist_set_names:
ts_wf.fill_codelist_selection_box(dlg, codelist_set_names)
if CityGML_codelist_set_names:
tl_wf.fill_CityGML_codelist_selection_box(dlg, CityGML_codelist_set_names)

# TO DO
#
# Add similar step to retrieve ADE codelist set
#
# if dlg.ADE_PREFIX:
# ADE_codelist_set_names: list = sql.fetch_ADE_codelist_set_names(dlg)
# # print("Initializing combo box with:", codelist_set_names)
# if ADE_codelist_set_names:
# tl_wf.fill_ADE_codelist_selection_box(dlg, ADE_codelist_set_names)
#

dlg.tabSettings.setDisabled(False)

Expand Down
58 changes: 58 additions & 0 deletions cdb4/gui_loader/functions/tab_layers_widget_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,55 @@
## Setup widget functions for 'Layer' tab
####################################################

def fill_CityGML_codelist_selection_box(dlg: CDB4LoaderDialog, CityGML_codelist_set_names: list = None) -> None:
"""Function that fills the 'Select CodeLists group' combo box.
"""
# Clean combo box from previous leftovers.
dlg.cbxCodeListSelCityGML.clear()

if not CityGML_codelist_set_names:
# Disable the combobox
dlg.cbxCodeListSelCityGML.setDisabled(True)
dlg.cbxCodeListSelCityGML.setDisabled(True)
else:
label: str = f"None"
dlg.cbxCodeListSelCityGML.addItem(label, userData=label)
for codelist_set_name in CityGML_codelist_set_names:
label: str = f"{codelist_set_name}"
dlg.cbxCodeListSelCityGML.addItem(label, userData=label)
if not dlg.cbxCodeListSelCityGML.isEnabled():
# Enable the combobox
dlg.cbxCodeListSelCityGML.setDisabled(False)
dlg.lblCodeListSelCityGML.setDisabled(False)

# REMEMBER: don't use method 'setSeparator', it adds a custom separator to join string of selected items
return None

# def fill_ADE_codelist_selection_box(dlg: CDB4LoaderDialog, ADE_codelist_set_names: list = None) -> None:
# """Function that fills the 'Select CodeLists group' combo box.
# """
# # Clean combo box from previous leftovers.
# dlg.cbxCodeListSelADE.clear()

# if not ADE_codelist_set_names:
# # Disable the combobox
# dlg.cbxCodeListSelADE.setDisabled(True)
# dlg.cbxCodeListSelADE.setDisabled(True)
# else:
# label: str = f"None"
# dlg.cbxCodeListSelADE.addItem(label, userData=label)
# for codelist_set_name in ADE_codelist_set_names:
# label: str = f"{codelist_set_name}"
# dlg.cbxCodeListSelADE.addItem(label, userData=label)
# if not dlg.cbxCodeListSelADE.isEnabled():
# # Enable the combobox
# dlg.cbxCodeListSelADE.setDisabled(False)
# dlg.lblCodeListSelADE.setDisabled(False)

# REMEMBER: don't use method 'setSeparator', it adds a custom separator to join string of selected items
return None


# In 'Basemap (OMS)' groupBox.
def gbxBasemapL_setup(dlg: CDB4LoaderDialog) -> None:
"""Function to setup the 'Basemap' groupbox. It uses an additional canvas instance to store an OSM map
Expand Down Expand Up @@ -59,6 +108,7 @@ def tabLayers_reset(dlg: CDB4LoaderDialog) -> None:
lblInfoText_reset(dlg)
gbxBasemapL_reset(dlg)
gbxLayerSelection_reset(dlg)
gbxCodeListSelection_reset(dlg)
gbxAvailableL_reset(dlg)

return None
Expand Down Expand Up @@ -94,6 +144,14 @@ def gbxLayerSelection_reset(dlg: CDB4LoaderDialog) -> None:

return None

def gbxCodeListSelection_reset(dlg: CDB4LoaderDialog) -> None:
"""Function to reset the 'Miscellaneous option' groupbox to the DEFAULT values
"""
dlg.cbxCodeListSelCityGML.clear()
# dlg.cbxCodeListSelADE.clear()
return None



def gbxAvailableL_reset(dlg: CDB4LoaderDialog) -> None:
"""Function to reset the 'Features to Import' group box (in Layers tab).
Expand Down
56 changes: 28 additions & 28 deletions cdb4/gui_loader/functions/tab_settings_widget_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@
FILE_LOCATION = gen_f.get_file_relative_path(file=__file__)


def fill_codelist_selection_box(dlg: CDB4LoaderDialog, codelist_set_names: list = None) -> None:
"""Function that fills the 'Select CodeLists group' combo box.
"""
# Clean combo box from previous leftovers.
dlg.cbxCodeListSelection.clear()

if not codelist_set_names:
# Disable the combobox
dlg.cbxCodeListSelection.setDisabled(True)
dlg.cbxCodeListSelection.setDisabled(True)
else:
label: str = f"None"
dlg.cbxCodeListSelection.addItem(label, userData=label)
for codelist_set_name in codelist_set_names:
label: str = f"{codelist_set_name}"
dlg.cbxCodeListSelection.addItem(label, userData=label)
if not dlg.cbxCodeListSelection.isEnabled():
# Enable the combobox
dlg.cbxCodeListSelection.setDisabled(False)
dlg.lblCodeListSelection.setDisabled(False)
# def fill_CityGML_codelist_selection_box(dlg: CDB4LoaderDialog, CityGML_codelist_set_names: list = None) -> None:
# """Function that fills the 'Select CodeLists group' combo box.
# """
# # Clean combo box from previous leftovers.
# dlg.cbxCodeListSelCityGML.clear()

# if not CityGML_codelist_set_names:
# # Disable the combobox
# dlg.cbxCodeListSelCityGML.setDisabled(True)
# dlg.cbxCodeListSelCityGML.setDisabled(True)
# else:
# label: str = f"None"
# dlg.cbxCodeListSelCityGML.addItem(label, userData=label)
# for codelist_set_name in CityGML_codelist_set_names:
# label: str = f"{codelist_set_name}"
# dlg.cbxCodeListSelCityGML.addItem(label, userData=label)
# if not dlg.cbxCodeListSelCityGML.isEnabled():
# # Enable the combobox
# dlg.cbxCodeListSelCityGML.setDisabled(False)
# dlg.lblCodeListSelCityGML.setDisabled(False)

# REMEMBER: don't use method 'setSeparator', it adds a custom separator to join string of selected items
return None
# # REMEMBER: don't use method 'setSeparator', it adds a custom separator to join string of selected items
# return None


####################################################
Expand All @@ -51,7 +51,7 @@ def tabSettings_reset(dlg: CDB4LoaderDialog) -> None:
dlg.tabSettings.setDisabled(True)
gbxGeomSimp_reset(dlg)
gbxLayerOptions_reset(dlg)
gbxCodeListSelection_reset(dlg)
# gbxCodeListSelection_reset(dlg)
gbxMisc_reset(dlg)

return None
Expand All @@ -77,12 +77,12 @@ def gbxLayerOptions_reset(dlg: CDB4LoaderDialog) -> None:
return None


def gbxCodeListSelection_reset(dlg: CDB4LoaderDialog) -> None:
"""Function to reset the 'Miscellaneous option' groupbox to the DEFAULT values
"""
dlg.cbxCodeListSelection.clear()
# def gbxCodeListSelection_reset(dlg: CDB4LoaderDialog) -> None:
# """Function to reset the 'Miscellaneous option' groupbox to the DEFAULT values
# """
# dlg.cbxCodeListSelCityGML.clear()

return None
# return None


def gbxMisc_reset(dlg: CDB4LoaderDialog) -> None:
Expand Down
20 changes: 13 additions & 7 deletions cdb4/gui_loader/loader_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def __init__(self, cdbMain: CDBToolsMain, parent=None):
# Dictionary containing config data to set up codelist combo boxes in the attribute forms
self.CodeListConfigRegistry: dict = {}
# Variable to store the selected CodeListSet
self.selectedCodeListSet: str = None
self.selectedCityGMLCodeListSet: str = None

self.CDBSchemaPrivileges: str = None
# self.n_cityobjects: int = 0 # Number of cityobjects in the current cdb_schema
Expand Down Expand Up @@ -1180,24 +1180,30 @@ def evt_btnImport_clicked(self) -> None:
if res == 16384: # YES, proceed with importing layers
success = tl_f.add_selected_layers_to_ToC(self, layers=selected_layers)
else:
return None #Import Cancelled
return None # Import Cancelled
else:

# Codelist settings and loading of the configs
sel_codelist_set: str = self.cbxCodeListSelection.currentData()
if sel_codelist_set == "None":
sel_CityGML_codelist_set: str = self.cbxCodeListSelCityGML.currentData()
if sel_CityGML_codelist_set == "None":
# do nothing
pass
else:
if any([not self.selectedCodeListSet, self.selectedCodeListSet != sel_codelist_set]):
if any([not self.selectedCityGMLCodeListSet, self.selectedCityGMLCodeListSet != sel_CityGML_codelist_set]):
# Set the new value
self.selectedCodeListSet = sel_codelist_set
self.selectedCityGMLCodeListSet = sel_CityGML_codelist_set
# print("Working with Codelist set:", self.selectedCodeListSet)
# Initialize the enum_lookup_config_registry
tl_f.populate_codelist_config_registry(self, codelist_set_name=sel_codelist_set)
tl_f.populate_codelist_config_registry(self, codelist_set_name=sel_CityGML_codelist_set)

success = tl_f.add_selected_layers_to_ToC(self, layers=selected_layers)

# TO DO
#
# Add similar process for selected ADE codelist set
#
#

if not success:
QgsMessageLog.logMessage(
message="Something went wrong while importing the layer(s)",
Expand Down
Loading

0 comments on commit 1209729

Please sign in to comment.