summaryrefslogtreecommitdiff
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
commitbd5db67b05654d8c79b18035cf33853da3541d48 (patch)
tree8c890597afae74c44266ccbec14882a772a201d4
parent221599192781585f4c7ca20dffdcf781b14ac3b9 (diff)
downloadIshtar-bd5db67b05654d8c79b18035cf33853da3541d48.tar.bz2
Ishtar-bd5db67b05654d8c79b18035cf33853da3541d48.zip
Geodata: gpkg, shp import
-rw-r--r--archaeological_context_records/models.py4
-rw-r--r--ishtar_common/data_importer.py44
-rw-r--r--ishtar_common/forms_common.py1
-rw-r--r--ishtar_common/ignf_utils.py181
-rw-r--r--ishtar_common/management/commands/ishtar_migrate_srid.py62
-rw-r--r--ishtar_common/migrations/0219_auto_20220407_1952.py39
-rw-r--r--ishtar_common/models.py16
-rw-r--r--ishtar_common/models_common.py39
-rw-r--r--ishtar_common/models_imports.py188
-rw-r--r--ishtar_common/templates/ishtar/import_table.html2
-rw-r--r--ishtar_common/views.py13
-rw-r--r--requirements.txt1
12 files changed, 573 insertions, 17 deletions
diff --git a/archaeological_context_records/models.py b/archaeological_context_records/models.py
index e9bd70aa8..3a89a6ca9 100644
--- a/archaeological_context_records/models.py
+++ b/archaeological_context_records/models.py
@@ -421,8 +421,10 @@ class GeographicSubTownItem(GeoItem):
)
if has_geo_town:
bad_towns = q_geodata_current_town
- else:
+ elif town:
bad_towns = q_geodata_current_town.exclude(source_id=town.id)
+ else:
+ return
modified = False
for bad_town in bad_towns.all():
diff --git a/ishtar_common/data_importer.py b/ishtar_common/data_importer.py
index 9dbd45e93..1c99782e3 100644
--- a/ishtar_common/data_importer.py
+++ b/ishtar_common/data_importer.py
@@ -26,8 +26,10 @@ import re
import sys
import zipfile
+from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
from django.contrib.gis.geos.error import GEOSException
from django.db.models.fields import FieldDoesNotExist
from django.core.exceptions import FieldError
@@ -831,6 +833,8 @@ class Importer(object):
SLUG = ""
NAME = ""
DESC = ""
+ TYPE = ""
+ MAIN_GEO = False
LINE_FORMAT = []
OBJECT_CLS = None
UNICITY_KEYS = []
@@ -1338,6 +1342,7 @@ class Importer(object):
n2 = n
self.c_errors = False
c_row = []
+ idx_col = 0
for idx_col, val in enumerate(line):
try:
self._row_processing(c_row, idx_col, idx_line, val, data)
@@ -1368,6 +1373,10 @@ class Importer(object):
self.new_objects, self.updated_objects = [], []
self.ambiguous_objects, self.not_find_objects = [], []
+ geodata = {}
+ if self.TYPE == "gis":
+ if "geodata" in data:
+ geodata = data.pop("geodata")
obj, created = self.get_object(self.OBJECT_CLS, data, idx_line=idx_line)
if self.simulate:
return data
@@ -1391,6 +1400,41 @@ class Importer(object):
obj._no_post_save = True
obj.save()
self._add_to_post_save(obj.__class__, obj.pk, idx_line)
+
+ if self.TYPE == "gis":
+ content_type = ContentType.objects.get(
+ app_label=obj.__class__._meta.app_label,
+ model=obj.__class__.__name__.lower()
+ )
+ geodata.update({
+ "source_id": obj.pk,
+ "source_content_type": content_type,
+ })
+ GeoVectorData = apps.get_model("ishtar_common", "GeoVectorData")
+ item = None
+ if "import_key" in geodata:
+ q = GeoVectorData.objects.filter(
+ import_key=geodata["import_key"],
+ source_id=obj.pk,
+ source_content_type=content_type,
+ )
+ if q.count():
+ item = q.all()[0]
+ if item:
+ for k in geodata:
+ setattr(item, k, geodata[k])
+ item.save()
+ else:
+ item = GeoVectorData.objects.create(**geodata)
+ if self.import_instance:
+ item.imports.add(self.import_instance)
+ if self.MAIN_GEO:
+ obj.main_geodata = item
+ obj._post_saved_geo = True
+ obj._no_move = True
+ obj.skip_history_when_saving = True
+ obj.save()
+
n = datetime.datetime.now()
logger.debug("* %s - Item saved" % (str(n - n2)))
n2 = n
diff --git a/ishtar_common/forms_common.py b/ishtar_common/forms_common.py
index c3367eaac..6e9e11006 100644
--- a/ishtar_common/forms_common.py
+++ b/ishtar_common/forms_common.py
@@ -364,6 +364,7 @@ class NewImportGISForm(BaseImportForm):
return value
def save(self, user, commit=True):
+ self.instance.user = user
item = super(NewImportGISForm, self).save(commit)
return item
diff --git a/ishtar_common/ignf_utils.py b/ishtar_common/ignf_utils.py
new file mode 100644
index 000000000..2f167299c
--- /dev/null
+++ b/ishtar_common/ignf_utils.py
@@ -0,0 +1,181 @@
+import xml.etree.ElementTree as ET
+
+
+# from the bellow script
+IGNF = {
+ "BORA01": 5607,
+ "CAD97": 4473,
+ "CAD97UTM38S": 5879,
+ "MAYO50UTM38S": 2980,
+ "CSG67UTM21": 3312,
+ "CSG67UTM22": 2971,
+ "STPM50": 5792,
+ "ED50UTM30": 23030,
+ "ED50UTM32": 23032,
+ "ED50UTM31": 23031,
+ "ETRS89": 4936,
+ "ETRS89LAEA": 3035,
+ "ETRS89LCC": 3034,
+ "ETRS89UTM28": 25828,
+ "ETRS89UTM29": 25829,
+ "ETRS89UTM30": 25830,
+ "ETRS89UTM31": 25831,
+ "ETRS89UTM32": 25832,
+ "ETRS89UTM33": 25833,
+ "ETRS89UTM34": 25834,
+ "ETRS89UTM35": 25835,
+ "ETRS89UTM36": 25836,
+ "ETRS89UTM37": 25837,
+ "EVRF2000": 5730,
+ "EVRF2007": 5621,
+ "GUADFMUTM20": 2969,
+ "GUADANNUTM20": 2970,
+ "HUAH01": 5605,
+ "ATUO63UTM7S": 3302,
+ "TAHITI66": 5601,
+ "MART87": 5756,
+ "GUAD88": 5757,
+ "GUAD88LS": 5616,
+ "GUAD88MG": 5617,
+ "GUAD88SB": 5619,
+ "GUAD88SM": 5620,
+ "REUN89": 5758,
+ "GUAD92LD": 5618,
+ "IGN72UTM58S": 3060,
+ "KERG62UTM42S": 3336,
+ "LIFOU56UT58S": 2981,
+ "MARE53UTM58S": 2995,
+ "MARTFDUTM20": 2973,
+ "MAUPITIUTM5S": 3306,
+ "MAUPITI01": 5604,
+ "MOOREA81": 5602,
+ "MOORE87UTM6S": 3305,
+ "NEA74LBTNM2": 3166,
+ "IGN69": 5720,
+ "IGN78C": 5721,
+ "NGF84": 5719,
+ "GUYA77": 5755,
+ "NGC48": 5791,
+ "NCAL69": 5753,
+ "NNLUX": 5774,
+ "LURESGKL": 2169,
+ "NTFLAMB1": 27561,
+ "NTFLAMB1C": 27571,
+ "NTFLAMB2": 27562,
+ "NTFLAMB2C": 27572,
+ "NTFLAMB2E": 27572,
+ "NTFLAMB3": 27563,
+ "NTFLAMB3C": 27573,
+ "NTFLAMB4": 27564,
+ "NTFLAMB4C": 27574,
+ "PETRE72SPSTA": 2985,
+ "RAIA01": 5603,
+ "RGAF09": 5487,
+ "RGAF09GEODD": 7085,
+ "RGAF09GDD": 7086,
+ "RGAF09G": 7086,
+ "RGAF09GEO": 7085,
+ "RGAF09UTM20": 5490,
+ "RGF93": 4964,
+ "RGF93CC42": 3942,
+ "RGF93CC43": 3943,
+ "RGF93CC44": 3944,
+ "RGF93CC45": 3945,
+ "RGF93CC46": 3946,
+ "RGF93CC47": 3947,
+ "RGF93CC48": 3948,
+ "RGF93CC49": 3949,
+ "RGF93CC50": 3950,
+ "RGF93GDD": 7084,
+ "RGF93GEODD": 7042,
+ "RGF93GEO": 7042,
+ "RGF93G": 7084,
+ "RGFG95": 4966,
+ "RGFG95GDD": 7041,
+ "RGFG95GEODD": 7040,
+ "RGFG95G": 7041,
+ "RGFG95GEO": 7040,
+ "RGFG95UTM21": 3313,
+ "RGFG95UTM22": 2972,
+ "RGM04": 4468,
+ "RGM04GDD": 7039,
+ "RGM04GEODD": 7038,
+ "RGM04G": 7039,
+ "RGM04GEO": 7038,
+ "RGM04UTM38S": 4471,
+ "RGNC": 4906,
+ "RGNCLAMBNC": 3163,
+ "RGNCUTM57S": 3169,
+ "RGNCUTM58S": 3170,
+ "RGNCUTM59S": 3171,
+ "RGPF": 4998,
+ "RGPFUTM5S": 3296,
+ "RGPFUTM6S": 3297,
+ "RGPFUTM7S": 3298,
+ "RGPFUTM8S": 3299,
+ "RGR92": 4970,
+ "RGR92GEODD": 7036,
+ "RGR92GDD": 7037,
+ "RGR92GEO": 7036,
+ "RGR92G": 7037,
+ "RGR92UTM40S": 2975,
+ "RGSPM06": 4465,
+ "RGSPM06GDD": 7035,
+ "RGSPM06GEODD": 7034,
+ "RGSPM06GEO": 7034,
+ "RGSPM06G": 7035,
+ "RGSPM06U21": 4467,
+ "RGTAAF07": 7071,
+ "RGTAAFGEODD": 7087,
+ "RGTAAF07GDD": 7133,
+ "RGTAAF07G": 7133,
+ "RGTAAFGEO": 7087,
+ "RGTAAFUTM37S": 7074,
+ "RGTAAFUTM39S": 7076,
+ "RGTAAFUTM42S": 7079,
+ "RGTAAFUTM43S": 7080,
+ "MAYO53": 5793,
+ "ST84UTM58S": 2996,
+ "ST87UTM58S": 3164,
+ "STPM50UTM21": 2987,
+ "TAHAA01": 5606,
+ "TAHI79UTM6S": 3304,
+ "TAHI51UTM6S": 2976,
+ "PGP50STEREPS": 2986,
+ "WALL78UTM1S": 2988,
+ "WGS72": 4984,
+ "WGS72UTM30": 32230,
+ "WGS72UTM31": 32231,
+ "WGS72UTM32": 32232,
+ "WGS72UTM1S": 32301,
+ "WGS84": 4978,
+ "WGS84UTM30": 32630,
+ "WGS84UTM31": 32631,
+ "WGS84UTM32": 32632,
+ "WGS84UTM1S": 32701,
+ "WGS84UTM39S": 32739,
+ "WGS84UTM42S": 32742,
+ "WGS84WMSV": 3857,
+}
+
+
+def extract_from_csv(filename):
+ tree = ET.parse(filename)
+ root = tree.getroot()
+ ns = "{http://www.isotc211.org/2005/gmx}"
+ ns_gml = "{http://www.opengis.net/gml}"
+ equiv = {}
+ base_srid = "urn:ogc:def:crs:EPSG:9.1.1:"
+
+ for crs_type in root.findall(f"{ns}crs"):
+ for crs in list(crs_type):
+ ignf_id = crs.attrib[f"{ns_gml}id"]
+ for epsg in crs.findall(f"{ns_gml}name"):
+ if epsg.text.startswith(base_srid):
+ equiv[ignf_id] = epsg.text[len(base_srid) :]
+ return equiv
+
+
+if __name__ == "__main__":
+ equiv = extract_from_csv("IGNF.xml")
+ print(equiv)
diff --git a/ishtar_common/management/commands/ishtar_migrate_srid.py b/ishtar_common/management/commands/ishtar_migrate_srid.py
new file mode 100644
index 000000000..19de96cb8
--- /dev/null
+++ b/ishtar_common/management/commands/ishtar_migrate_srid.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2022 Étienne Loks <etienne.loks_AT_peacefrogsDOTnet>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# See the file COPYING for details.
+
+from django.core.management.base import BaseCommand
+from optparse import make_option
+import sys
+
+from ishtar_common.models import GeoVectorData, IshtarSiteProfile
+from ishtar_common.utils import BColors
+
+
+class Command(BaseCommand):
+ help = "Update SRID - change from profile"
+ option_list = BaseCommand.option_list + (
+ make_option('--quiet',
+ action='store_true',
+ dest='quiet',
+ default=False,
+ help='Proceed silently with no interactive input.'),
+ )
+
+ def interactive_start(self):
+ sys.stdout.write(
+ BColors.HEADER + BColors.BOLD +
+ "Update to new SRID\n")
+ sys.stdout.write(BColors.ENDC)
+ yes = None
+ while yes != "yes":
+ sys.stdout.write(
+ "Are you sure you want to proceed? (yes/[n])\n")
+ yes = input()
+ if not yes or yes == "n":
+ sys.stdout.write(BColors.FAIL + "Aborting\n")
+ sys.stdout.write(BColors.ENDC)
+ sys.exit()
+
+ def handle(self, *args, **options):
+ quiet = options['quiet']
+ if not quiet:
+ self.interactive_start()
+ profile = IshtarSiteProfile.get_current_profile()
+ if not profile.srs or not profile.srs.srid:
+ sys.stdout.write(BColors.FAIL + "No SRS set\n")
+ sys.stdout.write(BColors.ENDC)
+ GeoVectorData.migrate_srid(profile.srs.srid)
+
diff --git a/ishtar_common/migrations/0219_auto_20220407_1952.py b/ishtar_common/migrations/0219_auto_20220407_1952.py
new file mode 100644
index 000000000..4ec916d63
--- /dev/null
+++ b/ishtar_common/migrations/0219_auto_20220407_1952.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.2.24 on 2022-04-07 19:52
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contenttypes', '0002_remove_content_type_name'),
+ ('ishtar_common', '0218_auto_20220329_1121'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='geovectordata',
+ name='import_key',
+ field=models.TextField(blank=True, help_text='Use this for update imports', null=True, verbose_name='Import key'),
+ ),
+ migrations.AddField(
+ model_name='geovectordata',
+ name='imports',
+ field=models.ManyToManyField(blank=True, related_name='imported_ishtar_common_geovectordata', to='ishtar_common.Import'),
+ ),
+ migrations.AddField(
+ model_name='ishtarsiteprofile',
+ name='srs',
+ field=models.ForeignKey(blank=True, help_text='Set it to the most used spatial reference system. Warning: after change launch the ishtar_migrate_srid script.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='profile_srs', to='ishtar_common.SpatialReferenceSystem', verbose_name='Spatial Reference System in database'),
+ ),
+ migrations.AlterField(
+ model_name='ishtarsiteprofile',
+ name='display_srs',
+ field=models.ForeignKey(blank=True, help_text='Spatial Reference System used for display when no SRS is defined', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='profile_display_srs', to='ishtar_common.SpatialReferenceSystem', verbose_name='Spatial Reference System for display'),
+ ),
+ migrations.AlterUniqueTogether(
+ name='geovectordata',
+ unique_together={('source_content_type', 'source_id', 'import_key')},
+ ),
+ ]
diff --git a/ishtar_common/models.py b/ishtar_common/models.py
index 5b9d48357..2bd983906 100644
--- a/ishtar_common/models.py
+++ b/ishtar_common/models.py
@@ -203,6 +203,7 @@ __all__ = [
"Department",
"State",
"CompleteIdentifierItem",
+ "GeoVectorData",
]
logger = logging.getLogger(__name__)
@@ -1413,9 +1414,22 @@ class IshtarSiteProfile(models.Model, Cached):
blank=True,
null=True,
help_text=_(
- "Spatial Reference System used for display when no SRS is " "defined"
+ "Spatial Reference System used for display when no SRS is defined"
),
on_delete=models.SET_NULL,
+ related_name="profile_display_srs"
+ )
+ srs = models.ForeignKey(
+ SpatialReferenceSystem,
+ verbose_name=_("Spatial Reference System in database"),
+ blank=True,
+ null=True,
+ help_text=_(
+ "Set it to the most used spatial reference system. Warning: after change "
+ "launch the ishtar_migrate_srid script."
+ ),
+ on_delete=models.SET_NULL,
+ related_name="profile_srs"
)
default_language = models.ForeignKey(
Language,
diff --git a/ishtar_common/models_common.py b/ishtar_common/models_common.py
index 104e46100..12f8b0507 100644
--- a/ishtar_common/models_common.py
+++ b/ishtar_common/models_common.py
@@ -23,6 +23,7 @@ from django.conf import settings
from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
+from django.db import migrations as db_migrations
from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
from django.contrib.gis.gdal.error import GDALException
@@ -2078,13 +2079,15 @@ GEOJSON_POINT_TPL = {
}
-class GeoVectorData(models.Model):
+class GeoVectorData(Imported):
name = models.TextField(_("Name"), default=_("Default"))
source_content_type = models.ForeignKey(
ContentType, related_name="content_type_geovectordata", on_delete=models.CASCADE
)
source_id = models.PositiveIntegerField()
source = GenericForeignKey("source_content_type", "source_id")
+ import_key = models.TextField(_("Import key"), blank=True, null=True,
+ help_text=_("Use this for update imports"))
origin = models.ForeignKey(
GeoOriginType,
blank=True,
@@ -2141,6 +2144,7 @@ class GeoVectorData(models.Model):
class Meta:
verbose_name = _("Geographic - Vector data")
verbose_name_plural = _("Geographic - Vector data")
+ unique_together = ("source_content_type", "source_id", "import_key")
def __str__(self):
name = self.name
@@ -2197,11 +2201,20 @@ class GeoVectorData(models.Model):
if dim == 2:
return [None, None]
return [None, None, None]
- point = geom.transform(srid, clone=True)
+
+ point = geom
+ if not srid or srid != geom.srid:
+ point = geom.transform(srid, clone=True)
+ x, y = point.x, point.y
+ # Coordinates are reversed - should be fixed on Django 3.2
+ if srid in (4326, 4979):
+ x, y = y, x
+ else:
+ x, y = point.x, point.y
if dim == 2:
- coordinates = [point.x, point.y]
+ coordinates = [x, y]
else:
- coordinates = [point.x, point.y, point.z]
+ coordinates = [x, y, point.z]
if not rounded:
return coordinates
return [round(coord, rounded) for coord in coordinates]
@@ -2362,6 +2375,24 @@ class GeoVectorData(models.Model):
return self._geojson_serialize("multi_polygon")
return "{}"
+ @classmethod
+ def migrate_srid(cls, new_srid):
+ fields = (
+ "point_2d", "point_3d", "multi_points", "multi_line", "multi_polygon",
+ )
+ with connection.cursor() as cursor:
+ for name in fields:
+ cursor.execute(
+ "UPDATE ishtar_common_geovectordata SET %s=ST_SetSRID(%s, %s);",
+ [name, name, new_srid]
+ )
+ items = cls.objects.all()
+ for item in items:
+ for name in fields:
+ getattr(item, name).transform(new_srid)
+ item._no_geo_check = True
+ item.save()
+
post_save.connect(post_save_geodata, sender=GeoVectorData)
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()
diff --git a/ishtar_common/templates/ishtar/import_table.html b/ishtar_common/templates/ishtar/import_table.html
index 1f7b242b6..e951e28c4 100644
--- a/ishtar_common/templates/ishtar/import_table.html
+++ b/ishtar_common/templates/ishtar/import_table.html
@@ -42,7 +42,7 @@ $("#import-list").find('input').prop('disabled', true);
{{import.name|default:"-"}}
</td>
<td>
- {{import.importer_type}}
+ {{import.importer_type}} ({% trans import.importer_type.type_label %})
</td>
<td>
{% if import.imported_file %}
diff --git a/ishtar_common/views.py b/ishtar_common/views.py
index 50964f462..b6686e0bd 100644
--- a/ishtar_common/views.py
+++ b/ishtar_common/views.py
@@ -66,6 +66,7 @@ from archaeological_operations.models import Operation, ArchaeologicalSite
from archaeological_warehouse.models import Warehouse
from ishtar_common import forms_common as forms
from ishtar_common import wizards
+from ishtar_common.data_importer import ImporterError
from ishtar_common.forms import FinalForm, FinalDeleteForm
from ishtar_common.models import get_current_profile
from ishtar_common.templatetags.link_to_window import simple_link_to_window
@@ -1473,7 +1474,17 @@ class ImportListView(IshtarMixin, LoginRequiredMixin, ListView):
if settings.USE_BACKGROUND_TASK:
imprt.delayed_importation(request, request.session.session_key)
else:
- imprt.importation()
+ try:
+ imprt.importation()
+ except ImporterError as e:
+ imprt.state = "FE"
+ imprt.end_date = datetime.datetime.now()
+ imprt.save()
+ put_session_message(
+ request.session.session_key,
+ f"{imprt} - {e}",
+ "warning",
+ )
elif action == "CH":
if settings.USE_BACKGROUND_TASK:
imprt.delayed_check_modified(request.session.session_key)
diff --git a/requirements.txt b/requirements.txt
index f43f1790a..167b2338e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,7 @@
# 3.2
fiona==1.8.18
+pyproj==3.0.0
psycopg2-binary==2.8.6
# old 2.7.7