Projet

Général

Profil

0001-opengis-add-reverse-geocoding-based-on-WFS-21558.patch

Serghei Mihai (congés, retour 15/05), 28 février 2018 12:05

Télécharger (8,97 ko)

Voir les différences:

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
passerelle/apps/opengis/migrations/0005_auto_20180227_1531.py
1
# -*- coding: utf-8 -*-
2
from __future__ import unicode_literals
3

  
4
from django.db import migrations, models
5
import jsonfield.fields
6

  
7

  
8
class Migration(migrations.Migration):
9

  
10
    dependencies = [
11
        ('opengis', '0004_auto_20180219_1613'),
12
    ]
13

  
14
    operations = [
15
        migrations.AddField(
16
            model_name='opengis',
17
            name='search_radius',
18
            field=models.IntegerField(default=5, verbose_name='Radius for point search'),
19
        ),
20
    ]
passerelle/apps/opengis/models.py
26 26
from django.utils.text import slugify
27 27
from django.utils.translation import ugettext_lazy as _
28 28

  
29
from jsonfield import JSONField
30

  
29 31
from passerelle.base.models import BaseResource
30 32
from passerelle.utils.api import endpoint
31 33
from passerelle.utils.jsonresponse import APIError
......
58 60
    query_layer = models.CharField(_('Query Layer'), max_length=256)
59 61
    projection = models.CharField(_('GIS projection'), choices=PROJECTIONS,
60 62
                                  default='epsg:3857', max_length=16)
63
    search_radius = models.IntegerField(_('Radius for point search'), default=5)
64
    attributes_mapping = (('road', ('road', 'road_name', 'street', 'street_name', 'voie', 'nom_voie', 'rue')),
65
                          ('city', ('city', 'city_name', 'town', 'town_name', 'commune', 'nom_commune', 'ville', 'nom_ville')),
66
                          ('house_number', ('house_number', 'number', 'numero', 'numero_voie', 'numero_rue')),
67
                          ('postcode', ('postcode', 'postalCode', 'zipcode', 'codepostal', 'cp', 'code_postal', 'code_post'))
68
                          )
61 69

  
62 70
    class Meta:
63 71
        verbose_name = _('OpenGIS')
......
231 239
        return HttpResponse(
232 240
                self.requests.get(self.wms_service_url, params=params, cache_duration=300).content,
233 241
                content_type='image/png')
242

  
243
    @endpoint(perm='can_access', description=_('Get feature info'))
244
    def reverse(self, request, lat, lon):
245
        result = None
246

  
247
        if self.projection != 'epsg:4326':
248
            wgs84 = pyproj.Proj(init='epsg:4326')
249
            target_projection = pyproj.Proj(init=self.projection)
250
            lon, lat = pyproj.transform(wgs84, target_projection, lon, lat)
251

  
252
        cql_filter = 'DWITHIN(the_geom,Point(%s %s),%s,meters)' % (lon, lat, self.search_radius)
253
        params = {
254
            'VERSION': self.get_wfs_service_version(),
255
            'SERVICE': 'WFS',
256
            'REQUEST': 'GetFeature',
257
            'TYPENAMES': self.query_layer,
258
            'OUTPUTFORMAT': 'json',
259
            'CQL_FILTER': cql_filter
260
        }
261
        response = self.requests.get(self.wfs_service_url, params=params)
262
        for feature in response.json().get('features'):
263
            if not feature['geometry']['type'] == 'Point':
264
                continue  # skip unknown
265
            result = {}
266
            point_lon = str(feature['geometry']['coordinates'][0])
267
            point_lat = str(feature['geometry']['coordinates'][1])
268
            if self.projection != 'epsg:4326':
269
                point_lon, point_lat = pyproj.transform(target_projection, wgs84,
270
                                                        point_lon, point_lat)
271
            result['lon'] = point_lon
272
            result['lat'] = point_lat
273
            result['address'] = {'country': 'France'}
274
            for attribute, properties in self.attributes_mapping:
275
                for field in properties:
276
                    if feature['properties'].get(field):
277
                        result['address'][attribute] = unicode(feature['properties'][field])
278
                        break
279
            # return first found point
280
            break
281
        return result
tests/test_opengis.py
101 101

  
102 102
FAKE_ERROR = u'<ows:ExceptionReport xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0.0" xsi:schemaLocation="http://www.opengis.net/ows/1.1 https://sigmetropole.lametro.fr/geoserver/schemas/ows/1.1.0/owsAll.xsd">\n  <ows:Exception exceptionCode="NoApplicableCode">\n    <ows:ExceptionText>Could not parse CQL filter list.\nEncountered &amp;quot;BIS&amp;quot; at line 1, column 129.\nWas expecting one of:\n    &amp;lt;EOF&amp;gt; \n    &amp;quot;and&amp;quot; ...\n    &amp;quot;or&amp;quot; ...\n    &amp;quot;;&amp;quot; ...\n    &amp;quot;/&amp;quot; ...\n    &amp;quot;*&amp;quot; ...\n    &amp;quot;+&amp;quot; ...\n    &amp;quot;-&amp;quot; ...\n     Parsing : strEqualsIgnoreCase(nom_commune, &amp;apos;Grenoble&amp;apos;) = true AND strEqualsIgnoreCase(nom_voie, &amp;apos;rue albert recoura&amp;apos;) = true AND numero=8 BIS.</ows:ExceptionText>\n  </ows:Exception>\n</ows:ExceptionReport>\n'
103 103

  
104
FAKE_GEOLOCATED_FEATURE = '''
105
{
106
    "crs": {
107
        "properties": {
108
            "name": "urn:ogc:def:crs:EPSG::3945"
109
        },
110
        "type": "name"
111
    },
112
    "features": [
113
        {
114
            "geometry": {
115
                "coordinates": [
116
                    1913311.15,
117
                    4224001.54
118
                ],
119
                "type": "Point"
120
            },
121
            "geometry_name": "the_geom",
122
            "id": "ref_ban_metro.fid--204aa923_161d7e3f06a_28bb",
123
            "properties": {
124
                "code_insee": 38185,
125
                "code_post": 38000,
126
                "libelle_acheminement": "GRENOBLE",
127
                "nom_afnor": "RUE DE NEW YORK",
128
                "nom_commune": "Grenoble",
129
                "nom_voie": "rue de new-york",
130
                "numero": 10,
131
                "secteur_voirie": "Nord-Ouest"
132
            },
133
            "type": "Feature"
134
        },
135
        {
136
            "geometry": {
137
                "coordinates": [
138
                    1913308.65,
139
                    4223996.14
140
                ],
141
                "type": "Point"
142
            },
143
            "geometry_name": "the_geom",
144
            "id": "ref_ban_metro.fid--204aa923_161d7e3f06a_28bc",
145
            "properties": {
146
                "code_insee": 38185,
147
                "code_post": 38000,
148
                "libelle_acheminement": "GRENOBLE",
149
                "nom_afnor": "RUE DE NEW YORK",
150
                "nom_commune": "Grenoble",
151
                "nom_voie": "rue de new-york",
152
                "numero": 23
153
            },
154
            "type": "Feature"
155
        }
156
    ],
157
    "totalFeatures": 2,
158
    "type": "FeatureCollection"
159
}
160

  
161
'''
162

  
104 163

  
105 164
@pytest.fixture
106 165
def connector(db):
......
244 303
    assert result['err'] == 1
245 304
    assert result['err_desc'] == 'OpenGIS Error: unparsable error'
246 305
    assert '<ows:' in result['data']['content']
306

  
307
@mock.patch('passerelle.utils.Request.get')
308
def test_reverse_geocoding(mocked_get, app, connector):
309
    endpoint = utils.generic_endpoint_url('opengis', 'reverse', slug=connector.slug)
310
    assert endpoint == '/opengis/test/reverse'
311
    mocked_get.return_value = utils.FakedResponse(content=FAKE_GEOLOCATED_FEATURE, status_code=200)
312
    resp = app.get(endpoint, params={'lat':'45.183784', 'lon': '5.714885'})
313
    assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'DWITHIN(the_geom,Point(636178.088142 5650501.0682),5,meters)'
314

  
315
    connector.projection = 'epsg:4326'
316
    connector.search_radius = 10
317
    connector.save()
318
    resp = app.get(endpoint, params={'lat':'45.183784', 'lon': '5.714885'})
319
    assert mocked_get.call_args[1]['params']['CQL_FILTER'] == 'DWITHIN(the_geom,Point(5.714885 45.183784),10,meters)'
320
    assert resp.json['address']['country'] == 'France'
321
    assert resp.json['address']['house_number'] == '10'
322
    assert resp.json['address']['road'] == 'rue de new-york'
323
    assert resp.json['address']['postcode'] == '38000'
324
    assert resp.json['address']['city'] == 'Grenoble'
247
-