From 9f4b34cae613a1b04e039b9130034eb9d4d4631c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 30 Apr 2016 12:02:46 +0200 Subject: [PATCH 2/4] workflows: add action to geolocate a formdata (#10581) --- wcs/wf/geolocate.py | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++ wcs/workflows.py | 1 + 2 files changed, 173 insertions(+) create mode 100644 wcs/wf/geolocate.py diff --git a/wcs/wf/geolocate.py b/wcs/wf/geolocate.py new file mode 100644 index 0000000..7dd92da --- /dev/null +++ b/wcs/wf/geolocate.py @@ -0,0 +1,172 @@ +# w.c.s. - web application for online forms +# Copyright (C) 2005-2016 Entr'ouvert +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . + +import collections +import json +import urllib2 + +try: + from PIL import Image + from PIL.ExifTags import TAGS, GPSTAGS +except ImportError: + Image = None + +from quixote import get_publisher + +from qommon import get_logger +from qommon.form import RadiobuttonsWidget, StringWidget, CheckboxWidget +from qommon.misc import http_get_page +from wcs.workflows import (WorkflowStatusItem, register_item_class, + template_on_formdata) + +class GeolocateWorkflowStatusItem(WorkflowStatusItem): + description = N_('Geolocate') + key = 'geolocate' + + method = 'string' + address_string = None + map_variable = None + photo_variable = None + overwrite = False + + def get_parameters(self): + return ('method', 'address_string', 'map_variable', 'photo_variable', 'overwrite') + + def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): + methods = collections.OrderedDict( + [('address_string', _('Address String')), + ('map_variable', _('Map Variable')), + ('photo_variable', _('Photo Variable'))]) + + if Image is None: + del methods['photo_variable'] + + if 'method' in parameters: + form.add(RadiobuttonsWidget, '%smethod' % prefix, + title=_('Method'), + options=methods.items(), + value=self.method, + attrs={'data-dynamic-display-parent': 'true'}) + if 'address_string' in parameters: + form.add(StringWidget, '%saddress_string' % prefix, + title=_('Address String'), value=self.address_string, + attrs={ + 'data-dynamic-display-child-of': '%smethod' % prefix, + 'data-dynamic-display-value': methods.get('address_string'), + }) + if 'map_variable' in parameters: + form.add(StringWidget, '%smap_variable' % prefix, + title=_('Map Variable'), value=self.map_variable, + attrs={ + 'data-dynamic-display-child-of': '%smethod' % prefix, + 'data-dynamic-display-value': methods.get('map_variable'), + }) + if 'photo_variable' in parameters: + form.add(StringWidget, '%sphoto_variable' % prefix, + title=_('Photo Variable'), value=self.photo_variable, + attrs={ + 'data-dynamic-display-child-of': '%smethod' % prefix, + 'data-dynamic-display-value': methods.get('photo_variable'), + }) + if 'overwrite' in parameters: + form.add(CheckboxWidget, '%soverwrite' % prefix, + title=_('Overwrite existing geolocation'), + value=self.overwrite) + + def perform(self, formdata): + if not self.method: + return + geolocation_point = formdata.formdef.geolocations.keys()[0] + if not formdata.geolocations: + formdata.geolocations = {} + if formdata.geolocations.get(geolocation_point) and not self.overwrite: + return + location = getattr(self, 'geolocate_' + self.method)(formdata) + if location: + formdata.geolocations[geolocation_point] = location + formdata.store() + + def geolocate_address_string(self, formdata): + nominatim_url = get_publisher().get_site_option('nominatim_url') + if not nominatim_url: + nominatim_url = 'http://nominatim.openstreetmap.org' + + try: + address = template_on_formdata(formdata, self.address_string) + except ezt.EZTException: + get_logger().error('error in template for address string [%s]', self.address_string) + return + + url = '%s/search?q=%s&format=json' % (nominatim_url, urllib2.quote(address)) + response, status, data, auth_header = http_get_page(url) + if status != 200: + get_logger().error('error calling geocoding service [%s]', status) + return + data = json.loads(data) + if len(data) == 0: + get_logger().error('error finding location') + return + coords = data[0] + return {'lon': float(coords['lon']), 'lat': float(coords['lat'])} + + def geolocate_map_variable(self, formdata): + try: + value = self.compute(self.map_variable) + except: + get_logger().error('error geolocating from map variable [%s]', self.map_variable) + return + if not value: + return + + lat, lon = map(float, value.split(';')) + return {'lon': lon, 'lat': lat} + + def geolocate_photo_variable(self, formdata): + if Image is None: + get_logger().error('error geolocating from file (missing PIL)') + return + + try: + value = self.compute(self.photo_variable) + except: + get_logger().error('error geolocating from photo variable [%s]', self.photo_variable) + return + + try: + image = Image.open(value.get_file_pointer()) + except IOError: + get_logger().error('error geolocating from file') + return + + exif_data = image._getexif() + if exif_data: + gps_info = exif_data.get(0x8825) + if gps_info: + # lat_ref will be N/S, lon_ref wil l be E/W + # lat and lon will be degrees/minutes/seconds (value, denominator), + # like ((33, 1), (51, 1), (2191, 100)) + lat_ref, lat, lon_ref, lon = gps_info[1], gps_info[2], gps_info[3], gps_info[4] + lat = (1.0*lat[0][0]/lat[0][1] + 1.0*lat[1][0]/lat[1][1]/60 + 1.0*lat[2][0]/lat[2][1]/3600) + lon = (1.0*lon[0][0]/lon[0][1] + 1.0*lon[1][0]/lon[1][1]/60 + 1.0*lon[2][0]/lon[2][1]/3600) + if lat_ref == 'S': + lat = -lat + if lon_ref == 'W': + lon = -lon + return {'lon': lon, 'lat': lat} + return + + +register_item_class(GeolocateWorkflowStatusItem) diff --git a/wcs/workflows.py b/wcs/workflows.py index 60137ff..c5a3ebe 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -2204,6 +2204,7 @@ def load_extra(): import wf.remove import wf.roles import wf.dispatch + import wf.geolocate import wf.wscall import wf.form import wf.register_comment -- 2.8.1