From ebed786c947625f9781d7014b1d843f44d631821 Mon Sep 17 00:00:00 2001 From: Aman Yadav Date: Thu, 19 Dec 2019 09:44:38 +0200 Subject: [PATCH] Geojson importers Implementation of geojson importers for permit area and payment zones. Tests related to geojson under parkings/tests --- parkings/importers/__init__.py | 7 ++ parkings/importers/geojson_importer.py | 20 +++++ parkings/importers/geojson_payment_zones.py | 48 ++++++++++ parkings/importers/geojson_permit_areas.py | 47 ++++++++++ .../commands/import_geojson_payment_zones.py | 14 +++ .../commands/import_geojson_permit_areas.py | 14 +++ ...eojson_payment_zones_importer_data.geojson | 90 +++++++++++++++++++ ...geojson_permit_areas_importer_data.geojson | 47 ++++++++++ ...t_geojson_importing_management_commands.py | 27 ++++++ .../test_geojson_payment_zone_importer.py | 24 +++++ .../test_geojson_permit_area_importer.py | 21 +++++ 11 files changed, 359 insertions(+) create mode 100644 parkings/importers/__init__.py create mode 100644 parkings/importers/geojson_importer.py create mode 100644 parkings/importers/geojson_payment_zones.py create mode 100644 parkings/importers/geojson_permit_areas.py create mode 100644 parkings/management/commands/import_geojson_payment_zones.py create mode 100644 parkings/management/commands/import_geojson_permit_areas.py create mode 100644 parkings/tests/geojson_payment_zones_importer_data.geojson create mode 100644 parkings/tests/geojson_permit_areas_importer_data.geojson create mode 100644 parkings/tests/test_geojson_importing_management_commands.py create mode 100644 parkings/tests/test_geojson_payment_zone_importer.py create mode 100644 parkings/tests/test_geojson_permit_area_importer.py diff --git a/parkings/importers/__init__.py b/parkings/importers/__init__.py new file mode 100644 index 00000000..daca14e0 --- /dev/null +++ b/parkings/importers/__init__.py @@ -0,0 +1,7 @@ +from .geojson_payment_zones import PaymentZoneImporter +from .geojson_permit_areas import PermitAreaImporter + +__all__ = [ + 'PermitAreaImporter', + 'PaymentZoneImporter', +] diff --git a/parkings/importers/geojson_importer.py b/parkings/importers/geojson_importer.py new file mode 100644 index 00000000..a9d58acc --- /dev/null +++ b/parkings/importers/geojson_importer.py @@ -0,0 +1,20 @@ +import abc +import json + +from django.contrib.gis.geos import GEOSGeometry + + +class GeoJsonImporter(metaclass=abc.ABCMeta): + + def read_and_parse(self, geojson_file_path): + with open(geojson_file_path, "rt") as file: + root = json.load(file) + for member in root['features']: + yield self._parse_member(member) + + @abc.abstractmethod + def _parse_member(self, member): + pass + + def get_polygons(self, geom): + return GEOSGeometry(json.dumps(geom)) diff --git a/parkings/importers/geojson_payment_zones.py b/parkings/importers/geojson_payment_zones.py new file mode 100644 index 00000000..6c09a00f --- /dev/null +++ b/parkings/importers/geojson_payment_zones.py @@ -0,0 +1,48 @@ +import logging +import os + +from django.db import transaction + +from parkings.models import PaymentZone + +from .geojson_importer import GeoJsonImporter + +logger = logging.getLogger(__name__) + +mydir = os.path.dirname(__file__) + + +class PaymentZoneImporter(GeoJsonImporter): + """ + Imports paymentzones data + """ + + def import_payment_zones(self, geojson_file_path): + payment_zone_dicts = self.read_and_parse(geojson_file_path) + count = self._save_payment_zones(payment_zone_dicts) + logger.info('Created or updated {} payment zones'.format(count)) + + @transaction.atomic + def _save_payment_zones(self, payment_zone_dicts): + logger.info('Saving payment zones.') + count = 0 + payment_zone_ids = [] + for payment_dict in payment_zone_dicts: + payment_zone, _ = PaymentZone.objects.update_or_create( + number=payment_dict['number'], + defaults=payment_dict) + payment_zone_ids.append(payment_zone.pk) + count += 1 + PaymentZone.objects.exclude(pk__in=payment_zone_ids).delete() + return count + + def _parse_member(self, member): + name = member['properties']['name'] + number = member['properties']['number'] + geom = self.get_polygons(member['geometry']) + + return { + 'name': name, + 'number': number, + 'geom': geom, + } diff --git a/parkings/importers/geojson_permit_areas.py b/parkings/importers/geojson_permit_areas.py new file mode 100644 index 00000000..e1e96b9a --- /dev/null +++ b/parkings/importers/geojson_permit_areas.py @@ -0,0 +1,47 @@ +import logging +import os + +from django.db import transaction + +from parkings.models import PermitArea + +from .geojson_importer import GeoJsonImporter + +logger = logging.getLogger(__name__) +mydir = os.path.dirname(__file__) + + +class PermitAreaImporter(GeoJsonImporter): + """ + Imports permit area data + """ + + def import_permit_areas(self, geojson_file_path): + permit_area_dicts = self.read_and_parse(geojson_file_path) + count = self._save_permit_areas(permit_area_dicts) + logger.info('Created or updated {} permit areas'.format(count)) + + @transaction.atomic + def _save_permit_areas(self, permit_areas_dict): + logger.info('Saving permit areas.') + count = 0 + permit_area_ids = [] + for area_dict in permit_areas_dict: + permit_area, _ = PermitArea.objects.update_or_create( + identifier=area_dict['identifier'], + defaults=area_dict) + permit_area_ids.append(permit_area.pk) + count += 1 + PermitArea.objects.exclude(pk__in=permit_area_ids).delete() + return count + + def _parse_member(self, member): + identifier = member['properties']['identifier'] + name = member['properties']['name'] + geom = self.get_polygons(member['geometry']) + + return { + 'name': name, + 'identifier': identifier, + 'geom': geom, + } diff --git a/parkings/management/commands/import_geojson_payment_zones.py b/parkings/management/commands/import_geojson_payment_zones.py new file mode 100644 index 00000000..a55a2498 --- /dev/null +++ b/parkings/management/commands/import_geojson_payment_zones.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand + +from parkings.importers import PaymentZoneImporter + + +class Command(BaseCommand): + help = 'Uses the PaymentZoneImporter to import payment zones.' + + def add_arguments(self, parser): + parser.add_argument('geojson_file_path') + + def handle(self, *args, **options): + file_path = options.get('geojson_file_path', None) + PaymentZoneImporter().import_payment_zones(file_path) diff --git a/parkings/management/commands/import_geojson_permit_areas.py b/parkings/management/commands/import_geojson_permit_areas.py new file mode 100644 index 00000000..00c09f11 --- /dev/null +++ b/parkings/management/commands/import_geojson_permit_areas.py @@ -0,0 +1,14 @@ +from django.core.management.base import BaseCommand + +from parkings.importers import PermitAreaImporter + + +class Command(BaseCommand): + help = 'Uses the PermitAreaImporter to create permit areas' + + def add_arguments(self, parser): + parser.add_argument('geojson_file_path') + + def handle(self, *args, **options): + file_path = options.get('geojson_file_path', None) + PermitAreaImporter().import_permit_areas(file_path) diff --git a/parkings/tests/geojson_payment_zones_importer_data.geojson b/parkings/tests/geojson_payment_zones_importer_data.geojson new file mode 100644 index 00000000..33fa307d --- /dev/null +++ b/parkings/tests/geojson_payment_zones_importer_data.geojson @@ -0,0 +1,90 @@ +{ + "type": "FeatureCollection", + "name": "maksut_wgs84", + "crs": { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:OGC:1.3:CRS84" + } + }, + "features": [{ + "type": "Feature", + "properties": { + "number": 0, + "name": "Zone 1" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [ + -47.900390625, + -14.944784875088372 + ], + [ + -51.591796875, + -19.91138351415555 + ], + [ + -41.11083984375, + -21.309846141087192 + ], + [ + -43.39599609375, + -15.390135715305204 + ], + [ + -47.900390625, + -14.944784875088372 + ] + ], + [ + [ + -46.6259765625, + -17.14079039331664 + ], + [ + -47.548828125, + -16.804541076383455 + ], + [ + -46.23046874999999, + -16.699340234594537 + ], + [ + -45.3515625, + -19.31114335506464 + ], + [ + -46.6259765625, + -17.14079039331664 + ] + ], + [ + [ + -44.40673828125, + -18.375379094031825 + ], + [ + -44.4287109375, + -20.097206227083888 + ], + [ + -42.9345703125, + -18.979025953255267 + ], + [ + -43.52783203125, + -17.602139123350838 + ], + [ + -44.40673828125, + -18.375379094031825 + ] + ] + ] + ] + } + }] +} diff --git a/parkings/tests/geojson_permit_areas_importer_data.geojson b/parkings/tests/geojson_permit_areas_importer_data.geojson new file mode 100644 index 00000000..a3238ce2 --- /dev/null +++ b/parkings/tests/geojson_permit_areas_importer_data.geojson @@ -0,0 +1,47 @@ +{ + "type": "FeatureCollection", + "name": "asukaspysakointi_wgs84", + "crs": { + "type": "name", + "properties": { + "name": "urn:ogc:def:crs:OGC:1.3:CRS84" + } + }, + "features": [{ + "type": "Feature", + "properties": { + "identifier": 1, + "name": "Asukaspysäköintialue A" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [102.0, 2.0], + [103.0, 2.0], + [103.0, 3.0], + [102.0, 3.0], + [102.0, 2.0] + ] + ], + [ + [ + [100.0, 0.0], + [101.0, 0.0], + [101.0, 1.0], + [100.0, 1.0], + [100.0, 0.0] + ], + [ + [100.2, 0.2], + [100.8, 0.2], + [100.8, 0.8], + [100.2, 0.8], + [100.2, 0.2] + ] + ] + ] + } + }] +} diff --git a/parkings/tests/test_geojson_importing_management_commands.py b/parkings/tests/test_geojson_importing_management_commands.py new file mode 100644 index 00000000..c6ddc0e7 --- /dev/null +++ b/parkings/tests/test_geojson_importing_management_commands.py @@ -0,0 +1,27 @@ +import os + +import pytest +from django.core.management import call_command + +from parkings.models import PaymentZone, PermitArea + +from ..management.commands import ( + import_geojson_payment_zones, import_geojson_permit_areas) + +mydir = os.path.dirname(__file__) +permit_areas = os.path.join(mydir, 'geojson_permit_areas_importer_data.geojson') +payment_zones = os.path.join(mydir, 'geojson_payment_zones_importer_data.geojson') + + +@pytest.mark.django_db +def test_import_payment_zones(): + call_command(import_geojson_payment_zones.Command(), payment_zones) + + assert PaymentZone.objects.count() == 1 + + +@pytest.mark.django_db +def test_permit_area_importer(): + call_command(import_geojson_permit_areas.Command(), permit_areas) + + assert PermitArea.objects.count() == 1 diff --git a/parkings/tests/test_geojson_payment_zone_importer.py b/parkings/tests/test_geojson_payment_zone_importer.py new file mode 100644 index 00000000..3827d8db --- /dev/null +++ b/parkings/tests/test_geojson_payment_zone_importer.py @@ -0,0 +1,24 @@ +import os + +from parkings.importers import PaymentZoneImporter + +mydir = os.path.dirname(__file__) + + +def test_payment_zone_importer(): + filename = os.path.join(mydir, 'geojson_payment_zones_importer_data.geojson') + importer = PaymentZoneImporter() + data = importer.read_and_parse(filename) + payment_zone = next(iter(data)) + + assert payment_zone['name'] == "Zone 1" + assert payment_zone['number'] == 0 + assert payment_zone['geom'].wkt == ( + 'MULTIPOLYGON ((' + '(-47.900390625 -14.94478487508837, -51.591796875 -19.91138351415555, -41.11083984375' + ' -21.30984614108719, -43.39599609375 -15.3901357153052, -47.900390625 -14.94478487508837), ' + '(-46.6259765625 -17.14079039331664, -47.548828125 -16.80454107638345, -46.23046874999999' + ' -16.69934023459454, -45.3515625 -19.31114335506464, -46.6259765625 -17.14079039331664), ' + '(-44.40673828125 -18.37537909403182, -44.4287109375 -20.09720622708389, -42.9345703125' + ' -18.97902595325527, -43.52783203125 -17.60213912335084, -44.40673828125 -18.37537909403182)))' + ) diff --git a/parkings/tests/test_geojson_permit_area_importer.py b/parkings/tests/test_geojson_permit_area_importer.py new file mode 100644 index 00000000..43a6c899 --- /dev/null +++ b/parkings/tests/test_geojson_permit_area_importer.py @@ -0,0 +1,21 @@ +import os + +from parkings.importers import PermitAreaImporter + +mydir = os.path.dirname(__file__) + + +def test_permit_area_importer(): + filename = os.path.join(mydir, 'geojson_permit_areas_importer_data.geojson') + importer = PermitAreaImporter() + data = importer.read_and_parse(filename) + permit_area = next(iter(data)) + + assert permit_area['name'] == 'Asukaspysäköintialue A' + assert permit_area['identifier'] == 1 + assert permit_area['geom'].wkt == ( + 'MULTIPOLYGON ((' + '(102 2, 103 2, 103 3, 102 3, 102 2)),' + ' ((100 0, 101 0, 101 1, 100 1, 100 0),' + ' (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2)))' + )