# Geo

Geocode addresses, reverse geocode coordinates, and compute distances with static, OSM, or Google backends.

One `Geo` interface covers `Geocode`, `ReverseGeocode`, and `Distance` (meters), so callers stay fixed while the backend changes.

## Quick start

`static` (the default) serves a file-backed list from memory, offline and deterministic; `path` points at a JSON fixture.

```sh
GEO_ADAPTER=static
GEO_PATH=data/cities.json
```

```go title="main.go" check
package main

import (
    "context"
    "fmt"

    "github.com/zenta-dev/zever/adapters/geo/static"
    "github.com/zenta-dev/zever/config"
    "github.com/zenta-dev/zever/container"
    "github.com/zenta-dev/zever/core/geo"
)

func main() {
    static.Register()
    c := container.New(config.Default())
    g, _ := c.Geo()
    defer g.Close()

    ctx := context.Background()
    locs, _ := g.Geocode(ctx, "Berlin")
    fmt.Println(len(locs))
    d, _ := g.Distance(ctx, geo.Point{Lat: 52.52, Lng: 13.40}, geo.Point{Lat: 48.85, Lng: 2.35})
    fmt.Println(d)
}
```

```text
1
877676.52...
```

The `1` assumes the fixture lists Berlin; `Distance` is haversine over a 6371 km sphere, in meters.

The bookings example pins `geo.adapter: static`; its tests point `cfg.Geo.Options.Path` at `testdata/cities.json`. `ValidCoord` checks WGS84 range and finiteness before coordinates reach a provider.

## Use OSM or Google in production

`osm` is free OpenStreetMap geocoding when Google-grade accuracy is not needed; it requires a `user_agent` per Nominatim policy. `google` is for accuracy with an API key budget.

```sh
GEO_ADAPTER=osm
GEO_ENDPOINT=https://nominatim.openstreetmap.org
GEO_USER_AGENT=bookings-demo/1.0
```

```sh
GEO_ADAPTER=google
GEO_API_KEY=your-key
GEO_BASE_URL=https://maps.googleapis.com
```

`Validate` rejects a negative `timeout` or `max_response_body`, and any `base_url` or `endpoint` that is not `https` unless `allow_insecure`. Outbound requests dial only globally routable addresses and never follow redirects; a local tile/mock endpoint needs `allow_private: true`, which logs a line at startup. Env vars mirror the keys. See the [geo config reference](https://zenta-dev.github.io/zever/reference/config/#geo).

| Adapter | Package | Notes |
|---|---|---|
| `static` | `adapters/geo/static` | Default. File-backed, offline, deterministic. |
| `osm` | `adapters/geo/osm` | Requires `user_agent`. |
| `google` | `adapters/geo/google` | Needs `api_key`. |

## Production checklist

- `Geo.Close() error` takes no context; the container closes resolved services in dependency order and joins errors with `errors.Join`.
- `static` is per-process, so every replica needs the same fixture; `osm` and `google` are remote and slow, so cache or job them.
- `api_key` is secret; never log raw option maps (use `cfg.RedactedServices()`).

## Troubleshooting

<details>
<summary>`zever doctor` reports FAIL on geo</summary>

Cause: zero-infra defaults read `cities.json` from the working directory, so a missing fixture fails. Fix: seed the fixture or point `path` at committed test data. It is a fixture gap, not a framework bug.
</details>

<details>
<summary>`base_url must use https` error</summary>

Cause: external adapters require `https`. Fix: set a valid `https` URL; `allow_insecure` is only for `httptest` tests.
</details>

<details>
<summary>OSM rejects requests</summary>

Cause: the `user_agent` is missing or generic. Fix: set a real app-identifying `User-Agent` per Nominatim policy, and cache results to stay inside usage limits.
</details>

## Related

- [Cache](https://zenta-dev.github.io/zever/guides/data/cache/)
- [Rate limiting](https://zenta-dev.github.io/zever/guides/security/ratelimit/)
- [Observability](https://zenta-dev.github.io/zever/guides/operate/observability/)
