├── test ├── fixtures │ ├── input.txt │ ├── cli │ │ ├── reverse.csv │ │ ├── reverse_with_errors.csv │ │ ├── forward_noresult.csv │ │ ├── output.csv2 │ │ ├── forward.csv │ │ └── forward_with_headers.csv │ ├── no_ratelimit.json │ ├── 403_apikey_disabled.json │ ├── 401_not_authorized.json │ ├── 402_rate_limit_exceeded.json │ ├── mudgee_australia.json │ ├── badssl-com-chain.pem │ ├── donostia.json │ ├── muenster.json │ └── uk_postcode.json ├── __init__.py ├── test_error_not_authorized.py ├── test_error_blocked.py ├── test_reverse.py ├── test_geocoder_args.py ├── test_batch.py ├── test_flotify_dict.py ├── test_headers.py ├── test_error_ssl.py ├── test_error_ratelimit_exceeded.py ├── test_session.py ├── test_error_unknown.py ├── test_async.py ├── test_all.py ├── test_error_invalid_input.py └── cli │ ├── test_cli_args.py │ └── test_cli_run.py ├── opencage ├── version.py ├── __init__.py ├── command_line.py ├── batch.py └── geocoder.py ├── pytest.ini ├── batch-progress.gif ├── opencage_logo_300_150.png ├── .flake8 ├── SECURITY.md ├── tox.ini ├── .gitignore ├── examples ├── demo.py ├── flask_demo.py ├── batch.py └── addresses.csv ├── Vagrantfile ├── .github └── workflows │ └── build.yml ├── vagrant-provision.sh ├── LICENSE.txt ├── setup.py ├── Changes.txt └── README.md /test/fixtures/input.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /opencage/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '3.2.0' 2 | -------------------------------------------------------------------------------- /test/fixtures/cli/reverse.csv: -------------------------------------------------------------------------------- 1 | 51.9526622,7.6324709 -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, '.') 3 | -------------------------------------------------------------------------------- /test/fixtures/cli/reverse_with_errors.csv: -------------------------------------------------------------------------------- 1 | 50.101010 2 | -100,60.1 3 | 4 | a,b -------------------------------------------------------------------------------- /test/fixtures/cli/forward_noresult.csv: -------------------------------------------------------------------------------- 1 | id,full_address 2 | 123,NOWHERE-INTERESTING -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | pythonpath = . 3 | asyncio_default_fixture_loop_scope = session 4 | -------------------------------------------------------------------------------- /batch-progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCageData/python-opencage-geocoder/HEAD/batch-progress.gif -------------------------------------------------------------------------------- /opencage_logo_300_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenCageData/python-opencage-geocoder/HEAD/opencage_logo_300_150.png -------------------------------------------------------------------------------- /test/fixtures/cli/output.csv2: -------------------------------------------------------------------------------- 1 | id,full_address,lat,lng,postcode 2 | 123,NOWHERE-INTERESTING,51.9526622,7.6324709,48153 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .git,__pycache__,build,dist 4 | ignore = W503 5 | per-file-ignores = 6 | __init__.py: F401 7 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please see the [OpenCage security bounty page](https://opencagedata.com/security-bounty) 6 | -------------------------------------------------------------------------------- /test/fixtures/cli/forward.csv: -------------------------------------------------------------------------------- 1 | "Rathausmarkt 1",Hamburg,20095,Germany 2 | "10 Downing Street",London,"SW1A 2AA","United Kingdom" 3 | "C/ de Mallorca 401",Barcelona,08013,Spain -------------------------------------------------------------------------------- /opencage/__init__.py: -------------------------------------------------------------------------------- 1 | """ Base module for OpenCage stuff. """ 2 | 3 | from .version import __version__ 4 | 5 | __author__ = "OpenCage GmbH" 6 | __email__ = 'support@opencagedata.com' 7 | -------------------------------------------------------------------------------- /test/fixtures/cli/forward_with_headers.csv: -------------------------------------------------------------------------------- 1 | street and number,town,postcode,country 2 | "Rathausmarkt 1",Hamburg,20095,Germany 3 | "10 Downing Street",London,"SW1A 2AA","United Kingdom" 4 | "C/ de Mallorca 401",Barcelona,08013,Spain -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,py312,py313,lint 3 | 4 | [gh] 5 | python = 6 | 3.13 = py313 7 | 3.12 = py312 8 | 3.11 = py311 9 | 3.10 = py310 10 | 3.9 = py39 11 | 3.8 = py38 12 | 13 | [testenv] 14 | deps = 15 | responses 16 | pytest 17 | pytest-aiohttp 18 | pytest-asyncio 19 | commands = 20 | pytest test 21 | 22 | [testenv:lint] 23 | usedevelop = True 24 | deps = 25 | responses 26 | flake8>=7.0.0 27 | pytest 28 | commands = 29 | flake8 opencage examples/demo.py setup.py test 30 | -------------------------------------------------------------------------------- /test/fixtures/no_ratelimit.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentation": "https://opencagedata.com/api", 3 | "licenses": [ 4 | { 5 | "name": "see attribution guide", 6 | "url": "https://opencagedata.com/credits" 7 | } 8 | ], 9 | "results": [ 10 | ], 11 | "status": { 12 | "code": 200, 13 | "message": "OK" 14 | }, 15 | "stay_informed": { 16 | "blog": "https://blog.opencagedata.com", 17 | "twitter": "https://twitter.com/OpenCage" 18 | }, 19 | "thanks": "For using an OpenCage API", 20 | "timestamp": { 21 | "created_http": "Sun, 07 Mar 2021 01:13:17 GMT", 22 | "created_unix": 1615079597 23 | }, 24 | "total_results": 0 25 | } -------------------------------------------------------------------------------- /test/fixtures/403_apikey_disabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentation": "https://opencagedata.com/api", 3 | "licenses": [ 4 | { 5 | "name": "see attribution guide", 6 | "url": "https://opencagedata.com/credits" 7 | } 8 | ], 9 | "results": [], 10 | "status": { 11 | "code": 403, 12 | "message": "disabled" 13 | }, 14 | "stay_informed": { 15 | "blog": "https://blog.opencagedata.com", 16 | "twitter": "https://twitter.com/OpenCage" 17 | }, 18 | "thanks": "For using an OpenCage API", 19 | "timestamp": { 20 | "created_http": "Sun, 07 Mar 2021 01:19:06 GMT", 21 | "created_unix": 1615079946 22 | }, 23 | "total_results": 0 24 | } -------------------------------------------------------------------------------- /test/fixtures/401_not_authorized.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentation": "https://opencagedata.com/api", 3 | "licenses": [{ 4 | "name": "see attribution guide", 5 | "url": "https://opencagedata.com/credits" 6 | }], 7 | "results": [], 8 | "status": { 9 | "code": 401, 10 | "message": "invalid API key" 11 | }, 12 | "stay_informed": { 13 | "blog": "https://blog.opencagedata.com", 14 | "twitter": "https://twitter.com/opencagedata" 15 | }, 16 | "thanks": "For using an OpenCage Data API", 17 | "timestamp": { 18 | "created_http": "Sun, 09 Jun 2019 19:58:46 GMT", 19 | "created_unix": 1560110326 20 | }, 21 | "total_results": 0 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | 24 | # Installer logs 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | 28 | # Unit test / coverage reports 29 | htmlcov/ 30 | .tox/ 31 | .coverage 32 | .cache 33 | nosetests.xml 34 | coverage.xml 35 | 36 | # Translations 37 | *.mo 38 | *.pot 39 | 40 | # Django stuff: 41 | *.log 42 | 43 | # Sphinx documentation 44 | docs/_build/ 45 | 46 | .*.swp 47 | .vagrant 48 | .pypirc 49 | .eggs/ 50 | 51 | # VS Code 52 | .vscode/ 53 | -------------------------------------------------------------------------------- /test/test_error_not_authorized.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import responses 5 | 6 | from opencage.geocoder import OpenCageGeocode 7 | from opencage.geocoder import NotAuthorizedError 8 | 9 | geocoder = OpenCageGeocode('unauthorized-key') 10 | 11 | 12 | @responses.activate 13 | def test_api_key_not_authorized(): 14 | responses.add( 15 | responses.GET, 16 | geocoder.url, 17 | body=Path('test/fixtures/401_not_authorized.json').read_text(encoding="utf-8"), 18 | status=401, 19 | ) 20 | 21 | with pytest.raises(NotAuthorizedError) as excinfo: 22 | geocoder.geocode("whatever") 23 | assert str(excinfo.value) == 'Your API key is not authorized. You may have entered it incorrectly.' 24 | -------------------------------------------------------------------------------- /test/test_error_blocked.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pytest 4 | import responses 5 | 6 | from opencage.geocoder import OpenCageGeocode 7 | from opencage.geocoder import ForbiddenError 8 | 9 | 10 | geocoder = OpenCageGeocode('2e10e5e828262eb243ec0b54681d699a') # will always return 403 11 | 12 | 13 | @responses.activate 14 | def test_api_key_blocked(): 15 | responses.add( 16 | responses.GET, 17 | geocoder.url, 18 | body=Path('test/fixtures/403_apikey_disabled.json').read_text(encoding="utf-8"), 19 | status=403, 20 | ) 21 | 22 | with pytest.raises(ForbiddenError) as excinfo: 23 | geocoder.geocode("whatever") 24 | assert str(excinfo.value) == 'Your API key has been blocked or suspended.' 25 | -------------------------------------------------------------------------------- /test/test_reverse.py: -------------------------------------------------------------------------------- 1 | from opencage.geocoder import _query_for_reverse_geocoding 2 | 3 | 4 | def _expected_output(input_latlng, expected_output): 5 | def test(): 6 | lat, lng = input_latlng 7 | assert _query_for_reverse_geocoding(lat, lng) == expected_output 8 | return test 9 | 10 | 11 | def test_reverse(): 12 | _expected_output((10, 10), "10,10") 13 | _expected_output((10.0, 10.0), "10.0,10.0") 14 | _expected_output((0.000002, -120), "0.000002,-120") 15 | _expected_output((2.000002, -120), "2.000002,-120") 16 | _expected_output((2.000002, -120.000002), "2.000002,-120.000002") 17 | _expected_output((2.000002, -1.0000002), "2.000002,-1.0000002") 18 | _expected_output((2.000002, 0.0000001), "2.000002,0.0000001") 19 | 20 | _expected_output(("2.000002", "-120"), "2.000002,-120") 21 | -------------------------------------------------------------------------------- /test/test_geocoder_args.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from opencage.geocoder import OpenCageGeocode 4 | 5 | import os 6 | 7 | 8 | def test_protocol_http(): 9 | """Test that HTTP protocol can be set correctly""" 10 | geocoder = OpenCageGeocode('abcde', protocol='http') 11 | assert geocoder.url == 'http://api.opencagedata.com/geocode/v1/json' 12 | 13 | 14 | def test_api_key_env_var(): 15 | """Test that API key can be set by an environment variable""" 16 | 17 | os.environ['OPENCAGE_API_KEY'] = 'from-env-var' 18 | geocoder = OpenCageGeocode() 19 | assert geocoder.key == 'from-env-var' 20 | 21 | 22 | def test_custom_domain(): 23 | """Test that custom domain can be set""" 24 | geocoder = OpenCageGeocode('abcde', domain='example.com') 25 | assert geocoder.url == 'https://example.com/geocode/v1/json' 26 | -------------------------------------------------------------------------------- /test/fixtures/402_rate_limit_exceeded.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentation": "https://opencagedata.com/api", 3 | "licenses": [ 4 | { 5 | "name": "see attribution guide", 6 | "url": "https://opencagedata.com/credits" 7 | } 8 | ], 9 | "rate": { 10 | "limit": 2500, 11 | "remaining": 0, 12 | "reset": 1615161600 13 | }, 14 | "results": [], 15 | "status": { 16 | "become_a_customer": "https://opencagedata.com/pricing", 17 | "code": 402, 18 | "message": "quota exceeded" 19 | }, 20 | "stay_informed": { 21 | "blog": "https://blog.opencagedata.com", 22 | "twitter": "https://twitter.com/OpenCage" 23 | }, 24 | "thanks": "For using an OpenCage API", 25 | "timestamp": { 26 | "created_http": "Sun, 07 Mar 2021 01:18:00 GMT", 27 | "created_unix": 1615079880 28 | }, 29 | "total_results": 0 30 | } -------------------------------------------------------------------------------- /examples/demo.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | from opencage.geocoder import OpenCageGeocode 3 | 4 | APIKEY = 'your-key-here' 5 | 6 | geocoder = OpenCageGeocode(APIKEY) 7 | 8 | results = geocoder.reverse_geocode(44.8303087, -0.5761911) 9 | pprint(results) 10 | # [{'components': {'city': 'Bordeaux', 11 | # 'country': 'France', 12 | # 'country_code': 'fr', 13 | # 'county': 'Bordeaux', 14 | # 'house_number': '11', 15 | # 'political_union': 'European Union', 16 | # 'postcode': '33800', 17 | # 'road': 'Rue Sauteyron', 18 | # 'state': 'New Aquitaine', 19 | # 'suburb': 'Bordeaux Sud'}, 20 | # 'formatted': '11 Rue Sauteyron, 33800 Bordeaux, France', 21 | # 'geometry': {'lat': 44.8303087, 'lng': -0.5761911}}] 22 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | 6 | config.vm.box = 'bento/ubuntu-24.04' 7 | if RUBY_PLATFORM.include?('darwin') && RUBY_PLATFORM.include?('arm64') 8 | # Apple Silicon/M processor 9 | config.vm.box = 'gutehall/ubuntu24-04' 10 | end 11 | 12 | config.vm.synced_folder ".", "/home/vagrant/python-opencage-geocoder" 13 | 14 | # provision using a simple shell script 15 | config.vm.provision :shell, path: "vagrant-provision.sh", privileged: false 16 | 17 | 18 | # configure shared package cache if possible 19 | if Vagrant.has_plugin?("vagrant-cachier") 20 | config.cache.enable :apt 21 | config.cache.scope = :box 22 | end 23 | 24 | 25 | config.vm.provider "virtualbox" do |vb| 26 | vb.gui = false 27 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/test_batch.py: -------------------------------------------------------------------------------- 1 | from opencage.batch import OpenCageBatchGeocoder 2 | 3 | batch = OpenCageBatchGeocoder({}) 4 | 5 | 6 | def test_deep_get_result_value(): 7 | result = { 8 | 'annotations': { 9 | 'FIPS': { 10 | 'state': 'CA' 11 | } 12 | }, 13 | 'components': { 14 | 'street': 'Main Road' 15 | } 16 | } 17 | 18 | assert batch.deep_get_result_value(result, ['hello', 'world']) is None 19 | 20 | assert batch.deep_get_result_value(result, ['components', 'street']) == 'Main Road' 21 | assert batch.deep_get_result_value(result, ['components', 'city']) is None 22 | assert batch.deep_get_result_value(result, ['components', 'city'], '') == '' 23 | 24 | assert batch.deep_get_result_value([], ['hello', 'world']) is None 25 | assert batch.deep_get_result_value(None, ['hello', 'world']) is None 26 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "11 22 2 * *" 8 | 9 | jobs: 10 | tox: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | py: 16 | - "3.13" 17 | - "3.12" 18 | - "3.11" 19 | - "3.10" 20 | - "3.9" 21 | - "3.8" 22 | os: 23 | - ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Setup python for test ${{ matrix.py }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.py }} 30 | - name: Install tox 31 | run: python -m pip install tox-gh>=1.2 32 | - name: Setup test suite 33 | run: tox -vv --notest 34 | - name: Run test suite 35 | run: tox --skip-pkg-install 36 | -------------------------------------------------------------------------------- /test/test_flotify_dict.py: -------------------------------------------------------------------------------- 1 | from opencage.geocoder import floatify_latlng 2 | 3 | 4 | def test_string(): 5 | assert floatify_latlng("123") == "123" 6 | 7 | 8 | def test_empty_dict(): 9 | assert floatify_latlng({}) == {} 10 | 11 | 12 | def test_empty_list(): 13 | assert floatify_latlng([]) == [] 14 | 15 | 16 | def test_dict_with_floats(): 17 | assert floatify_latlng({'geom': {'lat': 12.01, 'lng': -0.9}}) == {'geom': {'lat': 12.01, 'lng': -0.9}} 18 | 19 | 20 | def dict_with_stringified_floats(): 21 | assert floatify_latlng({'geom': {'lat': "12.01", 'lng': "-0.9"}}) == {'geom': {'lat': 12.01, 'lng': -0.9}} 22 | 23 | 24 | def dict_with_list(): 25 | assert floatify_latlng( 26 | {'results': [{'geom': {'lat': "12.01", 'lng': "-0.9"}}, {'geometry': {'lat': '0.1', 'lng': '10'}}]} 27 | ) == {'results': [{'geom': {'lat': 12.01, 'lng': -0.9}}, {'geometry': {'lat': 0.1, 'lng': 10}}]} 28 | 29 | 30 | def list_with_things(): 31 | assert floatify_latlng([{'foo': 'bar'}]) == [{'foo': 'bar'}] 32 | -------------------------------------------------------------------------------- /test/test_headers.py: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | from pathlib import Path 4 | 5 | import os 6 | import re 7 | import responses 8 | 9 | from opencage.geocoder import OpenCageGeocode 10 | 11 | # reduce maximum backoff retry time from 120s to 1s 12 | os.environ['BACKOFF_MAX_TIME'] = '1' 13 | 14 | geocoder = OpenCageGeocode('abcde', user_agent_comment='OpenCage Test') 15 | 16 | user_agent_format = re.compile( 17 | r'^opencage-python/[\d\.]+ Python/[\d\.]+ (requests|aiohttp)/[\d\.]+ \(OpenCage Test\)$') 18 | 19 | 20 | @responses.activate 21 | def test_sync(): 22 | responses.add( 23 | responses.GET, 24 | geocoder.url, 25 | body=Path('test/fixtures/uk_postcode.json').read_text(encoding="utf-8"), 26 | status=200 27 | ) 28 | 29 | geocoder.geocode("EC1M 5RF") 30 | 31 | # Check the User-Agent header in the most recent request 32 | request = responses.calls[-1].request 33 | user_agent = request.headers['User-Agent'] 34 | 35 | assert user_agent_format.match(user_agent) is not None 36 | -------------------------------------------------------------------------------- /examples/flask_demo.py: -------------------------------------------------------------------------------- 1 | # Sample forward geocode: http://127.0.0.1:5000/forward/147%20Farm%20STreet%20Blackstone%20MA%2001504 2 | # 3 | # Sample reverse geocode: http://127.0.0.1:5000/reverse/42.036488/-71.519678/ 4 | import json 5 | from flask import Flask 6 | from flask import request 7 | from opencage.geocoder import OpenCageGeocode 8 | 9 | app = Flask(__name__) 10 | _key = OPEN_CAGE_KEY = "YOUR_OPEN_CAGE_KEY" 11 | _geocoder = OpenCageGeocode(OPEN_CAGE_KEY) 12 | 13 | @app.route("/forward/
") 14 | def forward(address): 15 | verbose = json.loads(request.args.get('verbose', "false").lower()) 16 | raw_result = _geocoder.geocode(address) 17 | formatted = [{"confidence": r["confidence"], "geometry": r["geometry"]} for r in raw_result if r["confidence"]] 18 | return json.dumps(raw_result if verbose else formatted) 19 | 20 | @app.route("/reverse/
154 |
155 |
156 | ## Copyright & License
157 |
158 | This software is copyright OpenCage GmbH.
159 | Please see `LICENSE.txt`
160 |
161 | ### Who is OpenCage GmbH?
162 |
163 |
164 |
165 | We run a worldwide [geocoding API](https://opencagedata.com/api) and [geosearch](https://opencagedata.com/geosearch) service based on open data.
166 | Learn more [about us](https://opencagedata.com/about).
167 |
168 | We also run [Geomob](https://thegeomob.com), a series of regular meetups for location based service creators, where we do our best to highlight geoinnovation. If you like geo stuff, you will probably enjoy [the Geomob podcast](https://thegeomob.com/podcast/).
169 |
--------------------------------------------------------------------------------
/test/fixtures/donostia.json:
--------------------------------------------------------------------------------
1 | {
2 | "documentation": "https://opencagedata.com/api",
3 | "licenses": [
4 | {
5 | "name": "see attribution guide",
6 | "url": "https://opencagedata.com/credits"
7 | }
8 | ],
9 | "rate": {
10 | "limit": 2500,
11 | "remaining": 2498,
12 | "reset": 1615161600
13 | },
14 | "results": [
15 | {
16 | "bounds": {
17 | "northeast": {
18 | "lat": 43.3381594,
19 | "lng": -1.8878839
20 | },
21 | "southwest": {
22 | "lat": 43.2178373,
23 | "lng": -2.0868082
24 | }
25 | },
26 | "components": {
27 | "ISO_3166-1_alpha-2": "ES",
28 | "ISO_3166-1_alpha-3": "ESP",
29 | "_category": "place",
30 | "_type": "city",
31 | "city": "San Sebastián",
32 | "continent": "Europe",
33 | "country": "Spain",
34 | "country_code": "es",
35 | "municipality": "Donostialdea",
36 | "political_union": "European Union",
37 | "province": "Gipuzkoa",
38 | "state": "Autonomous Community of the Basque Country",
39 | "state_code": "PV"
40 | },
41 | "confidence": 6,
42 | "formatted": "San Sebastián, Autonomous Community of the Basque Country, Spain",
43 | "geometry": {
44 | "lat": 43.3224219,
45 | "lng": -1.9838889
46 | }
47 | },
48 | {
49 | "bounds": {
50 | "northeast": {
51 | "lat": 43.3235144,
52 | "lng": -1.987835
53 | },
54 | "southwest": {
55 | "lat": 43.3227734,
56 | "lng": -1.9895938
57 | }
58 | },
59 | "components": {
60 | "ISO_3166-1_alpha-2": "ES",
61 | "ISO_3166-1_alpha-3": "ESP",
62 | "_category": "natural/water",
63 | "_type": "water",
64 | "continent": "Europe",
65 | "country": "Spain",
66 | "country_code": "es",
67 | "political_union": "European Union",
68 | "suburb": "Parte Zaharra",
69 | "water": "Donostia"
70 | },
71 | "confidence": 9,
72 | "formatted": "Donostia, Parte Zaharra, Spain",
73 | "geometry": {
74 | "lat": 43.3232109,
75 | "lng": -1.9884685
76 | }
77 | },
78 | {
79 | "bounds": {
80 | "northeast": {
81 | "lat": 43.3449618,
82 | "lng": -1.8013445
83 | },
84 | "southwest": {
85 | "lat": 43.3422299,
86 | "lng": -1.8022745
87 | }
88 | },
89 | "components": {
90 | "ISO_3166-1_alpha-2": "ES",
91 | "ISO_3166-1_alpha-3": "ESP",
92 | "_category": "road",
93 | "_type": "road",
94 | "continent": "Europe",
95 | "country": "Spain",
96 | "country_code": "es",
97 | "county": "Bidasoa Beherea / Bajo Bidasoa",
98 | "hamlet": "Pinar",
99 | "political_union": "European Union",
100 | "postcode": "20301",
101 | "province": "Gipuzkoa",
102 | "road": "Donostia",
103 | "road_type": "residential",
104 | "state": "Autonomous Community of the Basque Country",
105 | "state_code": "PV",
106 | "town": "Irun"
107 | },
108 | "confidence": 9,
109 | "formatted": "Donostia, 20301 Irun, Spain",
110 | "geometry": {
111 | "lat": 43.3432825,
112 | "lng": -1.8019222
113 | }
114 | },
115 | {
116 | "bounds": {
117 | "northeast": {
118 | "lat": 42.6739972,
119 | "lng": 3.0325751
120 | },
121 | "southwest": {
122 | "lat": 42.6730864,
123 | "lng": 3.0318899
124 | }
125 | },
126 | "components": {
127 | "ISO_3166-1_alpha-2": "FR",
128 | "ISO_3166-1_alpha-3": "FRA",
129 | "_category": "road",
130 | "_type": "road",
131 | "continent": "Europe",
132 | "country": "France",
133 | "country_code": "fr",
134 | "county": "Pyrénées-Orientales",
135 | "municipality": "Perpignan",
136 | "political_union": "European Union",
137 | "postcode": "66140",
138 | "quarter": "L'Aviation",
139 | "road": "Donostia",
140 | "state": "Occitania",
141 | "state_code": "OCC",
142 | "town": "Canet-en-Roussillon"
143 | },
144 | "confidence": 9,
145 | "formatted": "Donostia, 66140 Canet-en-Roussillon, France",
146 | "geometry": {
147 | "lat": 42.6734947,
148 | "lng": 3.0322839
149 | }
150 | },
151 | {
152 | "bounds": {
153 | "northeast": {
154 | "lat": -31.312091,
155 | "lng": -64.4982232
156 | },
157 | "southwest": {
158 | "lat": -31.3143529,
159 | "lng": -64.5032543
160 | }
161 | },
162 | "components": {
163 | "ISO_3166-1_alpha-2": "AR",
164 | "ISO_3166-1_alpha-3": "ARG",
165 | "_category": "road",
166 | "_type": "road",
167 | "continent": "South America",
168 | "country": "Argentina",
169 | "country_code": "ar",
170 | "county": "Pedanía Rosario",
171 | "road": "Donostia",
172 | "road_type": "residential",
173 | "state": "Córdoba",
174 | "state_code": "X",
175 | "state_district": "Departamento Punilla",
176 | "village": "Bialet Massé"
177 | },
178 | "confidence": 9,
179 | "formatted": "Donostia, Departamento Punilla, Bialet Massé, Argentina",
180 | "geometry": {
181 | "lat": -31.3129419,
182 | "lng": -64.5002494
183 | }
184 | },
185 | {
186 | "bounds": {
187 | "northeast": {
188 | "lat": 43.3191704,
189 | "lng": -2.0063319
190 | },
191 | "southwest": {
192 | "lat": 43.3190704,
193 | "lng": -2.0064319
194 | }
195 | },
196 | "components": {
197 | "ISO_3166-1_alpha-2": "ES",
198 | "ISO_3166-1_alpha-3": "ESP",
199 | "_category": "transportation",
200 | "_type": "railway",
201 | "city": "San Sebastián",
202 | "continent": "Europe",
203 | "country": "Spain",
204 | "country_code": "es",
205 | "municipality": "Donostialdea",
206 | "political_union": "European Union",
207 | "postcode": "20008",
208 | "province": "Gipuzkoa",
209 | "railway": "Donostia",
210 | "road": "Funikularraren Plaza",
211 | "state": "Autonomous Community of the Basque Country",
212 | "state_code": "PV",
213 | "suburb": "Antiguo"
214 | },
215 | "confidence": 9,
216 | "formatted": "Donostia, Funikularraren Plaza, 20008 San Sebastián, Spain",
217 | "geometry": {
218 | "lat": 43.3191204,
219 | "lng": -2.0063819
220 | }
221 | },
222 | {
223 | "bounds": {
224 | "northeast": {
225 | "lat": 51.5147517,
226 | "lng": -0.160937
227 | },
228 | "southwest": {
229 | "lat": 51.5146517,
230 | "lng": -0.161037
231 | }
232 | },
233 | "components": {
234 | "ISO_3166-1_alpha-2": "GB",
235 | "ISO_3166-1_alpha-3": "GBR",
236 | "_category": "commerce",
237 | "_type": "restaurant",
238 | "city": "London",
239 | "continent": "Europe",
240 | "country": "United Kingdom",
241 | "country_code": "gb",
242 | "county": "Westminster",
243 | "county_code": "WSM",
244 | "house_number": "10",
245 | "postcode": "W1H 7JN",
246 | "restaurant": "Donostia",
247 | "road": "Seymour Place",
248 | "state": "England",
249 | "state_code": "ENG",
250 | "state_district": "Greater London",
251 | "suburb": "Marylebone"
252 | },
253 | "confidence": 9,
254 | "formatted": "Donostia, 10 Seymour Place, London W1H 7JN, United Kingdom",
255 | "geometry": {
256 | "lat": 51.5147017,
257 | "lng": -0.160987
258 | }
259 | },
260 | {
261 | "bounds": {
262 | "northeast": {
263 | "lat": 44.83445,
264 | "lng": -0.5666749
265 | },
266 | "southwest": {
267 | "lat": 44.83435,
268 | "lng": -0.5667749
269 | }
270 | },
271 | "components": {
272 | "ISO_3166-1_alpha-2": "FR",
273 | "ISO_3166-1_alpha-3": "FRA",
274 | "_category": "commerce",
275 | "_type": "restaurant",
276 | "city": "Bordeaux",
277 | "continent": "Europe",
278 | "country": "France",
279 | "country_code": "fr",
280 | "county": "Gironde",
281 | "house_number": "21",
282 | "municipality": "Bordeaux",
283 | "political_union": "European Union",
284 | "postcode": "33800",
285 | "restaurant": "Donostia",
286 | "road": "Place Meynard",
287 | "state": "New Aquitaine",
288 | "state_code": "NAQ",
289 | "suburb": "Saint-Michel"
290 | },
291 | "confidence": 9,
292 | "formatted": "Donostia, 21 Place Meynard, 33800 Bordeaux, France",
293 | "geometry": {
294 | "lat": 44.8344,
295 | "lng": -0.5667249
296 | }
297 | }
298 | ],
299 | "status": {
300 | "code": 200,
301 | "message": "OK"
302 | },
303 | "stay_informed": {
304 | "blog": "https://blog.opencagedata.com",
305 | "twitter": "https://twitter.com/OpenCage"
306 | },
307 | "thanks": "For using an OpenCage API",
308 | "timestamp": {
309 | "created_http": "Sun, 07 Mar 2021 01:12:01 GMT",
310 | "created_unix": 1615079521
311 | },
312 | "total_results": 8
313 | }
--------------------------------------------------------------------------------
/test/fixtures/muenster.json:
--------------------------------------------------------------------------------
1 | {
2 | "documentation": "https://opencagedata.com/api",
3 | "licenses": [
4 | {
5 | "name": "see attribution guide",
6 | "url": "https://opencagedata.com/credits"
7 | }
8 | ],
9 | "rate": {
10 | "limit": 2500,
11 | "remaining": 2497,
12 | "reset": 1615161600
13 | },
14 | "results": [
15 | {
16 | "bounds": {
17 | "northeast": {
18 | "lat": 52.0600251,
19 | "lng": 7.7743634
20 | },
21 | "southwest": {
22 | "lat": 51.8401448,
23 | "lng": 7.4737853
24 | }
25 | },
26 | "components": {
27 | "ISO_3166-1_alpha-2": "DE",
28 | "ISO_3166-1_alpha-3": "DEU",
29 | "_category": "place",
30 | "_type": "city",
31 | "city": "Münster",
32 | "continent": "Europe",
33 | "country": "Germany",
34 | "country_code": "de",
35 | "political_union": "European Union",
36 | "state": "North Rhine-Westphalia",
37 | "state_code": "NW"
38 | },
39 | "confidence": 4,
40 | "formatted": "Münster, North Rhine-Westphalia, Germany",
41 | "geometry": {
42 | "lat": 51.9625101,
43 | "lng": 7.6251879
44 | }
45 | },
46 | {
47 | "bounds": {
48 | "northeast": {
49 | "lat": 53.1689062,
50 | "lng": -6.949942
51 | },
52 | "southwest": {
53 | "lat": 51.388867,
54 | "lng": -10.6626169
55 | }
56 | },
57 | "components": {
58 | "ISO_3166-1_alpha-2": "IE",
59 | "ISO_3166-1_alpha-3": "IRL",
60 | "_category": "place",
61 | "_type": "state",
62 | "continent": "Europe",
63 | "country": "Ireland",
64 | "country_code": "ie",
65 | "political_union": "European Union",
66 | "state": "Munster",
67 | "state_code": "M"
68 | },
69 | "confidence": 1,
70 | "formatted": "Munster, Ireland",
71 | "geometry": {
72 | "lat": 52.3076216,
73 | "lng": -8.5708973
74 | }
75 | },
76 | {
77 | "bounds": {
78 | "northeast": {
79 | "lat": 48.9209128,
80 | "lng": 6.9284105
81 | },
82 | "southwest": {
83 | "lat": 48.8961288,
84 | "lng": 6.8640003
85 | }
86 | },
87 | "components": {
88 | "ISO_3166-1_alpha-2": "FR",
89 | "ISO_3166-1_alpha-3": "FRA",
90 | "_category": "place",
91 | "_type": "village",
92 | "continent": "Europe",
93 | "country": "France",
94 | "country_code": "fr",
95 | "county": "Moselle",
96 | "municipality": "Sarrebourg-Château-Salins",
97 | "political_union": "European Union",
98 | "postcode": "57670",
99 | "state": "Grand Est",
100 | "state_code": "GES",
101 | "village": "Munster"
102 | },
103 | "confidence": 7,
104 | "formatted": "57670 Munster, France",
105 | "geometry": {
106 | "lat": 48.9154719,
107 | "lng": 6.9037077
108 | }
109 | },
110 | {
111 | "bounds": {
112 | "northeast": {
113 | "lat": 49.4626882,
114 | "lng": 10.0571749
115 | },
116 | "southwest": {
117 | "lat": 49.4226882,
118 | "lng": 10.0171749
119 | }
120 | },
121 | "components": {
122 | "ISO_3166-1_alpha-2": "DE",
123 | "ISO_3166-1_alpha-3": "DEU",
124 | "_category": "place",
125 | "_type": "village",
126 | "continent": "Europe",
127 | "country": "Germany",
128 | "country_code": "de",
129 | "county": "Main-Tauber-Kreis",
130 | "political_union": "European Union",
131 | "postcode": "97993",
132 | "state": "Baden-Württemberg",
133 | "state_code": "BW",
134 | "town": "Creglingen",
135 | "village": "Münster"
136 | },
137 | "confidence": 7,
138 | "formatted": "97993 Creglingen, Germany",
139 | "geometry": {
140 | "lat": 49.4426882,
141 | "lng": 10.0371749
142 | }
143 | },
144 | {
145 | "bounds": {
146 | "northeast": {
147 | "lat": 48.0646256,
148 | "lng": 7.1665754
149 | },
150 | "southwest": {
151 | "lat": 48.0286031,
152 | "lng": 7.0964409
153 | }
154 | },
155 | "components": {
156 | "ISO_3166-1_alpha-2": "FR",
157 | "ISO_3166-1_alpha-3": "FRA",
158 | "_category": "place",
159 | "_type": "city",
160 | "continent": "Europe",
161 | "country": "France",
162 | "country_code": "fr",
163 | "county": "Haut-Rhin",
164 | "municipality": "Colmar-Ribeauvillé",
165 | "political_union": "European Union",
166 | "postcode": "68140",
167 | "state": "Grand Est",
168 | "state_code": "GES",
169 | "town": "Munster"
170 | },
171 | "confidence": 7,
172 | "formatted": "68140 Munster, France",
173 | "geometry": {
174 | "lat": 48.0408618,
175 | "lng": 7.1371568
176 | }
177 | },
178 | {
179 | "bounds": {
180 | "northeast": {
181 | "lat": 48.6515558,
182 | "lng": 10.9314006
183 | },
184 | "southwest": {
185 | "lat": 48.5841708,
186 | "lng": 10.8788966
187 | }
188 | },
189 | "components": {
190 | "ISO_3166-1_alpha-2": "DE",
191 | "ISO_3166-1_alpha-3": "DEU",
192 | "_category": "place",
193 | "_type": "village",
194 | "continent": "Europe",
195 | "country": "Germany",
196 | "country_code": "de",
197 | "county": "Landkreis Donau-Ries",
198 | "municipality": "Rain (Schwaben)",
199 | "political_union": "European Union",
200 | "postcode": "86692",
201 | "state": "Bavaria",
202 | "state_code": "BY",
203 | "village": "Münster"
204 | },
205 | "confidence": 7,
206 | "formatted": "86692 Münster, Germany",
207 | "geometry": {
208 | "lat": 48.6242219,
209 | "lng": 10.9008883
210 | }
211 | },
212 | {
213 | "bounds": {
214 | "northeast": {
215 | "lat": 50.4101197,
216 | "lng": 8.6366094
217 | },
218 | "southwest": {
219 | "lat": 50.3701197,
220 | "lng": 8.5966094
221 | }
222 | },
223 | "components": {
224 | "ISO_3166-1_alpha-2": "DE",
225 | "ISO_3166-1_alpha-3": "DEU",
226 | "_category": "place",
227 | "_type": "village",
228 | "continent": "Europe",
229 | "country": "Germany",
230 | "country_code": "de",
231 | "county": "Wetteraukreis",
232 | "political_union": "European Union",
233 | "state": "Hesse",
234 | "state_code": "HE",
235 | "town": "Butzbach",
236 | "village": "Münster"
237 | },
238 | "confidence": 7,
239 | "formatted": "Butzbach, Hesse, Germany",
240 | "geometry": {
241 | "lat": 50.3901197,
242 | "lng": 8.6166094
243 | }
244 | },
245 | {
246 | "bounds": {
247 | "northeast": {
248 | "lat": 47.4709858,
249 | "lng": 11.8640661
250 | },
251 | "southwest": {
252 | "lat": 47.4020668,
253 | "lng": 11.7729344
254 | }
255 | },
256 | "components": {
257 | "ISO_3166-1_alpha-2": "AT",
258 | "ISO_3166-1_alpha-3": "AUT",
259 | "_category": "place",
260 | "_type": "city",
261 | "city": "Gemeinde Münster",
262 | "continent": "Europe",
263 | "country": "Austria",
264 | "country_code": "at",
265 | "political_union": "European Union",
266 | "postcode": "6232",
267 | "region": "Bezirk Kufstein",
268 | "state": "Tyrol"
269 | },
270 | "confidence": 7,
271 | "formatted": "Gemeinde Münster, Tyrol, Austria",
272 | "geometry": {
273 | "lat": 47.4366555,
274 | "lng": 11.8134682
275 | }
276 | },
277 | {
278 | "bounds": {
279 | "northeast": {
280 | "lat": 46.9474256,
281 | "lng": 7.452171
282 | },
283 | "southwest": {
284 | "lat": 46.9470562,
285 | "lng": 7.4510185
286 | }
287 | },
288 | "components": {
289 | "ISO_3166-1_alpha-2": "CH",
290 | "ISO_3166-1_alpha-3": "CHE",
291 | "_category": "place_of_worship",
292 | "_type": "place_of_worship",
293 | "city": "Bern",
294 | "city_district": "Stadtteil I",
295 | "continent": "Europe",
296 | "country": "Switzerland",
297 | "country_code": "ch",
298 | "county": "Bern-Mittelland administrative district",
299 | "place_of_worship": "Münster",
300 | "postcode": "3011",
301 | "quarter": "Old City",
302 | "road": "Münsterplatz",
303 | "state": "Bern",
304 | "state_code": "BE",
305 | "state_district": "Bernese Mittelland administrative region"
306 | },
307 | "confidence": 9,
308 | "formatted": "Münster, Münsterplatz, 3011 Bern, Switzerland",
309 | "geometry": {
310 | "lat": 46.9472379,
311 | "lng": 7.4515787
312 | }
313 | },
314 | {
315 | "bounds": {
316 | "northeast": {
317 | "lat": 52.1952705,
318 | "lng": -104.9807394
319 | },
320 | "southwest": {
321 | "lat": 52.1874551,
322 | "lng": -105.0067561
323 | }
324 | },
325 | "components": {
326 | "ISO_3166-1_alpha-2": "CA",
327 | "ISO_3166-1_alpha-3": "CAN",
328 | "_category": "place",
329 | "_type": "county",
330 | "continent": "North America",
331 | "country": "Canada",
332 | "country_code": "ca",
333 | "county": "Muenster",
334 | "state": "Saskatchewan",
335 | "state_code": "SK"
336 | },
337 | "confidence": 8,
338 | "formatted": "SK, Canada",
339 | "geometry": {
340 | "lat": 52.1925528,
341 | "lng": -104.9937505
342 | }
343 | }
344 | ],
345 | "status": {
346 | "code": 200,
347 | "message": "OK"
348 | },
349 | "stay_informed": {
350 | "blog": "https://blog.opencagedata.com",
351 | "twitter": "https://twitter.com/OpenCage"
352 | },
353 | "thanks": "For using an OpenCage API",
354 | "timestamp": {
355 | "created_http": "Sun, 07 Mar 2021 01:13:17 GMT",
356 | "created_unix": 1615079597
357 | },
358 | "total_results": 10
359 | }
--------------------------------------------------------------------------------
/test/fixtures/uk_postcode.json:
--------------------------------------------------------------------------------
1 | {
2 | "total_results": 10,
3 | "licenses": [{
4 | "name": "CC-BY-SA",
5 | "url": "http://creativecommons.org/licenses/by-sa/3.0/"
6 | }, {
7 | "name": "ODbL",
8 | "url": "http://opendatacommons.org/licenses/odbl/summary/"
9 | }],
10 | "status": {
11 | "message": "OK",
12 | "code": 200
13 | },
14 | "thanks": "For using an OpenCage Data API",
15 | "rate": {
16 | "limit": "2500",
17 | "remaining": 2487,
18 | "reset": 1402185600
19 | },
20 | "results": [{
21 | "annotations": {},
22 | "components": {
23 | "country_name": "United Kingdom",
24 | "region": "Islington",
25 | "locality": "Clerkenwell"
26 | },
27 | "formatted": "Clerkenwell, Islington, United Kingdom",
28 | "geometry": {
29 | "lat": "51.5221558691",
30 | "lng": "-0.100838524406"
31 | },
32 | "bounds": null
33 | }, {
34 | "formatted": "82, Lokku Ltd, Clerkenwell Road, Clerkenwell, London Borough of Islington, London, EC1M 5RF, Greater London, England, United Kingdom, gb",
35 | "components": {
36 | "county": "London",
37 | "state_district": "Greater London",
38 | "road": "Clerkenwell Road",
39 | "country_code": "gb",
40 | "house_number": "82",
41 | "country": "United Kingdom",
42 | "city": "London Borough of Islington",
43 | "suburb": "Clerkenwell",
44 | "state": "England",
45 | "house": "Lokku Ltd",
46 | "postcode": "EC1M 5RF"
47 | },
48 | "annotations": {},
49 | "bounds": {
50 | "northeast": {
51 | "lng": "-0.1023889",
52 | "lat": "51.5226795"
53 | },
54 | "southwest": {
55 | "lat": "51.5225795",
56 | "lng": "-0.1024889"
57 | }
58 | },
59 | "geometry": {
60 | "lat": "51.5226295",
61 | "lng": "-0.1024389"
62 | }
63 | }, {
64 | "components": {
65 | "county": "London",
66 | "state_district": "Greater London",
67 | "road": "Clerkenwell Road",
68 | "country_code": "gb",
69 | "country": "United Kingdom",
70 | "city": "London Borough of Islington",
71 | "suburb": "Clerkenwell",
72 | "state": "England",
73 | "postcode": "EC1M 6DS"
74 | },
75 | "annotations": {},
76 | "formatted": "Clerkenwell Road, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb",
77 | "geometry": {
78 | "lat": "51.5225346",
79 | "lng": "-0.1027003"
80 | },
81 | "bounds": {
82 | "northeast": {
83 | "lat": "51.5225759",
84 | "lng": "-0.1020597"
85 | },
86 | "southwest": {
87 | "lat": "51.5225211",
88 | "lng": "-0.103223"
89 | }
90 | }
91 | }, {
92 | "formatted": "Clerkenwell Road, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb, Craft Central",
93 | "annotations": {},
94 | "components": {
95 | "postcode": "EC1M 6DS",
96 | "arts_centre": "Craft Central",
97 | "state": "England",
98 | "suburb": "Clerkenwell",
99 | "country": "United Kingdom",
100 | "city": "London Borough of Islington",
101 | "country_code": "gb",
102 | "road": "Clerkenwell Road",
103 | "state_district": "Greater London",
104 | "county": "London"
105 | },
106 | "bounds": {
107 | "northeast": {
108 | "lat": "51.52246",
109 | "lng": "-0.1027652"
110 | },
111 | "southwest": {
112 | "lng": "-0.1028652",
113 | "lat": "51.52236"
114 | }
115 | },
116 | "geometry": {
117 | "lng": "-0.1028152",
118 | "lat": "51.52241"
119 | }
120 | }, {
121 | "components": {
122 | "county": "London",
123 | "state_district": "Greater London",
124 | "restaurant": "Noodle Express",
125 | "road": "Albemarle Way",
126 | "country_code": "gb",
127 | "country": "United Kingdom",
128 | "city": "London Borough of Islington",
129 | "suburb": "Clerkenwell",
130 | "state": "England",
131 | "postcode": "EC1M 6DS"
132 | },
133 | "annotations": {},
134 | "formatted": "Noodle Express, Albemarle Way, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb",
135 | "geometry": {
136 | "lng": "-0.10255386845056",
137 | "lat": "51.5228195"
138 | },
139 | "bounds": {
140 | "southwest": {
141 | "lng": "-0.102621",
142 | "lat": "51.5227781"
143 | },
144 | "northeast": {
145 | "lat": "51.5228603",
146 | "lng": "-0.1024869"
147 | }
148 | }
149 | }, {
150 | "geometry": {
151 | "lat": "51.5229424",
152 | "lng": "-0.102380530769224"
153 | },
154 | "bounds": {
155 | "northeast": {
156 | "lat": "51.5229759",
157 | "lng": "-0.1023064"
158 | },
159 | "southwest": {
160 | "lng": "-0.1024639",
161 | "lat": "51.5229046"
162 | }
163 | },
164 | "annotations": {},
165 | "components": {
166 | "county": "London",
167 | "state_district": "Greater London",
168 | "road": "Albemarle Way",
169 | "country_code": "gb",
170 | "cafe": "PAR",
171 | "country": "United Kingdom",
172 | "city": "London Borough of Islington",
173 | "suburb": "Clerkenwell",
174 | "state": "England",
175 | "postcode": "EC1M 6DS"
176 | },
177 | "formatted": "PAR, Albemarle Way, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb"
178 | }, {
179 | "formatted": "Workshop Coffee Co., 27, Clerkenwell Road, Clerkenwell, London Borough of Islington, London, EC1M 5RN, Greater London, England, United Kingdom, gb",
180 | "components": {
181 | "county": "London",
182 | "state_district": "Greater London",
183 | "road": "Clerkenwell Road",
184 | "country_code": "gb",
185 | "house_number": "27",
186 | "cafe": "Workshop Coffee Co.",
187 | "country": "United Kingdom",
188 | "city": "London Borough of Islington",
189 | "suburb": "Clerkenwell",
190 | "state": "England",
191 | "postcode": "EC1M 5RN"
192 | },
193 | "annotations": {},
194 | "bounds": {
195 | "southwest": {
196 | "lng": "-0.1024422",
197 | "lat": "51.5222246"
198 | },
199 | "northeast": {
200 | "lng": "-0.1022307",
201 | "lat": "51.5224408"
202 | }
203 | },
204 | "geometry": {
205 | "lat": "51.52234585",
206 | "lng": "-0.102338899572156"
207 | }
208 | }, {
209 | "components": {
210 | "county": "London",
211 | "state_district": "Greater London",
212 | "road": "St. John Street",
213 | "country_code": "gb",
214 | "country": "United Kingdom",
215 | "city": "London Borough of Islington",
216 | "suburb": "Clerkenwell",
217 | "hairdresser": "Franco & Co",
218 | "state": "England",
219 | "postcode": "EC1M 6DS"
220 | },
221 | "annotations": {},
222 | "formatted": "St. John Street, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb, Franco & Co",
223 | "geometry": {
224 | "lng": "-0.1024118",
225 | "lat": "51.5231165"
226 | },
227 | "bounds": {
228 | "southwest": {
229 | "lng": "-0.1024618",
230 | "lat": "51.5230665"
231 | },
232 | "northeast": {
233 | "lng": "-0.1023618",
234 | "lat": "51.5231665"
235 | }
236 | }
237 | }, {
238 | "bounds": {
239 | "northeast": {
240 | "lng": "-0.1023218",
241 | "lat": "51.5231688"
242 | },
243 | "southwest": {
244 | "lat": "51.5229634",
245 | "lng": "-0.1024934"
246 | }
247 | },
248 | "geometry": {
249 | "lng": "-0.102399365567707",
250 | "lat": "51.5230257"
251 | },
252 | "formatted": "St. John Street, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb, MacCarthy",
253 | "annotations": {},
254 | "components": {
255 | "county": "London",
256 | "state_district": "Greater London",
257 | "road": "St. John Street",
258 | "country_code": "gb",
259 | "country": "United Kingdom",
260 | "city": "London Borough of Islington",
261 | "suburb": "Clerkenwell",
262 | "hairdresser": "MacCarthy",
263 | "state": "England",
264 | "postcode": "EC1M 6DS"
265 | }
266 | }, {
267 | "geometry": {
268 | "lng": "-0.102730855172415",
269 | "lat": "51.52267345"
270 | },
271 | "bounds": {
272 | "northeast": {
273 | "lng": "-0.1025498",
274 | "lat": "51.5227315"
275 | },
276 | "southwest": {
277 | "lat": "51.5226068",
278 | "lng": "-0.1028931"
279 | }
280 | },
281 | "annotations": {},
282 | "components": {
283 | "county": "London",
284 | "state_district": "Greater London",
285 | "road": "Albemarle Way",
286 | "country_code": "gb",
287 | "house_number": "84",
288 | "country": "United Kingdom",
289 | "city": "London Borough of Islington",
290 | "suburb": "Clerkenwell",
291 | "state": "England",
292 | "house": "The Printworks",
293 | "postcode": "EC1M 6DS"
294 | },
295 | "formatted": "84, The Printworks, Albemarle Way, Clerkenwell, London Borough of Islington, London, EC1M 6DS, Greater London, England, United Kingdom, gb"
296 | }],
297 | "timestamp": {
298 | "created_unix": 1402133768,
299 | "created_http": "Sat, 07 Jun 2014 09:36:08 GMT"
300 | }
301 | }
--------------------------------------------------------------------------------
/opencage/batch.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import ssl
3 | import asyncio
4 | import traceback
5 | import threading
6 | import random
7 | import json
8 |
9 | from contextlib import suppress
10 | from urllib.parse import urlencode
11 | from tqdm import tqdm
12 | import certifi
13 | import backoff
14 | from opencage.geocoder import (
15 | OpenCageGeocode,
16 | OpenCageGeocodeError,
17 | _query_for_reverse_geocoding,
18 | floatify_latlng
19 | )
20 |
21 |
22 | class OpenCageBatchGeocoder():
23 |
24 | """ Called from command_line.py
25 | init() receives the parsed command line parameters
26 | geocode() receive an input and output CSV reader/writer and loops over the data
27 | """
28 |
29 | def __init__(self, options):
30 | self.options = options
31 | self.sslcontext = ssl.create_default_context(cafile=certifi.where())
32 | self.user_agent_comment = 'OpenCage CLI'
33 | self.write_counter = 1
34 |
35 | def __call__(self, *args, **kwargs):
36 | asyncio.run(self.geocode(*args, **kwargs))
37 |
38 | async def geocode(self, csv_input, csv_output):
39 | if not self.options.dry_run:
40 | test = await self.test_request()
41 | if test['error']:
42 | self.log(test['error'])
43 | return
44 | if test['free'] is True and self.options.workers > 1:
45 | sys.stderr.write("Free trial account detected. Resetting number of workers to 1.\n")
46 | self.options.workers = 1
47 |
48 | if self.options.headers:
49 | header_columns = next(csv_input, None)
50 | if header_columns is None:
51 | return
52 |
53 | queue = asyncio.Queue(maxsize=self.options.limit)
54 |
55 | read_warnings = await self.read_input(csv_input, queue)
56 |
57 | if self.options.dry_run:
58 | if not read_warnings:
59 | print('All good.')
60 | return
61 |
62 | if self.options.headers:
63 | csv_output.writerow(header_columns + self.options.add_columns)
64 |
65 | progress_bar = not (self.options.no_progress or self.options.quiet) and \
66 | tqdm(total=queue.qsize(), position=0, desc="Addresses geocoded", dynamic_ncols=True)
67 |
68 | tasks = []
69 | for _ in range(self.options.workers):
70 | task = asyncio.create_task(self.worker(csv_output, queue, progress_bar))
71 | tasks.append(task)
72 |
73 | # This starts the workers and waits until all are finished
74 | await queue.join()
75 |
76 | # All tasks done
77 | for task in tasks:
78 | task.cancel()
79 |
80 | if progress_bar:
81 | progress_bar.close()
82 |
83 | async def test_request(self):
84 | try:
85 | async with OpenCageGeocode(
86 | self.options.api_key,
87 | domain=self.options.api_domain,
88 | sslcontext=self.sslcontext,
89 | user_agent_comment=self.user_agent_comment
90 | ) as geocoder:
91 | result = await geocoder.geocode_async('Kendall Sq, Cambridge, MA', raw_response=True)
92 |
93 | free = False
94 | with suppress(KeyError):
95 | free = result['rate']['limit'] == 2500
96 |
97 | return {'error': None, 'free': free}
98 | except Exception as exc:
99 | return {'error': exc}
100 |
101 | async def read_input(self, csv_input, queue):
102 | any_warnings = False
103 | for index, row in enumerate(csv_input):
104 | line_number = index + 1
105 |
106 | if len(row) == 0:
107 | self.log(f"Line {line_number} - Empty line")
108 | any_warnings = True
109 | row = ['']
110 |
111 | item = await self.read_one_line(row, line_number)
112 | if item['warnings'] is True:
113 | any_warnings = True
114 | await queue.put(item)
115 |
116 | if queue.full():
117 | break
118 |
119 | return any_warnings
120 |
121 | async def read_one_line(self, row, row_id):
122 | warnings = False
123 |
124 | if self.options.input_columns:
125 | input_columns = self.options.input_columns
126 | elif self.options.command == 'reverse':
127 | input_columns = [1, 2]
128 | else:
129 | input_columns = None
130 |
131 | if input_columns:
132 | address = []
133 | try:
134 | for column in input_columns:
135 | # input_columns option uses 1-based indexing
136 | address.append(row[column - 1])
137 | except IndexError:
138 | self.log(f"Line {row_id} - Missing input column {column} in {row}")
139 | warnings = True
140 | else:
141 | address = row
142 |
143 | if self.options.command == 'reverse':
144 |
145 | if len(address) != 2:
146 | self.log(
147 | f"Line {row_id} - Expected two comma-separated values for reverse geocoding, got {address}")
148 | else:
149 | # _query_for_reverse_geocoding attempts to convert into numbers. We rather have it fail
150 | # now than during the actual geocoding
151 | try:
152 | _query_for_reverse_geocoding(address[0], address[1])
153 | except BaseException:
154 | self.log(
155 | f"Line {row_id} - Does not look like latitude and longitude: '{address[0]}' and '{address[1]}'")
156 | warnings = True
157 | address = []
158 |
159 | return {'row_id': row_id, 'address': ','.join(address), 'original_columns': row, 'warnings': warnings}
160 |
161 | async def worker(self, csv_output, queue, progress):
162 | while True:
163 | item = await queue.get()
164 |
165 | try:
166 | await self.geocode_one_address(csv_output, item['row_id'], item['address'], item['original_columns'])
167 |
168 | if progress:
169 | progress.update(1)
170 | except Exception as exc:
171 | traceback.print_exception(exc, file=sys.stderr)
172 | finally:
173 | queue.task_done()
174 |
175 | async def geocode_one_address(self, csv_output, row_id, address, original_columns):
176 | def on_backoff(details):
177 | if not self.options.quiet:
178 | sys.stderr.write("Backing off {wait:0.1f} seconds afters {tries} tries "
179 | "calling function {target} with args {args} and kwargs "
180 | "{kwargs}\n".format(**details))
181 |
182 | @backoff.on_exception(backoff.expo,
183 | asyncio.TimeoutError,
184 | max_time=self.options.timeout,
185 | max_tries=self.options.retries,
186 | on_backoff=on_backoff)
187 | async def _geocode_one_address():
188 | async with OpenCageGeocode(
189 | self.options.api_key,
190 | domain=self.options.api_domain,
191 | sslcontext=self.sslcontext,
192 | user_agent_comment=self.user_agent_comment
193 | ) as geocoder:
194 | geocoding_results = None
195 | response = None
196 | params = {'no_annotations': 1, 'raw_response': True, **self.options.optional_api_params}
197 |
198 | try:
199 | if self.options.command == 'reverse':
200 | if ',' in address:
201 | lon, lat = address.split(',')
202 | response = await geocoder.reverse_geocode_async(lon, lat, **params)
203 | geocoding_results = floatify_latlng(response['results'])
204 | else:
205 | response = await geocoder.geocode_async(address, **params)
206 | geocoding_results = floatify_latlng(response['results'])
207 | except OpenCageGeocodeError as exc:
208 | self.log(str(exc))
209 | except Exception as exc:
210 | traceback.print_exception(exc, file=sys.stderr)
211 |
212 | try:
213 | if geocoding_results is not None and len(geocoding_results):
214 | geocoding_result = geocoding_results[0]
215 | else:
216 | geocoding_result = None
217 |
218 | if self.options.verbose:
219 | self.log({
220 | 'row_id': row_id,
221 | 'thread_id': threading.get_native_id(),
222 | 'request': geocoder.url + '?' + urlencode(geocoder._parse_request(address, params)),
223 | 'response': response
224 | })
225 |
226 | await self.write_one_geocoding_result(
227 | csv_output,
228 | row_id,
229 | geocoding_result,
230 | response,
231 | original_columns
232 | )
233 | except Exception as exc:
234 | traceback.print_exception(exc, file=sys.stderr)
235 |
236 | await _geocode_one_address()
237 |
238 | async def write_one_geocoding_result(
239 | self,
240 | csv_output,
241 | row_id,
242 | geocoding_result,
243 | raw_response,
244 | original_columns):
245 | row = original_columns
246 |
247 | for column in self.options.add_columns:
248 | if column == 'status':
249 | row.append(self.deep_get_result_value(raw_response, ['status', 'message']))
250 | elif geocoding_result is None:
251 | row.append('')
252 | elif column in geocoding_result:
253 | row.append(self.deep_get_result_value(geocoding_result, [column], ''))
254 | elif column in geocoding_result['components']:
255 | row.append(self.deep_get_result_value(geocoding_result, ['components', column], ''))
256 | elif column in geocoding_result['geometry']:
257 | row.append(self.deep_get_result_value(geocoding_result, ['geometry', column], ''))
258 | elif column == 'FIPS':
259 | row.append(
260 | self.deep_get_result_value(
261 | geocoding_result, [
262 | 'annotations', 'FIPS', 'county'], ''))
263 | elif column == 'json':
264 | row.append(json.dumps(geocoding_result, separators=(',', ':'))) # Compact JSON
265 | else:
266 | row.append('')
267 |
268 | # Enforce that row are written ordered. That means we might wait for other threads
269 | # to finish a task and make the overall process slower. Alternative would be to
270 | # use a second queue, or keep some results in memory.
271 | if not self.options.unordered:
272 | while row_id > self.write_counter:
273 | if self.options.verbose:
274 | self.log(f"Want to write row {row_id}, but write_counter is at {self.write_counter}")
275 | await asyncio.sleep(random.uniform(0.01, 0.1))
276 |
277 | if self.options.verbose:
278 | self.log(f"Writing row {row_id}")
279 | csv_output.writerow(row)
280 | self.write_counter = self.write_counter + 1
281 |
282 | def log(self, message):
283 | if not self.options.quiet:
284 | sys.stderr.write(f"{message}\n")
285 |
286 | def deep_get_result_value(self, data, keys, default=None):
287 | for key in keys:
288 | if isinstance(data, dict):
289 | data = data.get(key, default)
290 | else:
291 | return default
292 | return data
293 |
--------------------------------------------------------------------------------
/opencage/geocoder.py:
--------------------------------------------------------------------------------
1 | """ Geocoder module. """
2 |
3 | from decimal import Decimal
4 | import collections
5 |
6 | import os
7 | import sys
8 | import requests
9 | import backoff
10 | from .version import __version__
11 |
12 | try:
13 | import aiohttp
14 | AIOHTTP_AVAILABLE = True
15 | except ImportError:
16 | AIOHTTP_AVAILABLE = False
17 |
18 | DEFAULT_DOMAIN = 'api.opencagedata.com'
19 |
20 |
21 | def backoff_max_time():
22 | return int(os.environ.get('BACKOFF_MAX_TIME', '120'))
23 |
24 |
25 | class OpenCageGeocodeError(Exception):
26 |
27 | """Base class for all errors/exceptions that can happen when geocoding."""
28 |
29 |
30 | class InvalidInputError(OpenCageGeocodeError):
31 |
32 | """
33 | There was a problem with the input you provided.
34 |
35 | :var message: Error message describing the bad input.
36 | :var bad_value: The value that caused the problem.
37 | """
38 |
39 | def __init__(self, message, bad_value=None):
40 | super().__init__()
41 | self.message = message
42 | self.bad_value = bad_value
43 |
44 | def __unicode__(self):
45 | return self.message
46 |
47 | __str__ = __unicode__
48 |
49 |
50 | class UnknownError(OpenCageGeocodeError):
51 |
52 | """There was a problem with the OpenCage server."""
53 |
54 |
55 | class RateLimitExceededError(OpenCageGeocodeError):
56 |
57 | """
58 | Exception raised when account has exceeded it's limit.
59 | """
60 |
61 | def __unicode__(self):
62 | """Convert exception to a string."""
63 | return ("You have used the requests available on your plan. "
64 | "Please purchase more if you wish to continue: https://opencagedata.com/pricing")
65 |
66 | __str__ = __unicode__
67 |
68 |
69 | class NotAuthorizedError(OpenCageGeocodeError):
70 |
71 | """
72 | Exception raised when an unautorized API key is used.
73 | """
74 |
75 | def __unicode__(self):
76 | """Convert exception to a string."""
77 | return "Your API key is not authorized. You may have entered it incorrectly."
78 |
79 | __str__ = __unicode__
80 |
81 |
82 | class ForbiddenError(OpenCageGeocodeError):
83 |
84 | """
85 | Exception raised when a blocked or suspended API key is used.
86 | """
87 |
88 | def __unicode__(self):
89 | """Convert exception to a string."""
90 | return "Your API key has been blocked or suspended."
91 |
92 | __str__ = __unicode__
93 |
94 |
95 | class AioHttpError(OpenCageGeocodeError):
96 |
97 | """
98 | Exceptions related to async HTTP calls with aiohttp
99 | """
100 |
101 |
102 | class SSLError(OpenCageGeocodeError):
103 |
104 | """
105 | Exception raised when SSL connection to OpenCage server fails.
106 | """
107 |
108 | def __unicode__(self):
109 | """Convert exception to a string."""
110 | return ("SSL Certificate error connecting to OpenCage API. This is usually due to "
111 | "outdated CA root certificates of the operating system. "
112 | )
113 |
114 | __str__ = __unicode__
115 |
116 |
117 | class OpenCageGeocode:
118 |
119 | """
120 | Geocoder object.
121 |
122 | Initialize it with your API key:
123 |
124 | >>> geocoder = OpenCageGeocode('your-key-here')
125 |
126 | Query:
127 |
128 | >>> geocoder.geocode("London")
129 |
130 | Reverse geocode a latitude & longitude into a place:
131 |
132 | >>> geocoder.reverse_geocode(51.5104, -0.1021)
133 |
134 | """
135 |
136 | session = None
137 |
138 | def __init__(
139 | self,
140 | key=None,
141 | protocol='https',
142 | domain=DEFAULT_DOMAIN,
143 | sslcontext=None,
144 | user_agent_comment=None):
145 | """Constructor."""
146 | self.key = key if key is not None else os.environ.get('OPENCAGE_API_KEY')
147 |
148 | if self.key is None:
149 | raise ValueError(
150 | "API key not provided. "
151 | "Either pass a 'key' parameter or set the OPENCAGE_API_KEY environment variable."
152 | )
153 |
154 | if protocol and protocol not in ('http', 'https'):
155 | protocol = 'https'
156 | self.url = protocol + '://' + domain + '/geocode/v1/json'
157 |
158 | # https://docs.aiohttp.org/en/stable/client_advanced.html#ssl-control-for-tcp-sockets
159 | self.sslcontext = sslcontext
160 |
161 | self.user_agent_comment = user_agent_comment
162 |
163 | def __enter__(self):
164 | self.session = requests.Session()
165 | return self
166 |
167 | def __exit__(self, *args):
168 | self.session.close()
169 | self.session = None
170 | return False
171 |
172 | async def __aenter__(self):
173 | if not AIOHTTP_AVAILABLE:
174 | raise AioHttpError("You must install `aiohttp` to use async methods")
175 |
176 | self.session = aiohttp.ClientSession()
177 | return self
178 |
179 | async def __aexit__(self, *args):
180 | await self.session.close()
181 | self.session = None
182 | return False
183 |
184 | def geocode(self, query, **kwargs):
185 | """
186 | Given a string to search for, return the list (array) of results from OpenCage's Geocoder.
187 |
188 | :param string query: String to search for
189 |
190 | :returns: Dict results
191 | :raises InvalidInputError: if the query string is not a unicode string
192 | :raises RateLimitExceededError: if you have exceeded the number of queries you can make.
193 | : Exception says when you can try again
194 | :raises UnknownError: if something goes wrong with the OpenCage API
195 |
196 | """
197 |
198 | if self.session and isinstance(self.session, aiohttp.client.ClientSession):
199 | raise AioHttpError("Cannot use `geocode` in an async context, use `geocode_async`.")
200 |
201 | raw_response = kwargs.pop('raw_response', False)
202 | request = self._parse_request(query, kwargs)
203 | response = self._opencage_request(request)
204 |
205 | if raw_response:
206 | return response
207 |
208 | return floatify_latlng(response['results'])
209 |
210 | async def geocode_async(self, query, **kwargs):
211 | """
212 | Aync version of `geocode`.
213 |
214 | Given a string to search for, return the list (array) of results from OpenCage's Geocoder.
215 |
216 | :param string query: String to search for
217 |
218 | :returns: Dict results
219 | :raises InvalidInputError: if the query string is not a unicode string
220 | :raises RateLimitExceededError: if exceeded number of queries you can make. You can try again
221 | :raises UnknownError: if something goes wrong with the OpenCage API
222 |
223 | """
224 |
225 | if not AIOHTTP_AVAILABLE:
226 | raise AioHttpError("You must install `aiohttp` to use async methods.")
227 |
228 | if not self.session:
229 | raise AioHttpError("Async methods must be used inside an async context.")
230 |
231 | if not isinstance(self.session, aiohttp.client.ClientSession):
232 | raise AioHttpError("You must use `geocode_async` in an async context.")
233 |
234 | raw_response = kwargs.pop('raw_response', False)
235 | request = self._parse_request(query, kwargs)
236 | response = await self._opencage_async_request(request)
237 |
238 | if raw_response:
239 | return response
240 |
241 | return floatify_latlng(response['results'])
242 |
243 | def reverse_geocode(self, lat, lng, **kwargs):
244 | """
245 | Given a latitude & longitude, return an address for that point from OpenCage's Geocoder.
246 |
247 | :param lat: Latitude
248 | :param lng: Longitude
249 | :return: Results from OpenCageData
250 | :rtype: dict
251 | :raises RateLimitExceededError: if you have exceeded the number of queries you can make.
252 | : Exception says when you can try again
253 | :raises UnknownError: if something goes wrong with the OpenCage API
254 | :raises InvalidInputError: if the latitude or longitude is out of bounds
255 | """
256 |
257 | self._validate_lat_lng(lat, lng)
258 |
259 | return self.geocode(_query_for_reverse_geocoding(lat, lng), **kwargs)
260 |
261 | async def reverse_geocode_async(self, lat, lng, **kwargs):
262 | """
263 | Aync version of `reverse_geocode`.
264 |
265 | Given a latitude & longitude, return an address for that point from OpenCage's Geocoder.
266 |
267 | :param lat: Latitude
268 | :param lng: Longitude
269 | :return: Results from OpenCageData
270 | :rtype: dict
271 | :raises RateLimitExceededError: if exceeded number of queries you can make. You can try again
272 | :raises UnknownError: if something goes wrong with the OpenCage API
273 | :raises InvalidInputError: if the latitude or longitude is out of bounds
274 | """
275 |
276 | self._validate_lat_lng(lat, lng)
277 |
278 | return await self.geocode_async(_query_for_reverse_geocoding(lat, lng), **kwargs)
279 |
280 | @backoff.on_exception(
281 | backoff.expo,
282 | (UnknownError, requests.exceptions.RequestException),
283 | max_tries=5, max_time=backoff_max_time)
284 | def _opencage_request(self, params):
285 |
286 | if self.session:
287 | response = self.session.get(self.url, params=params, headers=self._opencage_headers('aiohttp'))
288 | else:
289 | response = requests.get(self.url, params=params, headers=self._opencage_headers('requests'))
290 |
291 | try:
292 | response_json = response.json()
293 | except ValueError as excinfo:
294 | raise UnknownError("Non-JSON result from server") from excinfo
295 |
296 | if response.status_code == 401:
297 | raise NotAuthorizedError()
298 |
299 | if response.status_code == 403:
300 | raise ForbiddenError()
301 |
302 | if response.status_code in (402, 429):
303 | raise RateLimitExceededError()
304 |
305 | if response.status_code == 500:
306 | raise UnknownError("500 status code from API")
307 |
308 | if 'results' not in response_json:
309 | raise UnknownError("JSON from API doesn't have a 'results' key")
310 |
311 | return response_json
312 |
313 | def _opencage_headers(self, client):
314 | client_version = requests.__version__
315 | if client == 'aiohttp':
316 | client_version = aiohttp.__version__
317 |
318 | py_version = '.'.join(str(x) for x in sys.version_info[0:3])
319 |
320 | comment = ''
321 | if self.user_agent_comment:
322 | comment = f" ({self.user_agent_comment})"
323 |
324 | return {
325 | 'User-Agent': f"opencage-python/{__version__} Python/{py_version} {client}/{client_version}{comment}"
326 | }
327 |
328 | async def _opencage_async_request(self, params):
329 | try:
330 | async with self.session.get(self.url, params=params, ssl=self.sslcontext) as response:
331 | try:
332 | response_json = await response.json()
333 | except ValueError as excinfo:
334 | raise UnknownError("Non-JSON result from server") from excinfo
335 |
336 | if response.status == 401:
337 | raise NotAuthorizedError()
338 |
339 | if response.status == 403:
340 | raise ForbiddenError()
341 |
342 | if response.status in (402, 429):
343 | raise RateLimitExceededError()
344 |
345 | if response.status == 500:
346 | raise UnknownError("500 status code from API")
347 |
348 | if 'results' not in response_json:
349 | raise UnknownError("JSON from API doesn't have a 'results' key")
350 |
351 | return response_json
352 | except aiohttp.ClientSSLError as exp:
353 | raise SSLError() from exp
354 | except aiohttp.client_exceptions.ClientConnectorCertificateError as exp:
355 | raise SSLError() from exp
356 |
357 | def _parse_request(self, query, params):
358 | if not isinstance(query, str):
359 | error_message = "Input must be a unicode string, not " + repr(query)[:100]
360 | raise InvalidInputError(error_message, bad_value=query)
361 |
362 | data = {'q': query, 'key': self.key}
363 | data.update(params) # Add user parameters
364 | return data
365 |
366 | def _validate_lat_lng(self, lat, lng):
367 | """
368 | Validate latitude and longitude values.
369 |
370 | Raises InvalidInputError if the values are out of bounds.
371 | """
372 | try:
373 | lat_float = float(lat)
374 | if not -90 <= lat_float <= 90:
375 | raise InvalidInputError(f"Latitude must be a number between -90 and 90, not {lat}", bad_value=lat)
376 | except ValueError:
377 | raise InvalidInputError(f"Latitude must be a number between -90 and 90, not {lat}", bad_value=lat)
378 |
379 | try:
380 | lng_float = float(lng)
381 | if not -180 <= lng_float <= 180:
382 | raise InvalidInputError(f"Longitude must be a number between -180 and 180, not {lng}", bad_value=lng)
383 | except ValueError:
384 | raise InvalidInputError(f"Longitude must be a number between -180 and 180, not {lng}", bad_value=lng)
385 |
386 |
387 | def _query_for_reverse_geocoding(lat, lng):
388 | """
389 | Given a lat & lng, what's the string search query.
390 |
391 | If the API changes, change this function. Only for internal use.
392 | """
393 | # have to do some stupid f/Decimal/str stuff to (a) ensure we get as much
394 | # decimal places as the user already specified and (b) to ensure we don't
395 | # get e-5 stuff
396 | return f"{Decimal(str(lat)):f},{Decimal(str(lng)):f}"
397 |
398 |
399 | def float_if_float(float_string):
400 | """
401 | Given a float string, returns the float value.
402 | On value error returns the original string.
403 | """
404 | try:
405 | float_val = float(float_string)
406 | return float_val
407 | except ValueError:
408 | return float_string
409 |
410 |
411 | def floatify_latlng(input_value):
412 | """
413 | Work around a JSON dict with string, not float, lat/lngs.
414 |
415 | Given anything (list/dict/etc) it will return that thing again, *but* any
416 | dict (at any level) that has only 2 elements lat & lng, will be replaced
417 | with the lat & lng turned into floats.
418 |
419 | If the API returns the lat/lng as strings, and not numbers, then this
420 | function will 'clean them up' to be floats.
421 | """
422 | if isinstance(input_value, collections.abc.Mapping):
423 | if len(input_value) == 2 and sorted(input_value.keys()) == ['lat', 'lng']:
424 | # This dict has only 2 keys 'lat' & 'lon'
425 | return {'lat': float_if_float(input_value["lat"]), 'lng': float_if_float(input_value["lng"])}
426 |
427 | return dict((key, floatify_latlng(value)) for key, value in input_value.items())
428 |
429 | if isinstance(input_value, collections.abc.MutableSequence):
430 | return [floatify_latlng(x) for x in input_value]
431 |
432 | return input_value
433 |
--------------------------------------------------------------------------------
/examples/addresses.csv:
--------------------------------------------------------------------------------
1 | id, address
2 | 1, "Via Allende 8, Cascina, Toscana, Italia"
3 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
4 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
5 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
6 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
7 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
8 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
9 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
10 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
11 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
12 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
13 | 1, "Via Allende 8, Cascina, Toscana, Italia"
14 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
15 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
16 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
17 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
18 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
19 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
20 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
21 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
22 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
23 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
24 | 1, "Via Allende 8, Cascina, Toscana, Italia"
25 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
26 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
27 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
28 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
29 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
30 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
31 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
32 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
33 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
34 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
35 | 1, "Via Allende 8, Cascina, Toscana, Italia"
36 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
37 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
38 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
39 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
40 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
41 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
42 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
43 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
44 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
45 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
46 | 1, "Via Allende 8, Cascina, Toscana, Italia"
47 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
48 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
49 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
50 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
51 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
52 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
53 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
54 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
55 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
56 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
57 | 1, "Via Allende 8, Cascina, Toscana, Italia"
58 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
59 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
60 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
61 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
62 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
63 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
64 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
65 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
66 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
67 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
68 | 1, "Via Allende 8, Cascina, Toscana, Italia"
69 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
70 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
71 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
72 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
73 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
74 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
75 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
76 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
77 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
78 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
79 | 1, "Via Allende 8, Cascina, Toscana, Italia"
80 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
81 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
82 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
83 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
84 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
85 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
86 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
87 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
88 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
89 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
90 | 1, "Via Allende 8, Cascina, Toscana, Italia"
91 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
92 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
93 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
94 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
95 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
96 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
97 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
98 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
99 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
100 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
101 | 1, "Via Allende 8, Cascina, Toscana, Italia"
102 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
103 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
104 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
105 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
106 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
107 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
108 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
109 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
110 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
111 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
112 | 1, "Via Allende 8, Cascina, Toscana, Italia"
113 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
114 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
115 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
116 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
117 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
118 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
119 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
120 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
121 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
122 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
123 | 1, "Via Allende 8, Cascina, Toscana, Italia"
124 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
125 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
126 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
127 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
128 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
129 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
130 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
131 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
132 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
133 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
134 | 1, "Via Allende 8, Cascina, Toscana, Italia"
135 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
136 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
137 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
138 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
139 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
140 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
141 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
142 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
143 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
144 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
145 | 1, "Via Allende 8, Cascina, Toscana, Italia"
146 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
147 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
148 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
149 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
150 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
151 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
152 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
153 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
154 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
155 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
156 | 1, "Via Allende 8, Cascina, Toscana, Italia"
157 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
158 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
159 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
160 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
161 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
162 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
163 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
164 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
165 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
166 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
167 | 1, "Via Allende 8, Cascina, Toscana, Italia"
168 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
169 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
170 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
171 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
172 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
173 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
174 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
175 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
176 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
177 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
178 | 1, "Via Allende 8, Cascina, Toscana, Italia"
179 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
180 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
181 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
182 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
183 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
184 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
185 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
186 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
187 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
188 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
189 | 1, "Via Allende 8, Cascina, Toscana, Italia"
190 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
191 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
192 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
193 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
194 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
195 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
196 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
197 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
198 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
199 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
200 | 1, "Via Allende 8, Cascina, Toscana, Italia"
201 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
202 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
203 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
204 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
205 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
206 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
207 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
208 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
209 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
210 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
211 | 1, "Via Allende 8, Cascina, Toscana, Italia"
212 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
213 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
214 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
215 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
216 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
217 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
218 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
219 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
220 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
221 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
222 | 1, "Via Allende 8, Cascina, Toscana, Italia"
223 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
224 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
225 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
226 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
227 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
228 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
229 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
230 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
231 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
232 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
233 | 1, "Via Allende 8, Cascina, Toscana, Italia"
234 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
235 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
236 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
237 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
238 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
239 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
240 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
241 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
242 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
243 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
244 | 1, "Via Allende 8, Cascina, Toscana, Italia"
245 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
246 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
247 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
248 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
249 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
250 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
251 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
252 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
253 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
254 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
255 | 1, "Via Allende 8, Cascina, Toscana, Italia"
256 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
257 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
258 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
259 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
260 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
261 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
262 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
263 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
264 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
265 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
266 | 1, "Via Allende 8, Cascina, Toscana, Italia"
267 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
268 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
269 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
270 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
271 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
272 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
273 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
274 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
275 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
276 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
277 | 1, "Via Allende 8, Cascina, Toscana, Italia"
278 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
279 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
280 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
281 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
282 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
283 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
284 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
285 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
286 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
287 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
288 | 1, "Via Allende 8, Cascina, Toscana, Italia"
289 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
290 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
291 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
292 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
293 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
294 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
295 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
296 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
297 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
298 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
299 | 1, "Via Allende 8, Cascina, Toscana, Italia"
300 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
301 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
302 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
303 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
304 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
305 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
306 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
307 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
308 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
309 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
310 | 1, "Via Allende 8, Cascina, Toscana, Italia"
311 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
312 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
313 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
314 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
315 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
316 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
317 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
318 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
319 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
320 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
321 | 1, "Via Allende 8, Cascina, Toscana, Italia"
322 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
323 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
324 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
325 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
326 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
327 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
328 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
329 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
330 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
331 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
332 | 1, "Via Allende 8, Cascina, Toscana, Italia"
333 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
334 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
335 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
336 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
337 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
338 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
339 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
340 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
341 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
342 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
343 | 1, "Via Allende 8, Cascina, Toscana, Italia"
344 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
345 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
346 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
347 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
348 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
349 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
350 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
351 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
352 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
353 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
354 | 1, "Via Allende 8, Cascina, Toscana, Italia"
355 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
356 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
357 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
358 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
359 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
360 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
361 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
362 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
363 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
364 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
365 | 1, "Via Allende 8, Cascina, Toscana, Italia"
366 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
367 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
368 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
369 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
370 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
371 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
372 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
373 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
374 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
375 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
376 | 1, "Via Allende 8, Cascina, Toscana, Italia"
377 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
378 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
379 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
380 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
381 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
382 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
383 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
384 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
385 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
386 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
387 | 1, "Via Allende 8, Cascina, Toscana, Italia"
388 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
389 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
390 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
391 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
392 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
393 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
394 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
395 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
396 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
397 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
398 | 1, "Via Allende 8, Cascina, Toscana, Italia"
399 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
400 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
401 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
402 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
403 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
404 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
405 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
406 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
407 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
408 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
409 | 1, "Via Allende 8, Cascina, Toscana, Italia"
410 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
411 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
412 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
413 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
414 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
415 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
416 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
417 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
418 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
419 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
420 | 1, "Via Allende 8, Cascina, Toscana, Italia"
421 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
422 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
423 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
424 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
425 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
426 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
427 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
428 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
429 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
430 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
431 | 1, "Via Allende 8, Cascina, Toscana, Italia"
432 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
433 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
434 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
435 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
436 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
437 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
438 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
439 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
440 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
441 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
442 | 1, "Via Allende 8, Cascina, Toscana, Italia"
443 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
444 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
445 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
446 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
447 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
448 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
449 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
450 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
451 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
452 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
453 | 1, "Via Allende 8, Cascina, Toscana, Italia"
454 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
455 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
456 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
457 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
458 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
459 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
460 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
461 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
462 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
463 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
464 | 1, "Via Allende 8, Cascina, Toscana, Italia"
465 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
466 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
467 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
468 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
469 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
470 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
471 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
472 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
473 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
474 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
475 | 1, "Via Allende 8, Cascina, Toscana, Italia"
476 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
477 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
478 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
479 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
480 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
481 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
482 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
483 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
484 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
485 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
486 | 1, "Via Allende 8, Cascina, Toscana, Italia"
487 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
488 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
489 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
490 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
491 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
492 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
493 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
494 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
495 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
496 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
497 | 1, "Via Allende 8, Cascina, Toscana, Italia"
498 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
499 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
500 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
501 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
502 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
503 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
504 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
505 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
506 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
507 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
508 | 1, "Via Allende 8, Cascina, Toscana, Italia"
509 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
510 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
511 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
512 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
513 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
514 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
515 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
516 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
517 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
518 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
519 | 1, "Via Allende 8, Cascina, Toscana, Italia"
520 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
521 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
522 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
523 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
524 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
525 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
526 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
527 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
528 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
529 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
530 | 1, "Via Allende 8, Cascina, Toscana, Italia"
531 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
532 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
533 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
534 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
535 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
536 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
537 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
538 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
539 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
540 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
541 | 1, "Via Allende 8, Cascina, Toscana, Italia"
542 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
543 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
544 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
545 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
546 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
547 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
548 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
549 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
550 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
551 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
552 | 1, "Via Allende 8, Cascina, Toscana, Italia"
553 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
554 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
555 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
556 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
557 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
558 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
559 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
560 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
561 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
562 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
563 | 1, "Via Allende 8, Cascina, Toscana, Italia"
564 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
565 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
566 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
567 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
568 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
569 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
570 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
571 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
572 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
573 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
574 | 1, "Via Allende 8, Cascina, Toscana, Italia"
575 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
576 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
577 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
578 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
579 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
580 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
581 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
582 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
583 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
584 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
585 | 1, "Via Allende 8, Cascina, Toscana, Italia"
586 | 2, "Via Coppi, 17, Formigine, Emilia-Romagna, Italia"
587 | 3, "Via Dei Salici 20, Gallarate, Lombardia, Italia"
588 | 4, "Via Vittorio Veneto N7, San Giuliano Terme, Toscana, Italia"
589 | 5, "Via Tiro A Segno 8, Gallarate, Lombardia, Italia"
590 | 6, "Urne Di Sopra 3, Bagnolo Mella, Lombardia, Italia"
591 | 7, "Via San Francesco D'assisi 84, Nichelino, Piemonte, Italia"
592 | 8, "Via Corletto, 10/A, Formigine, Emilia-Romagna, Italia"
593 | 9, "Via Beppe Fenoglio 17, Canale D'alba, Piemonte, Italia"
594 | 10, "Via 2 Giugno 19, Ponsacco, Toscana, Italia"
595 | 11, "Via Per Sassuolo, 74, Formigine, Emilia-Romagna, Italia"
596 |
--------------------------------------------------------------------------------