Metadata-Version: 2.4
Name: openplaces-cli
Version: 0.1.1
Summary: Open-source mapping CLI — place search, geocoding, routing and isochrones from OpenStreetMap, the Base Adresse Nationale and OpenRouteService. No paid API key, no data leaving the EU.
Project-URL: Homepage, https://github.com/PostDev360/openplaces
Project-URL: Issues, https://github.com/PostDev360/openplaces/issues
Author: PostDev360
License: MIT License
        
        Copyright (c) 2026 PostDev360
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: cli,gdpr,geocoding,openstreetmap,overpass,places,routing
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: GIS
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# openplaces

Open-source mapping CLI — place search, geocoding, reverse geocoding, routing
and isochrones, without Google and without a paid API key.

Built as a drop-in replacement for the kind of work [`goplaces`][goplaces] does
against the Google Places API, for people who cannot or will not send addresses
to a US-hosted service.

[![CI](https://github.com/PostDev360/openplaces/actions/workflows/ci.yml/badge.svg)](https://github.com/PostDev360/openplaces/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

---

## Why

| | Google Places | openplaces |
|---|---|---|
| Cost | Paid, per request | Free |
| Keys | Mandatory, billable | One, free, optional — routing only |
| Hosting | United States | France and Germany |
| Ratings and reviews | Yes | **No** — see [Limits](#limits) |
| Coverage | Uniform, global | Excellent in urban Europe, patchy elsewhere |

The hosting row is the point. `openplaces` was written for a workload where the
addresses being geocoded belong to an accountancy firm's clients, so sending
them outside the EU is a GDPR problem rather than a preference.

## Install

```bash
uv tool install openplaces-cli     # or: pipx install openplaces-cli
```

The distribution is [`openplaces-cli`](https://pypi.org/project/openplaces-cli/)
because `openplaces` was already taken on PyPI by an unrelated project. The
command it installs is `openplaces`, placed in `~/.local/bin` by both
installers.

Python 3.10 or newer; `uv` fetches a suitable interpreter itself, `pipx`
requires one already present. No runtime dependencies.

See [INSTALL.md](INSTALL.md) to wire it into an OpenClaw or Claude Code agent.

## Use

```bash
openplaces search "bakery" --near "Marseille 2e" --radius-m 1500 --limit 5
openplaces search "pharmacy" --lat 43.30 --lng 5.38 --open-now
openplaces search "pharmacy" --near "Lyon" --tag wheelchair=yes
openplaces resolve "8 quai du Port, Marseille"
openplaces reverse --lat 43.2965 --lng 5.3698
openplaces details node/1941628376
openplaces route --from "Marseille" --to "Carry-le-Rouet" --profile cycling-electric
openplaces isochrone --near "Aix-en-Provence" --minutes 20 --geojson
openplaces categories
openplaces cache info
```

`--json` gives flat records for scripts; `--geojson` gives a FeatureCollection
for mapping tools. Both are global flags, placed before the subcommand.

## Data sources

| Function | Service | Key | Hosting |
|---|---|---|---|
| French geocoding | [Base Adresse Nationale][ban] | no | France, public service |
| International geocoding | [Photon][photon] (Komoot) | no | Germany |
| Place search | [Overpass][overpass] / OpenStreetMap | no | Germany |
| Routing, isochrones | [OpenRouteService][ors] | free | Germany |

Every endpoint is overridable, so the whole tool can be pointed at self-hosted
instances without touching the code.

## Configuration

Nothing is mandatory.

| Variable | Role | Default |
|---|---|---|
| `OPENROUTESERVICE_API_KEY` | `route` and `isochrone` only | — |
| `OPENPLACES_USER_AGENT` | identifier sent to public instances | generic — **change it** |
| `OPENPLACES_BAN_URL` | BAN endpoint | `https://api-adresse.data.gouv.fr` |
| `OPENPLACES_PHOTON_URL` | Photon endpoint | `https://photon.komoot.io` |
| `OPENPLACES_OVERPASS_URL` | Overpass endpoint | `https://overpass-api.de/api/interpreter` |
| `OPENPLACES_ORS_URL` | ORS endpoint | `https://api.openrouteservice.org` |
| `OPENPLACES_CACHE_DIR` | cache directory | `~/.cache/openplaces` |
| `OPENPLACES_CACHE_TTL` | cache lifetime, seconds | `604800` |
| `OPENPLACES_MIN_INTERVAL` | seconds between calls to one host | `1.0` |
| `OPENPLACES_MAX_RETRIES` | retries on a retryable status | `3` |
| `OPENPLACES_TIMEOUT` | per-request timeout, seconds | `45` |
| `OPENPLACES_BAN_MIN_SCORE` | BAN score below which Photon is consulted | `0.5` |
| `OPENPLACES_NO_CACHE` | set to anything to bypass the cache | unset |

The OpenRouteService key must never be committed or written into a `SKILL.md`.
Use a `chmod 600` `.env` outside the repository, or an orchestrator secret.

## Exit codes

Distinct per failure family, so a caller can tell "retry this" from "this will
never work".

| Code | Meaning | Retry? |
|---|---|---|
| 0 | success | — |
| 2 | usage error | no |
| 3 | invalid input (out-of-range coordinates, radius, limit) | no |
| 4 | not found | no |
| 5 | network unreachable | yes |
| 6 | upstream error, already retried | yes, later |
| 7 | missing configuration (no ORS key) | no — fix the config |

## Design constraints

**Standard library only at runtime.** No dependency to audit, no wheel to
resolve, and `uv tool install` is instant. `pytest` and `mypy` are development
extras and never ship.

**Politeness is not optional.** Every response is cached in SQLite, calls to one
host are spaced a second apart, and the User-Agent is identifiable. Public
Overpass instances ban greedy IPs, and an agent in a loop is exactly the profile
they watch for.

**Three-state opening hours.** The `opening_hours` grammar is large; this
implementation covers the common forms and returns *unknown* — not *closed* —
for anything it does not recognise. `--open-now` drops unknowns, because a
wrong "open" sends someone to a closed door.

**The BAN is trusted, but verified.** It answers first on French addresses
because it is built on the cadastre. It also returns *something* for almost any
input: "Alexanderplatz Berlin" comes back as "Allée de Berlin, Les
Pavillons-sous-Bois" at score 0.38 rather than as an empty list. So the fallback
to Photon triggers on the confidence score, not on an empty result. Related:
the BAN weights town names weakly in free text, so `--postcode` and
`--citycode` are exposed as real filters.

## Limits

These are documented, not bugs, and not worth working around.

- **No ratings or reviews.** OpenStreetMap does not host them by design. This is
  the only irreducible functional difference with Google Places. Do not try to
  fill it by scraping.
- **Uneven coverage.** Excellent in urban Europe, thin in rural areas and
  outside Europe. The data is contributed.
- **Frequently missing fields.** `opening_hours`, `phone` and `website` are
  often absent. The tool degrades to empty strings; it never fails on this.
- **Overpass 504s.** Public instances return them often under load. They are
  retried with exponential backoff, but a busy period can still exhaust the
  retries and exit 6.

## Attribution

Data © OpenStreetMap contributors, available under the [ODbL][odbl]. Any public
reuse of results must carry that credit.

## Development

```bash
python -m pytest              # 128 offline tests; no network access needed
python -m mypy                # strict
python -m pytest -m network   # 11 integration tests against the real services
```

The default suite is offline by design: an autouse fixture makes any unmocked
outbound call fail loudly, and the fixtures under `tests/fixtures/` were
recorded from real responses, so the parsers are tested against what the
services actually send rather than what their documentation implies. Contact
tags in those fixtures are scrubbed — see the note in `tests/conftest.py`
before recording new ones.

`-m network` answers the question the offline suite cannot: *are the upstream
services still behaving the way the parsers expect?* It takes about two
minutes, throttled at one request per host per second. Some skips are normal —
the OpenRouteService tests need a key, and the Overpass tests skip rather than
fail when a public instance is saturated, since that says nothing about the
code. CI runs the offline suite only.

## Agent integration

`SKILL.md` in this repository is the [OpenClaw][openclaw] manifest. A companion
Claude Code skill lives in [postdev-skills][postdev-skills].

## License

MIT. See [LICENSE](LICENSE).

[ban]: https://adresse.data.gouv.fr/
[goplaces]: https://github.com/steipete/goplaces
[odbl]: https://opendatacommons.org/licenses/odbl/
[openclaw]: https://github.com/openclaw/openclaw
[ors]: https://openrouteservice.org/
[overpass]: https://wiki.openstreetmap.org/wiki/Overpass_API
[photon]: https://photon.komoot.io/
[postdev-skills]: https://github.com/PostDev360/postdev-skills
