Skip to content

Commit 21bdbfe

Browse files
[Fixes #14354] Cannot upload multiple layers from GPKG file (#14355)
* [Fixes #14354] Cannot upload multiple layers from GPKG file
1 parent 0e09ec4 commit 21bdbfe

9 files changed

Lines changed: 80 additions & 33 deletions

File tree

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ jobs:
9898
with:
9999
suite_name: "Upload Tests"
100100
test_command: >
101-
./test.sh geonode.upload --duration=30 --failfast
101+
./test.sh geonode.upload --duration=30
102102
103103
# -------------------------------------------------
104104
# CLEANUP JOB: REMOVE ALL ARTIFACTS AT THE END

geonode/settings.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -968,8 +968,7 @@
968968
MIDDLEWARE += ("geonode.security.middleware.AdminAllowedMiddleware",)
969969

970970
# LOCKDOWN API endpoints to prevent unauthenticated access.
971-
# If set to True, search won't deliver results and filtering ResourceBase-objects is not possible for anonymous users
972-
API_LOCKDOWN = ast.literal_eval(os.getenv("API_LOCKDOWN", "False"))
971+
API_LOCKDOWN = ast.literal_eval(os.getenv("API_LOCKDOWN", "True"))
973972

974973
# Require users to authenticate before using Geonode
975974
LOCKDOWN_GEONODE = ast.literal_eval(os.getenv("LOCKDOWN_GEONODE", "False"))

geonode/upload/handlers/base.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -478,12 +478,17 @@ def identify_authority(self, layer):
478478
if _code is None:
479479
raise Exception("CRS authority code not found, fallback to default behaviour")
480480
except Exception:
481-
spatial_ref = layer.GetSpatialRef()
482-
spatial_ref.AutoIdentifyEPSG()
483-
_name = spatial_ref.GetAuthorityName(None) or spatial_ref.GetAttrValue("AUTHORITY", 0)
484-
_code = (
485-
spatial_ref.GetAuthorityCode("PROJCS")
486-
or spatial_ref.GetAuthorityCode("GEOGCS")
487-
or spatial_ref.GetAttrValue("AUTHORITY", 1)
488-
)
481+
try:
482+
spatial_ref = layer.GetSpatialRef()
483+
spatial_ref.AutoIdentifyEPSG()
484+
_name = spatial_ref.GetAuthorityName(None) or spatial_ref.GetAttrValue("AUTHORITY", 0)
485+
_code = (
486+
spatial_ref.GetAuthorityCode("PROJCS")
487+
or spatial_ref.GetAuthorityCode("GEOGCS")
488+
or spatial_ref.GetAttrValue("AUTHORITY", 1)
489+
)
490+
except Exception:
491+
logger.error(
492+
f"The following layer {layer.GetName()} does not have a Coordinate Reference System (CRS) and will be skipped."
493+
)
489494
return f"{_name}:{_code}"

geonode/upload/handlers/common/tests_vector.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,7 @@ def test_select_valid_layers(self):
518518
The function should return only the datasets with a geometry
519519
The other one are discarded
520520
"""
521-
all_layers = GPKGFileHandler().get_ogr2ogr_driver().Open(self.no_crs_gpkg)
521+
all_layers = GPKGFileHandler().open_source_file({"base_file": self.no_crs_gpkg})
522522

523523
with self.assertLogs(level="ERROR") as _log:
524524
valid_layer = GPKGFileHandler()._select_valid_layers(all_layers)

geonode/upload/handlers/common/vector.py

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ def extract_resource_to_publish(self, files, action, layer_name, alternate, **kw
443443
}
444444
]
445445

446-
layers = self.open_source_file(files)
446+
layers = self._select_valid_layers(self.open_source_file(files), filter_layer=layer_name)
447447
if not layers:
448448
return []
449449
return [
@@ -589,18 +589,39 @@ def open_source_file(self, files):
589589
return [gdal_proxy]
590590

591591
def _select_valid_layers(self, all_layers, **kwargs):
592+
"""
593+
Select valid layers from GDAL datasource objects.
594+
If more than one layer is found, it loop over all the possibility
595+
to extract all the layers.
596+
Is possible to pass a filter_layer argument with the name of the layer
597+
to retrieve only the needed one
598+
"""
599+
filter_layer = kwargs.get("filter_layer", None)
592600
layers = []
593-
for layer in all_layers:
594-
try:
595-
layer = self._extract_layer(layer)
596-
self.identify_authority(layer)
597-
layers.append(layer)
598-
except Exception as e:
599-
logger.error(e)
600-
logger.error(
601-
f"The following layer {layer.GetName()} does not have a Coordinate Reference System (CRS) and will be skipped."
602-
)
603-
pass
601+
602+
for ds in all_layers:
603+
if not ds:
604+
continue
605+
if ds.GetLayerCount() > 0:
606+
candidates_layers = [ds.GetLayerByIndex(i) for i in range(ds.GetLayerCount())]
607+
else:
608+
candidates_layers = [ds]
609+
for layer in candidates_layers:
610+
try:
611+
lry = self._extract_layer(layer)
612+
if not lry:
613+
continue
614+
lry._parent_ds = ds
615+
self.identify_authority(lry)
616+
if filter_layer and self.fixup_name(lry.GetName()) == filter_layer:
617+
return [lry]
618+
layers.append(lry)
619+
except Exception as e:
620+
logger.error(f"Layer skipped due to error: {e}")
621+
622+
if filter_layer and not layers:
623+
logger.warning(f"No layer matching filter '{filter_layer}' was found.")
624+
604625
return layers
605626

606627
def can_overwrite(self, _exec_obj, dataset):

geonode/upload/handlers/gpkg/handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,12 @@ def is_valid(files, user, **kwargs):
107107
actual_upload = upload_validator._get_parallel_uploads_count()
108108
max_upload = upload_validator._get_max_parallel_uploads()
109109

110-
layers = GPKGFileHandler().get_ogr2ogr_driver().Open(files.get("base_file"))
110+
layers = GPKGFileHandler().open_source_file(files)
111111

112112
if not layers:
113113
raise InvalidGeopackageException("The geopackage provided is invalid")
114114

115-
layers_count = len(layers)
115+
layers_count = layers[0].GetLayerCount()
116116

117117
if layers_count >= max_upload:
118118
raise UploadParallelismLimitException(
@@ -152,7 +152,7 @@ def handle_xml_file(self, saved_dataset, _exec):
152152
pass
153153

154154
def _select_valid_layers(self, all_layers, **kwargs):
155-
layers = super()._select_valid_layers(all_layers=all_layers)
155+
layers = super()._select_valid_layers(all_layers=all_layers, **kwargs)
156156
if execution_id := kwargs.get("execution_id", None):
157157
exec_obj = orchestrator.get_execution_object(execution_id)
158158
if exec_obj.action in (ira.REPLACE.value, ira.UPSERT.value):

geonode/upload/handlers/gpkg/tests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def setUpClass(cls):
4141
super().setUpClass()
4242
cls.handler = GPKGFileHandler()
4343
cls.valid_gpkg = f"{project_dir}/tests/fixture/valid.gpkg"
44-
cls.multiple_layer = f"{project_dir}/tests/fixture/multiple_layer.gpkg"
44+
cls.multiple_layer = f"{project_dir}/tests/fixture/multiple_layers.gpkg"
4545
cls.invalid_gpkg = f"{project_dir}/tests/fixture/invalid.gpkg"
4646
cls.user, _ = get_user_model().objects.get_or_create(username="admin")
4747
cls.invalid_files = {"base_file": cls.invalid_gpkg}
@@ -181,14 +181,14 @@ def test_select_valid_layers(self):
181181
step="step",
182182
action="replace",
183183
input_params={
184-
"files": {"base_file": "/tmp/multiple_layer.gpkg"},
184+
"files": {"base_file": "/tmp/multiple_layers.gpkg"},
185185
"skip_existing_layer": True,
186186
"store_spatial_file": False,
187187
"handler_module_path": str(self.handler),
188188
},
189189
)
190190

191-
all_layers = GPKGFileHandler().get_ogr2ogr_driver().Open("/tmp/multiple_layer.gpkg")
191+
all_layers = GPKGFileHandler().open_source_file({"base_file": ("/tmp/multiple_layers.gpkg")})
192192

193193
with self.assertRaises(Exception) as exp:
194194
GPKGFileHandler()._select_valid_layers(all_layers, execution_id=str(exec_id))
@@ -198,4 +198,4 @@ def test_select_valid_layers(self):
198198
exp.exception.args[0],
199199
)
200200

201-
os.remove("/tmp/multiple_layer.gpkg")
201+
os.remove("/tmp/multiple_layers.gpkg")

geonode/upload/tests/end2end/test_end2end.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def setUpClass(cls) -> None:
5555
super().setUpClass()
5656
cls.user = get_user_model().objects.exclude(username="Anonymous").first()
5757
cls.valid_gkpg = f"{project_dir}/tests/fixture/valid.gpkg"
58+
cls.valid_multiple_layers_gkpg = f"{project_dir}/tests/fixture/multiple_layers.gpkg"
5859
cls.valid_geojson = f"{project_dir}/tests/fixture/valid.geojson"
5960
cls.no_crs_gpkg = f"{project_dir}/tests/fixture/noCrsTable.gpkg"
6061
file_path = gisdata.PROJECT_ROOT
@@ -132,9 +133,9 @@ def _assertimport(
132133
while (
133134
ExecutionRequest.objects.get(exec_id=response.json().get("execution_id"))
134135
!= ExecutionRequest.STATUS_FINISHED
135-
and tentative <= 10
136+
and tentative <= 15
136137
):
137-
time.sleep(10)
138+
time.sleep(2)
138139
tentative += 1
139140
exc_obj = ExecutionRequest.objects.get(exec_id=response.json().get("execution_id"))
140141
if exc_obj.status != ExecutionRequest.STATUS_FINISHED:
@@ -228,6 +229,27 @@ def test_file_upload_permissions_none_uploads_successfully(self):
228229
self._cleanup_layers(name="stazioni_metropolitana")
229230

230231

232+
class ImporterMultupleGeoPackageImportTest(BaseImporterEndToEndTest):
233+
@mock.patch.dict(os.environ, {"GEONODE_GEODATABASE": "test_geonode_data"})
234+
@override_settings(GEODATABASE_URL=f"{geourl.split('/geonode_data')[0]}/test_geonode_data")
235+
def test_import_multiple_layers_from_geopackage(self):
236+
self._cleanup_layers(name="area")
237+
self._cleanup_layers(name="example")
238+
try:
239+
resource = None
240+
payload = {"base_file": open(self.valid_multiple_layers_gkpg, "rb"), "action": "upload"}
241+
self._assertimport(payload, "area")
242+
resource = ResourceBase.objects.filter(
243+
Q(alternate__icontains="geonode:example") | Q(alternate__icontains="example")
244+
)
245+
self.assertTrue(resource.exists())
246+
finally:
247+
if resource.first():
248+
resource.first().delete()
249+
self._cleanup_layers(name="area")
250+
self._cleanup_layers(name="example")
251+
252+
231253
class ImporterNoCRSImportTest(BaseImporterEndToEndTest):
232254
@override_settings(ASYNC_SIGNALS=False)
233255
@mock.patch.dict(os.environ, {"GEONODE_GEODATABASE": "test_geonode_data"})
File renamed without changes.

0 commit comments

Comments
 (0)