summaryrefslogtreecommitdiff
path: root/ishtar_common/models_imports.py
diff options
context:
space:
mode:
authorÉtienne Loks <etienne.loks@iggdrasil.net>2022-03-31 17:38:31 +0200
committerÉtienne Loks <etienne.loks@iggdrasil.net>2022-12-12 12:21:00 +0100
commit810cf25c85cd44fe1610db4792693983a81c8818 (patch)
tree8c890597afae74c44266ccbec14882a772a201d4 /ishtar_common/models_imports.py
parent5dfc4cfee03d1bb8cc85f7148d9e3ce3344a4e30 (diff)
downloadIshtar-810cf25c85cd44fe1610db4792693983a81c8818.tar.bz2
Ishtar-810cf25c85cd44fe1610db4792693983a81c8818.zip
Geodata: gpkg, shp import
Diffstat (limited to 'ishtar_common/models_imports.py')
-rw-r--r--ishtar_common/models_imports.py188
1 files changed, 179 insertions, 9 deletions
diff --git a/ishtar_common/models_imports.py b/ishtar_common/models_imports.py
index b931cc4b4..b8c842284 100644
--- a/ishtar_common/models_imports.py
+++ b/ishtar_common/models_imports.py
@@ -19,9 +19,13 @@
import csv
import datetime
+import fiona
+from fiona import crs as fiona_crs
import json
+from osgeo import ogr
import os
import logging
+from pyproj import CRS
import shutil
import re
import tempfile
@@ -30,6 +34,9 @@ import zipfile
from django.apps import apps
from django.conf import settings
from django.contrib.gis.db import models
+from django.contrib.gis.gdal.error import GDALException
+from django.contrib.gis.geos import GEOSGeometry
+from django.contrib.gis.geos.error import GEOSException
from django.core.exceptions import ValidationError
from django.core.files import File
from django.core.files.base import ContentFile
@@ -75,6 +82,7 @@ from ishtar_common.data_importer import (
LowerCaseFormater,
)
from ishtar_common.utils import task
+from ishtar_common.ignf_utils import IGNF
logger = logging.getLogger(__name__)
@@ -124,6 +132,8 @@ IMPORT_TYPES = (
("gis", _("GIS")),
)
+IMPORT_TYPES_DICT = dict(IMPORT_TYPES)
+
class ImporterType(models.Model):
"""
@@ -185,6 +195,12 @@ class ImporterType(models.Model):
def __str__(self):
return self.name
+ @property
+ def type_label(self):
+ if self.type in IMPORT_TYPES_DICT:
+ return IMPORT_TYPES_DICT[self.type]
+ return ""
+
def get_libreoffice_template(self):
if not UnoCalc:
return
@@ -320,6 +336,8 @@ class ImporterType(models.Model):
"UNICITY_KEYS": UNICITY_KEYS,
"LINE_EXPORT_FORMAT": LINE_EXPORT_FORMAT,
"MODEL_CREATION_LIMIT": MODEL_CREATION_LIMIT,
+ "TYPE": self.type,
+ "MAIN_GEO": self.is_main_geometry,
}
name = str(
"".join(
@@ -1068,6 +1086,45 @@ def delayed_check(import_pk):
imp.check_modified()
+RE_NUMBER = r"[+-]?\d+(?:\.\d*)?"
+RE_COORDS = r"(" + RE_NUMBER + r") (" + RE_NUMBER + r")"
+
+
+def _reverse_coordinates(wkt):
+ # TODO: à effacer
+ return re.sub(RE_COORDS, r"\2 \1", wkt)
+
+
+def convert_geom(feature, srid):
+ geo_type = feature["type"]
+ if geo_type in ("LineString", "Polygon"):
+ feature["type"] = "Multi" + geo_type
+ feature["coordinates"] = [feature["coordinates"]]
+ feature = GEOSGeometry(json.dumps(feature))
+ has_z = feature.hasz
+ feature = f"SRID={srid};{feature.wkt}"
+
+ IshtarSiteProfile = apps.get_model("ishtar_common", "IshtarSiteProfile")
+ profile = IshtarSiteProfile.get_current_profile()
+ srs = 4326 if not has_z else 4979
+ if profile.srs and profile.srs.srid:
+ srs = profile.srs.srid
+ if srs != srid:
+ feature = GEOSGeometry(feature).transform(srs, clone=True).ewkt
+ return feature
+
+
+IMPORT_GEOMETRY = {
+ "Point": "point_2d",
+ "3D Point": "point_3d",
+ "MultiPoint": "multi_points",
+ "LineString": "multi_line",
+ "MultiLineString": "multi_line",
+ "Polygon": "multi_polygon",
+ "MultiPolygon": "multi_polygon",
+}
+
+
class Import(models.Model):
user = models.ForeignKey(
"IshtarUser", blank=True, null=True, on_delete=models.SET_NULL
@@ -1208,6 +1265,8 @@ class Import(models.Model):
return errors
def get_number_of_lines(self):
+ if self.importer_type.type == "gis":
+ return
if self.number_of_line:
return self.number_of_line
if not self.imported_file or not self.imported_file.path:
@@ -1291,13 +1350,11 @@ class Import(models.Model):
IshtarSiteProfile = apps.get_model("ishtar_common", "IshtarSiteProfile")
profile = IshtarSiteProfile.get_current_profile()
actions = []
- if self.state == 'C':
- actions.append(('A', _("Analyse")))
- if self.state in ('C', 'A'):
- actions.append(('ED', _("Edit")))
- if self.state in ('A', 'PI'):
- actions.append(('A', _("Re-analyse")))
- actions.append(('I', _("Launch import")))
+ if self.state == "C":
+ actions.append(("A", _("Analyse")))
+ if self.state in ("A", "PI"):
+ actions.append(("A", _("Re-analyse")))
+ actions.append(("I", _("Launch import")))
if profile.experimental_feature:
if self.changed_checked:
actions.append(("IS", _("Step by step import")))
@@ -1317,6 +1374,8 @@ class Import(models.Model):
if self.state == "AC":
state = "FE" if self.error_file else "F"
actions.append((state, _("Unarchive")))
+ if self.state in ("C", "A"):
+ actions.append(("ED", _("Edit")))
actions.append(("D", _("Delete")))
return actions
@@ -1337,8 +1396,7 @@ class Import(models.Model):
conservative_import=self.conservative_import,
)
- @property
- def data_table(self):
+ def _data_table_tab(self):
imported_file = self.imported_file.path
tmpdir = None
if zipfile.is_zipfile(imported_file):
@@ -1373,6 +1431,118 @@ class Import(models.Model):
shutil.rmtree(tmpdir)
return []
+ def get_gis_attr(self):
+ return self._data_table_gis(get_gis_attr=True)
+
+ def _data_table_gis(self, get_gis_attr=False):
+ self.gis_attr = None
+ imported_file = self.imported_file.path
+ tmp_dir = None
+ file_type = "gpkg"
+ if zipfile.is_zipfile(imported_file):
+ z = zipfile.ZipFile(imported_file)
+ imported_file = None
+ filenames = []
+ for name in z.namelist():
+ # get first CSV file found
+ name_l = name.lower()
+ if name_l.endswith(".gpkg"):
+ filenames = [name]
+ break
+ if name_l.endswith(".shp"):
+ file_type = "shp"
+ filenames.append(name)
+ continue
+ for end in [".cpg", ".prj", ".shx", ".dbf"]:
+ if name_l.endswith(end):
+ filenames.append(name)
+ continue
+ if not filenames:
+ return []
+ tmp_dir = tempfile.mkdtemp(prefix="tmp-ishtar-")
+ for filename in filenames:
+ if filename.lower().endswith(".shp") or filename.lower().endswith(
+ ".gpkg"
+ ):
+ imported_file = z.extract(filename, tmp_dir)
+ else:
+ z.extract(filename, tmp_dir)
+ elif not imported_file.endswith(".gpkg"):
+ raise ImporterError(_("Incorrect GIS file."))
+
+ if not imported_file:
+ raise ImporterError(_("Incorrect GIS file."))
+
+ kwargs = {}
+ if self.importer_type.layer_name:
+ kwargs["layer"] = self.importer_type.layer_name
+ try:
+ with fiona.open(imported_file, **kwargs) as collection:
+ schema = collection.schema
+ geometry = schema["geometry"]
+ if geometry not in IMPORT_GEOMETRY:
+ raise ImporterError(_(f'Geometry "{geometry}" not managed.'))
+ self.gis_attr = IMPORT_GEOMETRY[geometry]
+ if get_gis_attr:
+ if tmp_dir:
+ shutil.rmtree(tmp_dir)
+ return self.gis_attr
+ properties = schema["properties"].keys()
+
+ crs = fiona_crs.to_string(collection.crs)
+ if not crs:
+ driver_type = {"shp": "ESRI Shapefile", "gpkg": "GPKG"}
+ driver = ogr.GetDriverByName(driver_type[file_type])
+ shape = driver.Open(imported_file)
+ layer = shape.GetLayer()
+ crs = layer.GetSpatialRef()
+ auth = crs.GetAttrValue("AUTHORITY", 0)
+ srid = crs.GetAttrValue("AUTHORITY", 1)
+ if auth == "EPSG":
+ srid = int(srid)
+ elif auth == "IGNF" and srid in IGNF:
+ srid = IGNF[srid]
+ else:
+ raise ImporterError(_("CRS not managed."))
+
+ # https://pyproj4.github.io/pyproj/stable/gotchas.html#axis-order-changes-in-proj-6
+ elif crs.startswith("+init=epsg:"):
+ srid = crs[len("+init=epsg:"):]
+ else:
+ srid = CRS.from_proj4(crs).to_epsg()
+ data = []
+ # Warning: RuntimeWarning: Sequential read of iterator was interrupted.
+ # Resetting iterator.
+ # not relevant -> bug in fiona 1.8.18 (fixed in 1.8.19)
+ for idx, feature in enumerate(collection):
+ try:
+ line = [
+ convert_geom(feature["geometry"], srid)
+ ]
+ for prop in properties:
+ value = feature["properties"][prop]
+ if value is None:
+ value = ""
+ line.append(str(value))
+ data.append(line)
+ except (TypeError, GDALException, GEOSException) as e:
+ raise ImporterError(
+ _(f"Error reading feature {idx + 1} - {e}")
+ )
+ except fiona.errors.DriverError:
+ raise ImporterError(_("Incorrect GIS file."))
+
+ if tmp_dir:
+ shutil.rmtree(tmp_dir)
+ return data
+
+ @property
+ def data_table(self):
+ if self.importer_type.type == "tab":
+ return self._data_table_tab(), None
+ if self.importer_type.type == "gis":
+ return self._data_table_gis()
+
def initialize(self, user=None, session_key=None):
self.state = "AP"
self.end_date = datetime.datetime.now()