Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / geopy / geocoders / what3words.py @ 564

History | View | Annotate | Download (8.35 KB)

1
"""
2
:class:`.What3Words` geocoder.
3
"""
4

    
5
import re
6
from geopy.compat import urlencode
7
from geopy.geocoders.base import (
8
    Geocoder,
9
    DEFAULT_FORMAT_STRING,
10
    DEFAULT_TIMEOUT,
11
    DEFAULT_SCHEME
12
)
13
from geopy.location import Location
14
from geopy.util import logger, join_filter
15
from geopy import exc
16

    
17

    
18
__all__ = ("What3Words", )
19

    
20

    
21
class What3Words(Geocoder):
22
    """
23
    What3Words geocoder, documentation at:
24
        http://what3words.com/api/reference
25
    """
26

    
27
    word_re = re.compile(r"^\*{1,1}[^\W\d\_]+$", re.U)
28
    multiple_word_re = re.compile(
29
        r"[^\W\d\_]+\.{1,1}[^\W\d\_]+\.{1,1}[^\W\d\_]+$", re.U
30
        )
31

    
32
    def __init__(
33
            self,
34
            api_key,
35
            format_string=DEFAULT_FORMAT_STRING,
36
            scheme=DEFAULT_SCHEME,
37
            timeout=DEFAULT_TIMEOUT,
38
            proxies=None,
39
            user_agent=None,
40
    ):
41
        """
42
        Initialize a What3Words geocoder with 3-word or OneWord-address and
43
        What3Words API key.
44

45
            .. versionadded:: 1.5.0
46

47
        :param string api_key: Key provided by What3Words.
48

49
        :param string format_string: String containing '%s' where the
50
            string to geocode should be interpolated before querying the
51
            geocoder. For example: '%s, piped.gains.jungle'. The default
52
            is just '%s'.
53

54
        :param string scheme: Use 'https' or 'http' as the API URL's scheme.
55
            Default is https. Note that SSL connections' certificates are not
56
            verified.
57

58

59
        :param int timeout: Time, in seconds, to wait for the geocoding service
60
            to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
61
            exception.
62

63

64
        :param dict proxies: If specified, routes this geocoder's requests
65
            through the specified proxy. E.g., {"https": "192.0.2.0"}. For
66
            more information, see documentation on
67
            :class:`urllib2.ProxyHandler`.
68
        """
69
        super(What3Words, self).__init__(
70
            format_string,
71
            scheme,
72
            timeout,
73
            proxies,
74
            user_agent=user_agent,
75
        )
76
        self.api_key = api_key
77
        self.api = (
78
            "%s://api.what3words.com/" % self.scheme
79
        )
80

    
81
    def _check_query(self, query):
82
        """
83
        Check query validity with regex
84
        """
85
        if not (self.word_re.match(query) or
86
                self.multiple_word_re.match(query)):
87
            return False
88
        else:
89
            return True
90

    
91
    def geocode(self,
92
                query,
93
                lang='en',
94
                exactly_one=True,
95
                timeout=None):
96

    
97
        """
98
        Geocode a "3 words" or "OneWord" query.
99

100
        :param string query: The 3-word or OneWord-address you wish to geocode.
101

102
        :param string lang: two character language codes as supported by
103
            the API (http://what3words.com/api/reference/languages).
104

105
        :param bool exactly_one: Parameter has no effect for this geocoder.
106
            Due to the address scheme there is always exactly one result.
107

108
        :param int timeout: Time, in seconds, to wait for the geocoding service
109
            to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
110
            exception. Set this only if you wish to override, on this call
111
            only, the value set during the geocoder's initialization.
112
            .. versionadded:: 0.97
113
        """
114

    
115
        if not self._check_query(query):
116
            raise exc.GeocoderQueryError(
117
                "Search string must be either like "
118
                "'word.word.word' or '*word' "
119
            )
120

    
121
        params = {
122
            'string': self.format_string % query,
123
            'lang': self.format_string % lang.lower()
124

    
125
        }
126

    
127
        url = "?".join((
128
            (self.api + "w3w"),
129
            "&".join(("=".join(('key', self.api_key)), urlencode(params)))
130
        ))
131
        logger.debug("%s.geocode: %s", self.__class__.__name__, url)
132
        return self._parse_json(
133
            self._call_geocoder(url, timeout=timeout),
134
            exactly_one
135
        )
136

    
137
    def _parse_json(self, resources, exactly_one=True):
138
        """
139
        Parse type, words, latitude, and longitude and language from a
140
        JSON response.
141
        """
142
        if resources.get('error') == "X1":
143
            raise exc.GeocoderAuthenticationFailure()
144

    
145
        if resources.get('error') == "11":
146
            raise exc.GeocoderQueryError(
147
                "Address (Word(s)) not recognised by What3Words."
148
            )
149

    
150
        def parse_resource(resource):
151
            """
152
            Parse record.
153
            """
154

    
155
            if resource['type'] == '3 words':
156
                words = resource['words']
157
                words = join_filter(".", [words[0], words[1], words[2]])
158
                position = resource['position']
159
                latitude, longitude = position[0], position[1]
160

    
161
                if latitude and longitude:
162
                    latitude = float(latitude)
163
                    longitude = float(longitude)
164

    
165
                return Location(words, (latitude, longitude), resource)
166
            elif resource['type'] == 'OneWord':
167
                words = resource['words']
168
                words = join_filter(".", [words[0], words[1], words[2]])
169
                oneword = resource['oneword']
170
                info = resource['info']
171

    
172
                address = join_filter(", ", [
173
                    oneword,
174
                    words,
175
                    info['name'],
176
                    info['address1'],
177
                    info['address2'],
178
                    info['address3'],
179
                    info['city'],
180
                    info['county'],
181
                    info['postcode'],
182
                    info['country_id']
183
                ])
184

    
185
                position = resource['position']
186
                latitude, longitude = position[0], position[1]
187

    
188
                if latitude and longitude:
189
                    latitude = float(latitude)
190
                    longitude = float(longitude)
191

    
192
                return Location(address, (latitude, longitude), resource)
193
            else:
194
                raise exc.GeocoderParseError('Error parsing result.')
195

    
196

    
197
        return parse_resource(resources)
198

    
199

    
200
    def reverse(self, query, lang='en', exactly_one=True, timeout=None):
201
        """
202
        Given a point, find the 3 word address.
203

204
        :param query: The coordinates for which you wish to obtain the 3 word
205
            address.
206

207
        :type query: :class:`geopy.point.Point`, list or tuple of (latitude,
208
            longitude), or string as "%(latitude)s, %(longitude)s"
209

210
        :param string lang: two character language codes as supported by the
211
            API (http://what3words.com/api/reference/languages).
212

213
        :param bool exactly_one: Parameter has no effect for this geocoder.
214
            Due to the address scheme there is always exactly one result.
215

216
        :param int timeout: Time, in seconds, to wait for the geocoding service
217
            to respond before raising a :class:`geopy.exc.GeocoderTimedOut`
218
            exception. Set this only if you wish to override, on this call
219
            only, the value set during the geocoder's initialization.
220

221
        """
222
        lang = lang.lower()
223

    
224
        params = {
225
            'position': self._coerce_point_to_string(query),
226
            'lang': self.format_string % lang
227

    
228
        }
229

    
230
        url = "?".join((
231
            (self.api + "position"),
232
            "&".join(("=".join(('key', self.api_key)), urlencode(params)))
233
        ))
234

    
235
        logger.debug("%s.reverse: %s", self.__class__.__name__, url)
236
        return self._parse_reverse_json(
237
            self._call_geocoder(url, timeout=timeout),
238
        )
239

    
240

    
241
    @staticmethod
242
    def _parse_reverse_json(resources):
243
        """
244
        Parses a location from a single-result reverse API call.
245
        """
246

    
247
        if resources.get('error') == "21":
248
            raise exc.GeocoderQueryError("Invalid coordinates")
249

    
250
        def parse_resource(resource):
251
            """
252
            Parse resource to return Geopy Location object
253
            """
254
            words = resource['words']
255
            words = join_filter(".", [words[0], words[1], words[2]])
256
            position = resource['position']
257
            latitude, longitude = position[0], position[1]
258

    
259
            if latitude and longitude:
260
                latitude = float(latitude)
261
                longitude = float(longitude)
262

    
263
            return Location(words, (latitude, longitude), resource)
264

    
265
        return parse_resource(resources)
266

    
267

    
268

    
269

    
270

    
271

    
272