geobatch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- geobatch-0.1.0/CHANGELOG.md +34 -0
- geobatch-0.1.0/CITATION.cff +20 -0
- geobatch-0.1.0/LICENSE +21 -0
- geobatch-0.1.0/MANIFEST.in +9 -0
- geobatch-0.1.0/PKG-INFO +338 -0
- geobatch-0.1.0/README.md +301 -0
- geobatch-0.1.0/examples/landmarks.csv +6 -0
- geobatch-0.1.0/examples/quickstart.py +61 -0
- geobatch-0.1.0/pyproject.toml +116 -0
- geobatch-0.1.0/setup.cfg +4 -0
- geobatch-0.1.0/src/geobatch/__init__.py +97 -0
- geobatch-0.1.0/src/geobatch/__main__.py +7 -0
- geobatch-0.1.0/src/geobatch/_version.py +1 -0
- geobatch-0.1.0/src/geobatch/batch.py +526 -0
- geobatch-0.1.0/src/geobatch/cache.py +185 -0
- geobatch-0.1.0/src/geobatch/cli.py +367 -0
- geobatch-0.1.0/src/geobatch/errors.py +52 -0
- geobatch-0.1.0/src/geobatch/http.py +201 -0
- geobatch-0.1.0/src/geobatch/io.py +320 -0
- geobatch-0.1.0/src/geobatch/models.py +182 -0
- geobatch-0.1.0/src/geobatch/normalize.py +73 -0
- geobatch-0.1.0/src/geobatch/pandas_ext.py +95 -0
- geobatch-0.1.0/src/geobatch/progress.py +95 -0
- geobatch-0.1.0/src/geobatch/providers/__init__.py +61 -0
- geobatch-0.1.0/src/geobatch/providers/base.py +291 -0
- geobatch-0.1.0/src/geobatch/providers/commercial.py +514 -0
- geobatch-0.1.0/src/geobatch/providers/nominatim.py +270 -0
- geobatch-0.1.0/src/geobatch/providers/pelias.py +156 -0
- geobatch-0.1.0/src/geobatch/providers/photon.py +148 -0
- geobatch-0.1.0/src/geobatch/providers/template.py +228 -0
- geobatch-0.1.0/src/geobatch/py.typed +0 -0
- geobatch-0.1.0/src/geobatch/quality.py +120 -0
- geobatch-0.1.0/src/geobatch/ratelimit.py +90 -0
- geobatch-0.1.0/src/geobatch.egg-info/PKG-INFO +338 -0
- geobatch-0.1.0/src/geobatch.egg-info/SOURCES.txt +47 -0
- geobatch-0.1.0/src/geobatch.egg-info/dependency_links.txt +1 -0
- geobatch-0.1.0/src/geobatch.egg-info/entry_points.txt +2 -0
- geobatch-0.1.0/src/geobatch.egg-info/requires.txt +9 -0
- geobatch-0.1.0/src/geobatch.egg-info/top_level.txt +1 -0
- geobatch-0.1.0/tests/conftest.py +60 -0
- geobatch-0.1.0/tests/mockserver.py +448 -0
- geobatch-0.1.0/tests/test_batch.py +332 -0
- geobatch-0.1.0/tests/test_cli.py +337 -0
- geobatch-0.1.0/tests/test_core.py +227 -0
- geobatch-0.1.0/tests/test_http.py +128 -0
- geobatch-0.1.0/tests/test_io.py +151 -0
- geobatch-0.1.0/tests/test_pandas.py +37 -0
- geobatch-0.1.0/tests/test_providers.py +445 -0
- geobatch-0.1.0/tests/test_ratelimit.py +103 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-09
|
|
8
|
+
### Added
|
|
9
|
+
- `Geocoder` batch orchestrator: de-duplication of normalised queries, thread pool with
|
|
10
|
+
per-provider token-bucket rate limits and concurrency limits, provider fallback chain
|
|
11
|
+
(on errors and, optionally, on "not found"), circuit breaker after consecutive failures,
|
|
12
|
+
`BatchInterrupted` with the partial result on Ctrl+C.
|
|
13
|
+
- Persistent SQLite cache keyed by provider endpoint, query kind, normalised query and
|
|
14
|
+
options; caches found and not-found answers, never errors; resume by re-running;
|
|
15
|
+
per-provider TTL and "do not store" rules.
|
|
16
|
+
- HTTP transport on `urllib` with timeouts, exponential backoff with jitter, `Retry-After`
|
|
17
|
+
(seconds or HTTP date) shared across threads, `max_retry_after` guard, and redaction of
|
|
18
|
+
API keys from every message.
|
|
19
|
+
- Providers: Nominatim (OSMF usage policy enforced on the public instance: identifying
|
|
20
|
+
User-Agent required, 1 req/s, single thread, cache required, 5000 uncached lookups per
|
|
21
|
+
run, policy link printed), Photon (fair-use limits on the public server), Pelias,
|
|
22
|
+
generic URL-template provider, and API-key adapters for OpenCage, Geoapify, Mapbox
|
|
23
|
+
(temporary results not cached) and Google (30-day cache TTL).
|
|
24
|
+
- Structured queries (street/city/county/state/postcode/country), country and bbox
|
|
25
|
+
biasing, language, address normalisation.
|
|
26
|
+
- Structured results (lat, lon, display name, normalised components, confidence,
|
|
27
|
+
importance, bbox, precision, raw JSON) and quality flags: `low_confidence`,
|
|
28
|
+
`country_mismatch`, `outside_bbox`, `duplicate_coordinates`, `region_centroid`,
|
|
29
|
+
`fallback_provider`.
|
|
30
|
+
- I/O: CSV and Excel CSV (BOM, delimiter sniffing) in; CSV, GeoJSON and a
|
|
31
|
+
standard-library GeoPackage writer out; stdlib progress bar; run summary (text/JSON).
|
|
32
|
+
- CLI: `geobatch geocode`, `geobatch reverse`, `geobatch cache stats|clear`,
|
|
33
|
+
`geobatch providers`, `geobatch flags`.
|
|
34
|
+
- Optional pandas helpers: `geocode_dataframe`, `reverse_dataframe`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
authors:
|
|
4
|
+
- family-names: "Eminoğlu"
|
|
5
|
+
given-names: "Yusuf"
|
|
6
|
+
email: "yusufeminoglu@gmail.com"
|
|
7
|
+
orcid: "https://orcid.org/0009-0005-6000-2934"
|
|
8
|
+
title: "geobatch"
|
|
9
|
+
version: "0.1.0"
|
|
10
|
+
date-released: "2026-11-09"
|
|
11
|
+
license: "MIT"
|
|
12
|
+
repository-code: "https://github.com/YusufEminoglu/geobatch_sdk"
|
|
13
|
+
url: "https://pypi.org/project/geobatch/"
|
|
14
|
+
abstract: "Polite, resumable batch geocoding and reverse geocoding of spreadsheets over Nominatim, Photon, Pelias and API-key providers, with a persistent SQLite cache, token-bucket rate limiting, Retry-After aware retries, provider fallback and quality flags."
|
|
15
|
+
keywords:
|
|
16
|
+
- geocoding
|
|
17
|
+
- reverse geocoding
|
|
18
|
+
- nominatim
|
|
19
|
+
- openstreetmap
|
|
20
|
+
- gis
|
geobatch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yusuf Eminoğlu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
geobatch-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: geobatch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Polite, resumable batch geocoding and reverse geocoding of spreadsheets over Nominatim, Photon, Pelias and API-key providers, with caching, rate limiting, fallback and quality flags.
|
|
5
|
+
Author-email: Yusuf Eminoğlu <yusufeminoglu@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YusufEminoglu/geobatch_sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/YusufEminoglu/geobatch_sdk
|
|
9
|
+
Project-URL: Issues, https://github.com/YusufEminoglu/geobatch_sdk/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/YusufEminoglu/geobatch_sdk/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: geocoding,reverse-geocoding,batch-geocoding,nominatim,photon,pelias,openstreetmap,address,csv,geojson,geopackage,gis,rate-limiting,cache
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Operating System :: OS Independent
|
|
20
|
+
Classifier: Environment :: Console
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: GIS
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Classifier: Intended Audience :: Science/Research
|
|
24
|
+
Classifier: Intended Audience :: Developers
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: pandas
|
|
30
|
+
Requires-Dist: pandas>=1.3.0; extra == "pandas"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
35
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# geobatch
|
|
39
|
+
|
|
40
|
+
**Polite, resumable batch geocoding for spreadsheets.**
|
|
41
|
+
|
|
42
|
+
`geobatch` turns a CSV of thousands of addresses into coordinates (or a CSV of
|
|
43
|
+
coordinates into addresses) reliably and without upsetting the services you
|
|
44
|
+
depend on. It runs on the Python standard library alone and puts several
|
|
45
|
+
providers behind one interface:
|
|
46
|
+
|
|
47
|
+
- **Nominatim** (public or self-hosted). The OpenStreetMap Foundation usage policy is enforced on the public instance.
|
|
48
|
+
- **Photon** (komoot public demo or self-hosted).
|
|
49
|
+
- **Pelias** (self-hosted or hosted).
|
|
50
|
+
- **Custom HTTP template** for any JSON geocoding API.
|
|
51
|
+
- Thin adapters for API-key services: **OpenCage**, **Geoapify**, **Mapbox** and **Google**. Keys are read only from environment variables and are never logged.
|
|
52
|
+
|
|
53
|
+
Every lookup goes through a persistent SQLite cache, per-provider token-bucket
|
|
54
|
+
rate limiting, retries that honour `Retry-After`, a fallback chain and
|
|
55
|
+
quality checks. A batch that stops halfway can be resumed: run the same
|
|
56
|
+
command again.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install geobatch # stdlib only
|
|
60
|
+
pip install "geobatch[pandas]" # + DataFrame helpers
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Requires Python 3.9 or newer.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Quick start (command line)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
geobatch geocode addresses.csv --address-col address \
|
|
71
|
+
--provider nominatim \
|
|
72
|
+
--user-agent "MyProject/1.0 (https://my-project.org; gis-team@my-project.org)" \
|
|
73
|
+
-o geocoded.csv
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Illustrative output (abridged):
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
geobatch: Nominatim usage policy applies: https://operations.osmfoundation.org/policies/nominatim/ (1 req/s, results cached, at most 5000 new lookups per run)
|
|
80
|
+
geocoding [##############----------------] 1412/3000 47.1% cached=388 failed=3 elapsed=17:04 eta=19:10
|
|
81
|
+
...
|
|
82
|
+
geobatch forward summary
|
|
83
|
+
rows 3000
|
|
84
|
+
ok 2941
|
|
85
|
+
not found 52
|
|
86
|
+
errors 3
|
|
87
|
+
skipped (empty) 4
|
|
88
|
+
unique lookups 2710
|
|
89
|
+
cache hits 388
|
|
90
|
+
HTTP requests 2325 (retries 3)
|
|
91
|
+
by provider nominatim=2941
|
|
92
|
+
by precision locality=61, rooftop=2402, street=478
|
|
93
|
+
quality flags country_mismatch=4, duplicate_coordinates=18, region_centroid=61
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The output keeps every input column and adds `geobatch_status`, `geobatch_lat`,
|
|
97
|
+
`geobatch_lon`, `geobatch_display_name`, `geobatch_precision`,
|
|
98
|
+
`geobatch_confidence`, `geobatch_importance`, `geobatch_country_code`,
|
|
99
|
+
`geobatch_provider`, `geobatch_flags`, `geobatch_from_cache` and
|
|
100
|
+
`geobatch_error`.
|
|
101
|
+
|
|
102
|
+
More examples:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Structured columns, Germany only, fallback to Photon, GeoJSON output
|
|
106
|
+
geobatch geocode customers.csv --street-col street --postcode-col zip --city-col city \
|
|
107
|
+
--country-codes de --provider nominatim,photon \
|
|
108
|
+
--user-agent "MyProject/1.0 (https://my-project.org)" -o customers.geojson
|
|
109
|
+
|
|
110
|
+
# Self-hosted Nominatim: no public-policy limits, so raise the rate and parallelism
|
|
111
|
+
geobatch geocode big.csv --address-col addr --url nominatim=http://localhost:8080 \
|
|
112
|
+
--rate 50 --concurrency 8 -o big.gpkg
|
|
113
|
+
|
|
114
|
+
# Reverse geocoding
|
|
115
|
+
geobatch reverse points.csv --lat-col lat --lon-col lon \
|
|
116
|
+
--user-agent "MyProject/1.0 (https://my-project.org)" -o points_addresses.csv
|
|
117
|
+
|
|
118
|
+
# A commercial provider (key from the environment, never from the command line)
|
|
119
|
+
export OPENCAGE_API_KEY=...
|
|
120
|
+
geobatch geocode addresses.csv --address-col address --provider opencage -o out.csv
|
|
121
|
+
|
|
122
|
+
# Cache maintenance
|
|
123
|
+
geobatch cache stats
|
|
124
|
+
geobatch cache clear --provider photon
|
|
125
|
+
geobatch cache clear --expired
|
|
126
|
+
|
|
127
|
+
# Provider terms and key variables, quality flag meanings
|
|
128
|
+
geobatch providers
|
|
129
|
+
geobatch flags
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Input files: UTF-8 CSV, including the BOM that Excel writes. The delimiter
|
|
133
|
+
(`,` `;` tab `|`) is detected automatically, so semicolon "CSV" exports from
|
|
134
|
+
European Excel work as they are. `--excel` writes the output with a BOM so
|
|
135
|
+
Excel shows accented characters correctly. Output format follows the file
|
|
136
|
+
extension: `.csv`, `.tsv`, `.geojson` or `.gpkg`. GeoPackage is written with
|
|
137
|
+
the standard-library `sqlite3` as an OGC GeoPackage 1.3 point layer in EPSG:4326,
|
|
138
|
+
so GDAL is not needed. QGIS and GDAL read it directly.
|
|
139
|
+
|
|
140
|
+
Exit codes: `0` done, `2` usage or configuration error (including policy
|
|
141
|
+
refusals), `3` aborted after too many consecutive failures, `130` interrupted
|
|
142
|
+
(a partial output is still written).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Responsible use
|
|
147
|
+
|
|
148
|
+
Free geocoders are run by volunteers and small teams on donated hardware. A
|
|
149
|
+
careless batch job can get your IP blocked, and it also costs everyone else
|
|
150
|
+
capacity. geobatch builds the rules in rather than leaving them to the
|
|
151
|
+
documentation.
|
|
152
|
+
|
|
153
|
+
| Provider | Terms / policy | What geobatch enforces |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| Nominatim (public `nominatim.openstreetmap.org`) | [OSMF Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/), [OSM copyright & ODbL](https://www.openstreetmap.org/copyright) | Refuses to run without an identifying `user_agent=` / `--user-agent`. Stock library agents, geobatch's own default agent and placeholders such as `example.org` are rejected, and geobatch never fills in an e-mail address for you. Max **1 request/second**, **single thread**, cache required, at most **5000 uncached lookups per run** ("smaller one-time bulk tasks" only). The policy link is printed on every run, and the policy cannot be switched off for this host. |
|
|
156
|
+
| Photon (public `photon.komoot.io`) | [photon.komoot.io](https://photon.komoot.io/): fair use, heavy use is throttled; [source](https://github.com/komoot/photon) | 1 request/second, single thread, cache required (a conservative choice by geobatch). |
|
|
157
|
+
| Pelias | [documentation](https://github.com/pelias/documentation); hosted services such as [geocode.earth](https://geocode.earth) have their own terms | Optional `PELIAS_API_KEY`, redacted from logs. |
|
|
158
|
+
| OpenCage | [terms](https://opencagedata.com/terms) | Key from `OPENCAGE_API_KEY`; default 1 req/s (free-trial limit). |
|
|
159
|
+
| Geoapify | [terms](https://www.geoapify.com/terms-and-conditions/) | Key from `GEOAPIFY_API_KEY`; default 5 req/s. Attribution is required by Geoapify. |
|
|
160
|
+
| Mapbox | [terms of service](https://www.mapbox.com/legal/tos) | Token from `MAPBOX_ACCESS_TOKEN`. Temporary geocoding results are **not cached**; pass `permanent=True` / `--mapbox-permanent` only if your account includes permanent geocoding. |
|
|
161
|
+
| Google Geocoding | [Maps Platform terms](https://cloud.google.com/maps-platform/terms), [geocoding policies](https://developers.google.com/maps/documentation/geocoding/policies) | Key from `GOOGLE_MAPS_API_KEY`. Cache entries **expire after 30 days**. Google requires results to be used with a Google map. |
|
|
162
|
+
|
|
163
|
+
More rules that apply to every provider:
|
|
164
|
+
|
|
165
|
+
- **Keys** are read from environment variables only and never accepted as
|
|
166
|
+
arguments. They are not part of cache keys, and they are replaced by `***` in
|
|
167
|
+
every error message.
|
|
168
|
+
- **Retries** use exponential backoff with jitter, and a server's
|
|
169
|
+
`Retry-After` always wins. A 429 pauses the provider's token bucket for all
|
|
170
|
+
threads, not just the thread that received it. If a server asks you to wait
|
|
171
|
+
more than `max_retry_after` (300 s by default; usually a used-up daily
|
|
172
|
+
quota), geobatch gives up on that lookup instead of hammering or blocking
|
|
173
|
+
for hours.
|
|
174
|
+
- **Circuit breaker:** after 25 consecutive failed lookups the batch stops,
|
|
175
|
+
because the service is down or blocking you.
|
|
176
|
+
- **De-duplication:** identical addresses (after normalisation) are looked up
|
|
177
|
+
once.
|
|
178
|
+
- **Attribution:** OpenStreetMap-based results (Nominatim, Photon, most Pelias
|
|
179
|
+
installations) are © OpenStreetMap contributors under the ODbL. Credit them
|
|
180
|
+
wherever you publish the data.
|
|
181
|
+
|
|
182
|
+
For large or recurring jobs, run your own Nominatim, Photon or Pelias, or use a
|
|
183
|
+
commercial provider. Self-hosted instances have no built-in limits in
|
|
184
|
+
geobatch. Set `--rate` and `--concurrency` to what your server can handle.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Python API
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from geobatch import Geocoder, Nominatim, Photon, Query, SearchOptions, QualityConfig
|
|
192
|
+
|
|
193
|
+
ua = "MyProject/1.0 (https://my-project.org)"
|
|
194
|
+
geocoder = Geocoder(
|
|
195
|
+
[Nominatim(user_agent=ua), Photon(user_agent=ua)], # fallback chain
|
|
196
|
+
cache="geocache.sqlite", # default: per-user cache dir
|
|
197
|
+
options=SearchOptions(country_codes=("de", "at"), language="en"),
|
|
198
|
+
quality=QualityConfig(min_confidence=0.5, expected_countries=("de", "at")),
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
result = geocoder.geocode_many(
|
|
202
|
+
[
|
|
203
|
+
"Pariser Platz 1, 10117 Berlin",
|
|
204
|
+
Query(street="Stephansplatz 3", postcode="1010", city="Wien", country="Austria"),
|
|
205
|
+
{"city": "Hamburg"},
|
|
206
|
+
],
|
|
207
|
+
progress=True,
|
|
208
|
+
)
|
|
209
|
+
for rec in result:
|
|
210
|
+
if rec.ok:
|
|
211
|
+
r = rec.result
|
|
212
|
+
print(r.lat, r.lon, r.display_name, r.precision, r.confidence, rec.flags)
|
|
213
|
+
else:
|
|
214
|
+
print(rec.status, rec.error)
|
|
215
|
+
print(result.summary.format())
|
|
216
|
+
|
|
217
|
+
# one-offs
|
|
218
|
+
rec = geocoder.geocode("Brandenburger Tor, Berlin")
|
|
219
|
+
rec = geocoder.reverse(52.5163, 13.3777)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
`GeocodeResult` fields: `provider`, `lat`, `lon`, `display_name`,
|
|
223
|
+
`components` (normalised keys such as `house_number`, `road`, `postcode`,
|
|
224
|
+
`city`, `state`, `country`, `country_code`), `confidence` (0..1), `importance`,
|
|
225
|
+
`bbox` `(min_lon, min_lat, max_lon, max_lat)`, `precision` (`rooftop`,
|
|
226
|
+
`interpolated`, `street`, `postcode`, `locality`, `region`, `country`,
|
|
227
|
+
`unknown`), `result_type` and `raw` (the provider's original JSON). If a
|
|
228
|
+
provider gives no confidence score (Nominatim, Photon), `confidence` is
|
|
229
|
+
derived from `precision`.
|
|
230
|
+
|
|
231
|
+
Reading and writing files:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
from geobatch import read_table, build_queries, write_output
|
|
235
|
+
|
|
236
|
+
columns, rows = read_table("addresses.csv")
|
|
237
|
+
queries = build_queries(columns, rows, street_col="street", city_col="city", postcode_col="zip")
|
|
238
|
+
result = geocoder.geocode_many(queries, rows=rows)
|
|
239
|
+
write_output(result.records, "addresses.gpkg")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
With pandas (`pip install "geobatch[pandas]"`):
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from geobatch.pandas_ext import geocode_dataframe, reverse_dataframe
|
|
246
|
+
|
|
247
|
+
df = geocode_dataframe(df, geocoder, address_col="address", expected_country_col="iso2")
|
|
248
|
+
df.attrs["geobatch_summary"]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Custom HTTP template provider
|
|
252
|
+
|
|
253
|
+
Any JSON API can be described by URL templates and dotted JSON paths:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from geobatch import TemplateProvider
|
|
257
|
+
|
|
258
|
+
inhouse = TemplateProvider(
|
|
259
|
+
"https://geo.internal/find?address={query}&token={env:GEO_TOKEN}",
|
|
260
|
+
reverse_url="https://geo.internal/reverse?lat={lat}&lon={lon}&token={env:GEO_TOKEN}",
|
|
261
|
+
results_path="data.matches", lat_field="y", lon_field="x",
|
|
262
|
+
display_field="label", confidence_field="score", confidence_scale=0.01,
|
|
263
|
+
name="inhouse", rate=10, concurrency=4,
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
The available placeholders are `{query}`, `{street}`, `{city}`, `{county}`,
|
|
268
|
+
`{state}`, `{postcode}`, `{country}`, `{country_codes}`, `{language}`,
|
|
269
|
+
`{limit}`, `{lat}`, `{lon}` and `{env:NAME}`. A parameter that uses `{env:...}`
|
|
270
|
+
is redacted from logs and kept out of the cache key. GeoJSON
|
|
271
|
+
FeatureCollections are recognised automatically. On the command line, use
|
|
272
|
+
`--provider template --template-url ...`.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## How it works
|
|
277
|
+
|
|
278
|
+
**Normalisation.** Before lookup and before computing cache keys, addresses
|
|
279
|
+
are cleaned: Unicode NFKC (full-width digits, non-breaking spaces), control
|
|
280
|
+
characters, collapsed whitespace, surrounding quotes, doubled or trailing
|
|
281
|
+
separators, and spreadsheet null tokens (`NaN`, `N/A`, `-`) become empty.
|
|
282
|
+
Wording is never rewritten. Cache keys are case-insensitive.
|
|
283
|
+
|
|
284
|
+
**Cache and resume.** The SQLite cache (WAL mode, thread-safe) is keyed by
|
|
285
|
+
provider and endpoint, query kind, normalised query and search options. It
|
|
286
|
+
stores found results and "not found" answers, and each one is committed as
|
|
287
|
+
soon as it arrives. Network errors are never cached. If a batch is
|
|
288
|
+
interrupted (Ctrl+C, crash, laptop lid), run the same command again: finished
|
|
289
|
+
lookups come from the cache and only the rest goes to the network. In Python,
|
|
290
|
+
`BatchInterrupted.result` holds the partial result. The default location is
|
|
291
|
+
the per-user cache directory (`%LOCALAPPDATA%\geobatch`, `~/Library/Caches/geobatch`
|
|
292
|
+
or `~/.cache/geobatch`). Override it with `--cache` or `GEOBATCH_CACHE`.
|
|
293
|
+
|
|
294
|
+
**Rate limiting and concurrency.** Each provider has a token bucket
|
|
295
|
+
(`rate` requests per second, no initial burst) and a concurrency limit, shared
|
|
296
|
+
by all worker threads.
|
|
297
|
+
|
|
298
|
+
**Fallback.** Providers are tried in order. The next one is asked when a
|
|
299
|
+
provider fails, and also when it finds nothing (turn that off with
|
|
300
|
+
`fallback_on_not_found=False` / `--no-fallback-on-not-found`).
|
|
301
|
+
|
|
302
|
+
**Biasing.** `country_codes`, `bbox` and `bounded` are translated into each
|
|
303
|
+
provider's own parameters, for example Nominatim
|
|
304
|
+
`countrycodes`/`viewbox`/`bounded`, Pelias `boundary.*`/`focus.point`,
|
|
305
|
+
Geoapify `filter`/`bias` and Google `components`/`bounds`. Structured queries
|
|
306
|
+
use native structured endpoints where they exist (Nominatim, Pelias, Geoapify,
|
|
307
|
+
Mapbox) and are joined into one line elsewhere.
|
|
308
|
+
|
|
309
|
+
### Quality flags
|
|
310
|
+
|
|
311
|
+
| Flag | Meaning |
|
|
312
|
+
|---|---|
|
|
313
|
+
| `low_confidence` | confidence below `min_confidence` (default 0.5) |
|
|
314
|
+
| `country_mismatch` | result is outside the expected countries (`--country-codes` or a per-row `--expected-country-col`) |
|
|
315
|
+
| `outside_bbox` | result is outside the expected bounding box (`--bbox --bounded`) |
|
|
316
|
+
| `duplicate_coordinates` | the same point was returned for *different* addresses, a typical sign of a fallback to a street or town centroid |
|
|
317
|
+
| `region_centroid` | a postcode, city or region centroid was returned for a query that had street-level detail, or a region or country centroid was returned for any query |
|
|
318
|
+
| `fallback_provider` | answered by a provider other than the first in the chain |
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Development
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
pip install -e ".[dev,pandas]"
|
|
326
|
+
pytest --cov=geobatch
|
|
327
|
+
ruff check . && ruff format --check src tests && mypy src
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The test suite never touches the real network. It runs a local stdlib HTTP
|
|
331
|
+
server (`tests/mockserver.py`) that imitates every provider's JSON, including
|
|
332
|
+
429/503 with `Retry-After`, malformed JSON, timeouts, rate-limit timing,
|
|
333
|
+
cache hits, resume and fallback.
|
|
334
|
+
|
|
335
|
+
## License
|
|
336
|
+
|
|
337
|
+
MIT © 2026 Yusuf Eminoğlu. See [LICENSE](LICENSE). To cite geobatch, see
|
|
338
|
+
[CITATION.cff](CITATION.cff).
|