Skip to content

Commit

Permalink
Merge pull request #3356 from GeotrekCE/add_poi_shapeparser
Browse files Browse the repository at this point in the history
✨ Add poi shapeparser
  • Loading branch information
marcantoinedupre authored Dec 12, 2022
2 parents 8f4c9a7 + 545a2dc commit c1162b7
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 8 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CHANGELOG
- New ``XMLParser`` to import content from XML
- ApidaeTrekParser: import trek's contact info into description
- New ``Parser`` subclass to import POIs from the APIDAE touristic data system.
- New ``POIParser`` to import POIs from files (with and without dynamic segmentation)

**Bug fixes**

Expand Down
11 changes: 11 additions & 0 deletions docs/install/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,17 @@ Treks are now imported into your own instance.
Import other datas from a file
==============================

You can add parsers in your custom `parsers.py` file (``/opt/geotrek-admin/var/conf/parsers.py``) which will allow you to
import data from files directly in your admin (superusers only).
For example, some parsers are not available by default but you can use them adding some lines in your parsers file :

::

from geotrek.trekking.parsers import TrekParser # only without dynamic segmentation (`TREKKING_TOPOLOGY_ENABLED` = False)
from geotrek.trekking.parsers import POIParser



You can also use some of Geotrek commands to import data from a vector file handled by GDAL (https://gdal.org/drivers/vector/index.htm) (e.g.: ESRI Shapefile, GeoJSON, GeoPackage etc.)

Possible data are e.g.: POI, infrastructures, signages, cities, districts, restricted areas, dives, paths.
Expand Down
15 changes: 14 additions & 1 deletion geotrek/trekking/locale/de/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: 2015-10-21 11:16+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
Expand Down Expand Up @@ -473,6 +473,19 @@ msgstr ""
"Falscher Wert'{val}' für das Feld {src}. Sollte ähnlich sein wie'2h30', "
"'2,5' oder '2.5'"

msgid "You need to add a network of paths before importing POIs"
msgstr ""

msgid "Invalid geometry"
msgstr ""

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""
"Der Geometrietyp des Feldes '{src}' ist ungültig. Es sollte sein Point, "
"nicht {geom_type}"

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
13 changes: 12 additions & 1 deletion geotrek/trekking/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
Expand Down Expand Up @@ -464,6 +464,17 @@ msgid ""
"Bad value '{val}' for field {src}. Should be like '2h30', '2,5' or '2.5'"
msgstr ""

msgid "You need to add a network of paths before importing POIs"
msgstr ""

msgid "Invalid geometry"
msgstr ""

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
13 changes: 12 additions & 1 deletion geotrek/trekking/locale/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: 2015-10-21 11:16+0200\n"
"Last-Translator: [email protected]\n"
"Language-Team: \n"
Expand Down Expand Up @@ -464,6 +464,17 @@ msgid ""
"Bad value '{val}' for field {src}. Should be like '2h30', '2,5' or '2.5'"
msgstr ""

msgid "You need to add a network of paths before importing POIs"
msgstr ""

msgid "Invalid geometry"
msgstr ""

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
15 changes: 14 additions & 1 deletion geotrek/trekking/locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: 2020-09-22 15:57+0000\n"
"Last-Translator: Emmanuelle Helly <[email protected]>\n"
"Language-Team: French <https://weblate.makina-corpus.net/projects/geotrek-"
Expand Down Expand Up @@ -488,6 +488,19 @@ msgstr ""
"Valeur '{val}' erronée pour le champ {src}. Devrait être similaire à '2h30', "
"'2,5' ou '2.5'"

msgid "You need to add a network of paths before importing POIs"
msgstr "Tu as besoin d'ajouter un réseau de tronçons avant d'importer des POIs"

msgid "Invalid geometry"
msgstr "Geométrie invalide"

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""
"Le type de géométrie du champ '{src}' est invalide. Il devrait être un "
"Point, pas {geom_type}"

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
15 changes: 14 additions & 1 deletion geotrek/trekking/locale/it/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: 2015-10-21 11:16+0200\n"
"Last-Translator: \n"
"Language-Team: \n"
Expand Down Expand Up @@ -471,6 +471,19 @@ msgstr ""
"Valore errato '{val}' per il campo {src}. Dovrebbe essere come '2h30', '2,5' "
"o '2.5'"

msgid "You need to add a network of paths before importing POIs"
msgstr ""

msgid "Invalid geometry"
msgstr ""

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""
"Tipo di geometria non valida per il campo '{src}'. Dovrebbe essere Point, "
"non {geom_type}"

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
13 changes: 12 additions & 1 deletion geotrek/trekking/locale/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-03 13:51+0000\n"
"POT-Creation-Date: 2022-12-12 16:05+0000\n"
"PO-Revision-Date: 2015-10-21 11:16+0200\n"
"Last-Translator: [email protected]\n"
"Language-Team: \n"
Expand Down Expand Up @@ -464,6 +464,17 @@ msgid ""
"Bad value '{val}' for field {src}. Should be like '2h30', '2,5' or '2.5'"
msgstr ""

msgid "You need to add a network of paths before importing POIs"
msgstr ""

msgid "Invalid geometry"
msgstr ""

#, python-brace-format
msgid ""
"Invalid geometry type for field '{src}'. Should be Point, not {geom_type}"
msgstr ""

#, python-brace-format
msgid ""
"Not contiguous segment {i} ({distance} m) for geometry for field '{src}'"
Expand Down
51 changes: 50 additions & 1 deletion geotrek/trekking/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

from geotrek.common.models import Label, Theme
from geotrek.common.parsers import (
ShapeParser, AttachmentParserMixin, GeotrekParser, RowImportError, Parser, ApidaeBaseParser
ShapeParser, AttachmentParserMixin, GeotrekParser, GlobalImportError, RowImportError, Parser, ApidaeBaseParser
)
from geotrek.core.models import Path, Topology
from geotrek.trekking.models import OrderedTrekChild, POI, Service, Trek, DifficultyLevel, TrekNetwork, Accessibility


Expand All @@ -38,6 +39,54 @@ def filter_duration(self, src, val):
return None


class POIParser(AttachmentParserMixin, ShapeParser):
label = "Import POI"
label_fr = "Import POI"
label_en = "Import POI"
model = POI
simplify_tolerance = 2
eid = 'name'
constant_fields = {
'published': True,
'deleted': False,
}
natural_keys = {
'type': 'label',
}
field_options = {
'geom': {'required': True},
'type': {'required': True}
}
topology = Topology.objects.none()

def start(self):
super().start()
if settings.TREKKING_TOPOLOGY_ENABLED and not Path.objects.exists():
raise GlobalImportError(_("You need to add a network of paths before importing POIs"))

def filter_geom(self, src, val):
self.topology = Topology.objects.none()
if val is None:
# We use RowImportError because with TREKKING_TOPOLOGY_ENABLED, geom has default value POINT(0 0)
raise RowImportError(_("Invalid geometry"))
if val.geom_type != 'Point':
raise RowImportError(_("Invalid geometry type for field '{src}'. Should be Point, not {geom_type}").format(src=src, geom_type=val.geom_type))
if settings.TREKKING_TOPOLOGY_ENABLED:
# Use existing topology helpers to transform a Point(x, y)
# to a path aggregation (topology)
geometry = val.transform(settings.API_SRID, clone=True)
geometry.coord_dim = 2
serialized = '{"lng": %s, "lat": %s}' % (geometry.x, geometry.y)
self.topology = Topology.deserialize(serialized)
# Move deserialization aggregations to the POI
return val

def parse_obj(self, row, operation):
super().parse_obj(row, operation)
if settings.TREKKING_TOPOLOGY_ENABLED and self.obj.geom and self.topology:
self.obj.mutate(self.topology)


class TrekParser(DurationParserMixin, AttachmentParserMixin, ShapeParser):
label = "Import trek"
label_fr = "Import itinéraires"
Expand Down
13 changes: 13 additions & 0 deletions geotrek/trekking/tests/data/empty_geom.geojson
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {"NAME": "Nom", "TYPE": "équipement"},
"geometry": {
"src": "2154",
"type": "Point"
}
}
]
}
49 changes: 48 additions & 1 deletion geotrek/trekking/tests/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
from django.conf import settings
from django.contrib.gis.geos import Point, LineString, MultiLineString, WKTWriter
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test import TestCase, SimpleTestCase
from django.test.utils import override_settings

from geotrek.common.utils import testdata
from geotrek.common.models import Theme, FileType, Attachment, Label
from geotrek.common.tests.mixins import GeotrekParserTestMixin
from geotrek.core.tests.factories import PathFactory
from geotrek.trekking.tests.factories import RouteFactory
from geotrek.trekking.models import POI, Service, Trek, DifficultyLevel, Route
from geotrek.trekking.models import POI, POIType, Service, Trek, DifficultyLevel, Route
from geotrek.trekking.parsers import (
TrekParser, GeotrekPOIParser, GeotrekServiceParser, GeotrekTrekParser, ApidaeTrekParser, ApidaeTrekThemeParser,
ApidaePOIParser, _prepare_attachment_from_apidae_illustration
Expand Down Expand Up @@ -143,6 +145,51 @@ def test_create(self):
self.assertEqual(WKTWriter(precision=4).write(trek.geom), WKT)


WKT_POI = (
b'POINT (1.5238 43.5294)'
)


class POIParserTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.poi_type_e = POIType.objects.create(label="équipement")
cls.poi_type_s = POIType.objects.create(label="signaletique")
cls.filetype = FileType.objects.create(type="Photographie")

@skipIf(not settings.TREKKING_TOPOLOGY_ENABLED, 'Test with dynamic segmentation only')
def test_import_cmd_raises_error_when_no_path(self):
filename = os.path.join(os.path.dirname(__file__), 'data', 'poi.shp')
with self.assertRaisesRegex(CommandError, 'You need to add a network of paths before importing POIs'):
call_command('import', 'geotrek.trekking.parsers.POIParser', filename, verbosity=0)

def test_import_cmd_raises_wrong_geom_type(self):
PathFactory.create(geom=LineString((0, 0), (0, 10), srid=4326))
filename = os.path.join(os.path.dirname(__file__), 'data', 'trek.shp')
output = StringIO()
call_command('import', 'geotrek.trekking.parsers.POIParser', filename, verbosity=2, stdout=output)
self.assertEqual(POI.objects.count(), 0)
self.assertIn("Invalid geometry type for field 'GEOM'. Should be Point, not LineString,", output.getvalue())

def test_import_cmd_raises_no_geom(self):
PathFactory.create(geom=LineString((0, 0), (0, 10), srid=4326))
filename = os.path.join(os.path.dirname(__file__), 'data', 'empty_geom.geojson')
output = StringIO()
call_command('import', 'geotrek.trekking.parsers.POIParser', filename, verbosity=2, stdout=output)
self.assertEqual(POI.objects.count(), 0)
self.assertIn("Invalid geometry", output.getvalue())

def test_create(self):
PathFactory.create(geom=LineString((0, 0), (0, 10), srid=4326))
filename = os.path.join(os.path.dirname(__file__), 'data', 'poi.shp')
call_command('import', 'geotrek.trekking.parsers.POIParser', filename, verbosity=0)
poi = POI.objects.all().last()
self.assertEqual(poi.name, "pont")
poi.reload()
self.assertEqual(WKTWriter(precision=4).write(poi.geom), WKT_POI)
self.assertEqual(poi.geom, poi.geom_3d)


class TestGeotrekTrekParser(GeotrekTrekParser):
url = "https://test.fr"
provider = 'geotrek1'
Expand Down

0 comments on commit c1162b7

Please sign in to comment.