0001-opengis-add-reverse-geocoding-based-on-WFS-21558.patch
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 &quot;BIS&quot; at line 1, column 129.\nWas expecting one of:\n &lt;EOF&gt; \n &quot;and&quot; ...\n &quot;or&quot; ...\n &quot;;&quot; ...\n &quot;/&quot; ...\n &quot;*&quot; ...\n &quot;+&quot; ...\n &quot;-&quot; ...\n Parsing : strEqualsIgnoreCase(nom_commune, &apos;Grenoble&apos;) = true AND strEqualsIgnoreCase(nom_voie, &apos;rue albert recoura&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 |
- |