Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Concern multiple geometries in same table for geopackage #108

Merged
merged 13 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion modelbaker/db_factory/gpkg_layer_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ def __init__(self, uri: str) -> None:
LayerUri.__init__(self, uri)
self.provider = "ogr"

def get_data_source_uri(self, record: dict) -> str:
def get_data_source_uri(self, record: dict, multigeom: bool) -> str:
data_source_uri = "{uri}|layername={table}".format(
uri=self.uri, table=record["tablename"]
)
if multigeom:
data_source_uri = "{} ({})".format(
data_source_uri, record["geometry_column"]
)
return data_source_uri
2 changes: 1 addition & 1 deletion modelbaker/db_factory/layer_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(self, uri: str) -> None:
self.provider = None

@abstractmethod
def get_data_source_uri(self, record: dict) -> str:
def get_data_source_uri(self, record: dict, multigeom: bool) -> str:
"""Provides layer uri based on database uri and specific information of the data source.

:param str record: Dictionary containing specific information of the data source.
Expand Down
2 changes: 1 addition & 1 deletion modelbaker/db_factory/mssql_layer_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, uri: str) -> None:
LayerUri.__init__(self, uri)
self.provider = "mssql"

def get_data_source_uri(self, record: dict) -> str:
def get_data_source_uri(self, record: dict, multigeom: bool) -> str:
if record["geometry_column"]:
data_source_uri = '{uri} key={primary_key} estimatedmetadata=true srid={srid} type={type} table="{schema}"."{table}" ({geometry_column}) sql='.format(
uri=self._get_layer_uri_common(),
Expand Down
2 changes: 1 addition & 1 deletion modelbaker/db_factory/pg_layer_uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self, uri: str) -> None:
self.pg_estimated_metadata = False
self.provider = "postgres"

def get_data_source_uri(self, record: dict) -> str:
def get_data_source_uri(self, record: dict, multigeom: bool) -> str:
if record["geometry_column"]:
str_pg_estimated_metadata = (
"true" if self.pg_estimated_metadata else "false"
Expand Down
6 changes: 6 additions & 0 deletions modelbaker/dbconnector/db_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ def get_classes_relevance(self):
"""
return []

def multiple_geometry_tables(self):
"""
Returns a list of tables having multiple geometry columns.
"""
return []

def create_basket(
self, dataset_tid, topic, tilitid_value=None, attachment_key="modelbaker"
):
Expand Down
19 changes: 19 additions & 0 deletions modelbaker/dbconnector/gpkg_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,25 @@ def get_classes_relevance(self):
return contents
return []

def multiple_geometry_tables(self):
tables = []
if self._table_exists("gpkg_geometry_columns"):
cursor = self.conn.cursor()
cursor.execute(
"""
SELECT table_name
FROM gpkg_geometry_columns
GROUP BY table_name
HAVING COUNT(table_name)>1
"""
)
records = cursor.fetchall()
cursor.close()
for record in records:
tables.append(record["table_name"])

return tables

def create_basket(
self, dataset_tid, topic, tilitid_value=None, attachment_key="modelbaker"
):
Expand Down
8 changes: 7 additions & 1 deletion modelbaker/generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,13 @@ def layers(self, filter_layer_list: list = []) -> list[Layer]:

layer = Layer(
layer_uri.provider,
layer_uri.get_data_source_uri(record),
layer_uri.get_data_source_uri(
record,
bool(
table_appearance_count[record["tablename"]] > 1
and "geometry_column" in record
),
),
record.get("tablename"),
record.get("srid"),
record.get("extent"),
Expand Down
8 changes: 8 additions & 0 deletions modelbaker/iliwrapper/ili2dbconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qgis.core import QgsNetworkAccessManager
from qgis.PyQt.QtNetwork import QNetworkProxy

from .globals import DbIliMode
from .ili2dbutils import get_all_modeldir_in_path


Expand Down Expand Up @@ -239,6 +240,7 @@ def __init__(self, other: Ili2DbCommandConfiguration = None):
self.create_import_tid = True
self.srs_auth = "EPSG" # Default SRS auth in ili2db
self.srs_code = 2056 # Default SRS code in ili2db
self.create_gpkg_multigeom = False
self.stroke_arcs = True
self.pre_script = ""
self.post_script = ""
Expand Down Expand Up @@ -297,6 +299,12 @@ def to_ili2db_args(self, extra_args=[], with_action=True):
elif self.db_ili_version is None or self.db_ili_version > 3:
self.append_args(args, ["--strokeArcs=False"])

if self.tool & DbIliMode.gpkg:
if self.create_gpkg_multigeom:
self.append_args(args, ["--gpkgMultiGeomPerTable"], True)
elif self.db_ili_version is None or self.db_ili_version > 3:
self.append_args(args, ["--gpkgMultiGeomPerTable=False"])

if self.create_basket_col:
self.append_args(args, ["--createBasketCol"])
elif self.db_ili_version is None or self.db_ili_version > 3:
Expand Down
6 changes: 3 additions & 3 deletions modelbaker/iliwrapper/ili2dbtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ def get_tool_version(tool, db_ili_version):
if db_ili_version == 3:
return "3.11.3"
else:
return "5.1.0"
return "5.1.1"
elif tool == DbIliMode.ili2pg:
if db_ili_version == 3:
return "3.11.2"
else:
return "5.1.0"
return "5.1.1"
elif tool == DbIliMode.ili2mssql:
if db_ili_version == 3:
return "3.12.2"
else:
return "5.1.0"
return "5.1.1"

return "0"

Expand Down
159 changes: 159 additions & 0 deletions tests/test_multiple_geom_gpkg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""
/***************************************************************************
-------------------
begin : 28.10.2024
git sha : :%H$
copyright : (C) 2024 by Dave Signer
email : [email protected]
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""

import datetime
import logging
import os
import pathlib
import tempfile

from osgeo import gdal
from qgis.core import QgsProject
from qgis.testing import start_app, unittest

import modelbaker.utils.db_utils as db_utils
from modelbaker.dataobjects.project import Project
from modelbaker.db_factory.gpkg_command_config_manager import GpkgCommandConfigManager
from modelbaker.generator.generator import Generator
from modelbaker.iliwrapper import iliimporter
from modelbaker.iliwrapper.globals import DbIliMode
from tests.utils import iliimporter_config, testdata_path

start_app()

test_path = pathlib.Path(__file__).parent.absolute()


class TestMultipleGeometriesPerTable(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Run before all tests."""
cls.basetestpath = tempfile.mkdtemp()

def test_multiple_geom_geopackage(self):
"""
Checks when the gdal version is sufficient (means >=3.4) if tables are created with multiple geometries and the correct layers are generated.
This of course depends with what gdal version the current images are built.
"""

importer = iliimporter.Importer()
importer.tool = DbIliMode.ili2gpkg
importer.configuration = iliimporter_config(importer.tool)
importer.configuration.ilifile = testdata_path("ilimodels/MultipleGeom.ili")
importer.configuration.ilimodels = "MultipleGeom"
importer.configuration.dbfile = os.path.join(
self.basetestpath,
"tmp_multiple_geom_{:%Y%m%d%H%M%S%f}.gpkg".format(datetime.datetime.now()),
)
importer.configuration.srs_code = 2056
importer.configuration.inheritance = "smart2"
importer.configuration.create_basket_col = True

# create it when there's a sufficient gdal version
importer.configuration.create_gpkg_multigeom = self._sufficient_gdal()

importer.stdout.connect(self.print_info)
importer.stderr.connect(self.print_error)
assert importer.run() == iliimporter.Importer.SUCCESS

# check geometry table
# check if there are multiple geometry columns of the same table
db_connector = db_utils.get_db_connector(importer.configuration)
tables_with_multiple_geometries = db_connector.multiple_geometry_tables()

bool(len(tables_with_multiple_geometries) > 0)

# should have multiple when having a sufficient gdal and otherwise not
if self._sufficient_gdal():
assert len(tables_with_multiple_geometries) > 0
else:
assert len(tables_with_multiple_geometries) == 0

# create project
config_manager = GpkgCommandConfigManager(importer.configuration)
uri = config_manager.get_uri()

generator = Generator(
tool=DbIliMode.ili2gpkg,
uri=uri,
inheritance=importer.configuration.inheritance,
consider_basket_handling=True,
)

project = Project()

available_layers = generator.layers()
relations, _ = generator.relations(available_layers)
legend = generator.legend(available_layers)
project.layers = available_layers
project.relations = relations
project.legend = legend
project.post_generate()

qgis_project = QgsProject.instance()
project.create(None, qgis_project)

# check layertree
root = qgis_project.layerTreeRoot()
assert root is not None

tree_layers = root.findLayers()
assert len(tree_layers) == 7

{layer.name() for layer in tree_layers}
expected_layer_names_with_multigeometry = {
"NoGeomClass",
"POI",
"GOI (Point)",
"T_ILI2DB_BASKET",
"T_ILI2DB_DATASET",
"GOI (Line)",
"GOI (Surface)",
}
expected_layer_names_without_multigeometry = {
"NoGeomClass",
"GOI",
"POI",
"GOI (Line)",
"GOI (Surface)",
"T_ILI2DB_BASKET",
"T_ILI2DB_DATASET",
}

if self._sufficient_gdal():
assert {
layer.name() for layer in tree_layers
} == expected_layer_names_with_multigeometry
else:
assert {
layer.name() for layer in tree_layers
} == expected_layer_names_without_multigeometry

def _sufficient_gdal(self):
return bool(int(gdal.VersionInfo("VERSION_NUM")) >= 3080000)

def print_info(self, text):
logging.info(text)

def print_error(self, text):
logging.error(text)

def tearDown(self):
QgsProject.instance().removeAllMapLayers()
QgsProject.instance().clear()
32 changes: 32 additions & 0 deletions tests/testdata/ilimodels/MultipleGeom.ili
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
INTERLIS 2.3;

MODEL MultipleGeom (en)
AT "http://modelbaker.ch"
VERSION "2024-06-22" =

IMPORTS GeometryCHLV95_V1;

DOMAIN
Line = POLYLINE WITH (STRAIGHTS) VERTEX GeometryCHLV95_V1.Coord2;
Surface = SURFACE WITH (STRAIGHTS) VERTEX GeometryCHLV95_V1.Coord2 WITHOUT OVERLAPS > 0.005;

TOPIC Spots =

CLASS POI =
Name: TEXT;
Point: GeometryCHLV95_V1.Coord2;
END POI;

CLASS GOI =
Name: TEXT;
Point: GeometryCHLV95_V1.Coord2;
Line: Line;
Surface: Surface;
END GOI;

CLASS NoGeomClass =
Name: TEXT;
END NoGeomClass;
END Spots;

END MultipleGeom.
Loading