From c5e81afa6b5b812a25dcf06958baf69a8407d52e Mon Sep 17 00:00:00 2001 From: Serghei Mihai Date: Wed, 31 Jan 2018 17:35:02 +0100 Subject: [PATCH] opengis: add reverse geocoding based on WFS (#21558) --- .../opengis/migrations/0005_auto_20180227_1531.py | 20 ++++++ passerelle/apps/opengis/models.py | 48 +++++++++++++ tests/test_opengis.py | 78 ++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 passerelle/apps/opengis/migrations/0005_auto_20180227_1531.py diff --git a/passerelle/apps/opengis/migrations/0005_auto_20180227_1531.py b/passerelle/apps/opengis/migrations/0005_auto_20180227_1531.py new file mode 100644 index 00000000..e866d62e --- /dev/null +++ b/passerelle/apps/opengis/migrations/0005_auto_20180227_1531.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('opengis', '0004_auto_20180219_1613'), + ] + + operations = [ + migrations.AddField( + model_name='opengis', + name='search_radius', + field=models.IntegerField(default=5, verbose_name='Radius for point search'), + ), + ] diff --git a/passerelle/apps/opengis/models.py b/passerelle/apps/opengis/models.py index 3f3265bf..d631d2ca 100644 --- a/passerelle/apps/opengis/models.py +++ b/passerelle/apps/opengis/models.py @@ -26,6 +26,8 @@ from django.http import HttpResponse from django.utils.text import slugify from django.utils.translation import ugettext_lazy as _ +from jsonfield import JSONField + from passerelle.base.models import BaseResource from passerelle.utils.api import endpoint from passerelle.utils.jsonresponse import APIError @@ -58,6 +60,12 @@ class OpenGIS(BaseResource): query_layer = models.CharField(_('Query Layer'), max_length=256) projection = models.CharField(_('GIS projection'), choices=PROJECTIONS, default='epsg:3857', max_length=16) + search_radius = models.IntegerField(_('Radius for point search'), default=5) + attributes_mapping = (('road', ('road', 'road_name', 'street', 'street_name', 'voie', 'nom_voie', 'rue')), + ('city', ('city', 'city_name', 'town', 'town_name', 'commune', 'nom_commune', 'ville', 'nom_ville')), + ('house_number', ('house_number', 'number', 'numero', 'numero_voie', 'numero_rue')), + ('postcode', ('postcode', 'postalCode', 'zipcode', 'codepostal', 'cp', 'code_postal', 'code_post')) + ) class Meta: verbose_name = _('OpenGIS') @@ -231,3 +239,43 @@ class OpenGIS(BaseResource): return HttpResponse( self.requests.get(self.wms_service_url, params=params, cache_duration=300).content, content_type='image/png') + + @endpoint(perm='can_access', description=_('Get feature info')) + def reverse(self, request, lat, lon): + result = None + + if self.projection != 'epsg:4326': + wgs84 = pyproj.Proj(init='epsg:4326') + target_projection = pyproj.Proj(init=self.projection) + lon, lat = pyproj.transform(wgs84, target_projection, lon, lat) + + cql_filter = 'DWITHIN(the_geom,Point(%s %s),%s,meters)' % (lon, lat, self.search_radius) + params = { + 'VERSION': self.get_wfs_service_version(), + 'SERVICE': 'WFS', + 'REQUEST': 'GetFeature', + 'TYPENAMES': self.query_layer, + 'OUTPUTFORMAT': 'json', + 'CQL_FILTER': cql_filter + } + response = self.requests.get(self.wfs_service_url, params=params) + for feature in response.json().get('features'): + if not feature['geometry']['type'] == 'Point': + continue # skip unknown + result = {} + point_lon = str(feature['geometry']['coordinates'][0]) + point_lat = str(feature['geometry']['coordinates'][1]) + if self.projection != 'epsg:4326': + point_lon, point_lat = pyproj.transform(target_projection, wgs84, + point_lon, point_lat) + result['lon'] = point_lon + result['lat'] = point_lat + result['address'] = {'country': 'France'} + for attribute, properties in self.attributes_mapping: + for field in properties: + if feature['properties'].get(field): + result['address'][attribute] = unicode(feature['properties'][field]) + break + # return first found point + break + return result diff --git a/tests/test_opengis.py b/tests/test_opengis.py index 8dd1b807..c999a406 100644 --- a/tests/test_opengis.py +++ b/tests/test_opengis.py @@ -101,6 +101,65 @@ FAKE_FEATURES_JSON = ''' FAKE_ERROR = u'\n \n Could not parse CQL filter list.\nEncountered "BIS" at line 1, column 129.\nWas expecting one of:\n <EOF> \n "and" ...\n "or" ...\n ";" ...\n "/" ...\n "*" ...\n "+" ...\n "-" ...\n Parsing : strEqualsIgnoreCase(nom_commune, 'Grenoble') = true AND strEqualsIgnoreCase(nom_voie, 'rue albert recoura') = true AND numero=8 BIS.\n \n\n' +FAKE_GEOLOCATED_FEATURE = ''' +{ + "crs": { + "properties": { + "name": "urn:ogc:def:crs:EPSG::3945" + }, + "type": "name" + }, + "features": [ + { + "geometry": { + "coordinates": [ + 1913311.15, + 4224001.54 + ], + "type": "Point" + }, + "geometry_name": "the_geom", + "id": "ref_ban_metro.fid--204aa923_161d7e3f06a_28bb", + "properties": { + "code_insee": 38185, + "code_post": 38000, + "libelle_acheminement": "GRENOBLE", + "nom_afnor": "RUE DE NEW YORK", + "nom_commune": "Grenoble", + "nom_voie": "rue de new-york", + "numero": 10, + "secteur_voirie": "Nord-Ouest" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + 1913308.65, + 4223996.14 + ], + "type": "Point" + }, + "geometry_name": "the_geom", + "id": "ref_ban_metro.fid--204aa923_161d7e3f06a_28bc", + "properties": { + "code_insee": 38185, + "code_post": 38000, + "libelle_acheminement": "GRENOBLE", + "nom_afnor": "RUE DE NEW YORK", + "nom_commune": "Grenoble", + "nom_voie": "rue de new-york", + "numero": 23 + }, + "type": "Feature" + } + ], + "totalFeatures": 2, + "type": "FeatureCollection" +} + +''' + @pytest.fixture def connector(db): @@ -244,3 +303,22 @@ def test_get_feature_error(mocked_get, app, connector): assert result['err'] == 1 assert result['err_desc'] == 'OpenGIS Error: unparsable error' assert '