"On the shores of Lake Geneva" — Teaching machines to understand where you mean
Frédéric Junod
The gap
Here's a problem that comes up constantly in geospatial products, and almost nobody has solved cleanly.
Users describe locations the way humans do: "somewhere near the lake", "in the old town", "along the Rhône", "a few kilometers north of Zurich". It's natural, expressive, and rich with meaning.
Your application, on the other hand, expects a bounding box. Or a radius. Or a polygon. It speaks coordinates.
These two representations are not the same thing, and the gap between them is where a lot of product quality gets lost.
The naive solution is a geocoder: turn "Lausanne" into coordinates and draw a circle. It mostly works — until you're dealing with "on the shores of Lake Geneva" (which is not the center of the lake, it's a 1km ring just outside the water body), or "right bank of the Rhône" (which depends on flow direction, not geography), or "in the heart of Geneva" (which means you want the inner core, not a buffer centered on the city hall).
These aren't edge cases. They're the kind of queries that make location-aware apps feel smart or feel dumb.
Why we built etter
At Camptocamp, we spend a lot of time at this intersection: geospatial infrastructure, search systems, and now increasingly LLMs. A client project brought all three together. We needed to parse user location queries — in French, German, Italian, English — and turn them into reliable spatial filters that a PostGIS database could act on.
We looked for an existing library to handle this. We didn't find one that covered the full problem: not just geocoding, not just NLP, but the specific chain of extract spatial intent → resolve location → apply geometry transformation.
So we built it, and then we open-sourced it under the name etter — a medieval Swiss German word, likely dating to the Middle Ages, for the boundary marking the edge of a village. Felt right.
How it works (the three-layer pipeline)
etter is built around a clean three-layer architecture. Understanding these layers is key to understanding why it works.
Layer 1 — Parsing. A GeoFilterParser takes a natural language query and uses an LLM to extract a structured GeoQuery object. This includes: the spatial relation ("north_of", "on_shores_of", "right_bank"), the reference location ("Lausanne", "Rhône", "Lac Léman"), and optional parameters like distance. The LLM does what LLMs are good at: semantic understanding, multilingual handling, disambiguation. The output is a typed Pydantic model — no free-form strings leaking downstream.
Layer 2 — Resolution. The GeoDataSource takes the reference location extracted in Layer 1 and resolves it to an actual geometry. etter ships with adapters for SwissNames3D (Swiss federal toponymy data), IGN BD-CARTO (French national geographic data), and PostGIS (bring your own data). But the datasource layer is deliberately flexible: the protocol is a simple Python interface, and you can implement it against anything — a REST API, a flat GeoJSON file, an in-memory dataset, a third-party geocoder, or any spatial database your infrastructure runs. There is no required backend. Swapping or combining datasources is a first-class use case.
Layer 3 — Spatial operations. This is where the geometry gets shaped. The apply_spatial_relation() function takes the resolved geometry and applies the spatial transformation defined by the relation. "North of Lausanne" becomes a 10km sector oriented north. "On the shores of Lake Geneva" becomes a 1km ring buffer from the boundary of the lake polygon, excluding the water itself. "Right bank of the Rhône" uses the flow direction of the river to determine which side is "right" and buffers accordingly.
The output is a GeoJSON geometry, ready to use as a spatial filter in your search query.
The spatial relations catalog
etter currently supports 15 spatial relations across three categories:
Containment:
in — exact boundary matching.
Buffer / Proximity:
- near — context-aware distance (default 5km, but the LLM infers the right scale from query intent — "near the Eiffel Tower" is different from "near Paris")
- on_shores_of — 1km ring from boundary, excludes the water body
- along — 500m buffer for linear features (rivers, roads)
- left_bank / right_bank — buffer on one side of a linear feature, relative to flow direction
- in_the_heart_of — erosion of the geometry to isolate its central core
Directional (all cardinal and diagonal):
north_of, south_of, east_of, west_of, northeast_of, southeast_of, southwest_of, northwest_of — 10km sector geometries, 90° each.
Each relation has its own geometry logic. A buffer from the center of a lake is not the same as a buffer from its boundary. A directional sector is not a semicircle. These distinctions matter.
The catalog is open for extension — you can define and register your own spatial relations alongside the built-ins, implementing custom geometry logic for any relation your domain requires.
The stack
Python, obviously. The key dependencies:
- LangChain — for LLM integration. etter uses init_chat_model, which means you can plug in OpenAI, Anthropic, any local model, whatever your infrastructure runs. The library is provider-agnostic by design.
- Shapely — geometry operations
- Pydantic — structured output with full type safety throughout the pipeline
Install it with:
pip install etter
# or with PostGIS support:
pip install etter[postgis]What it looks like in practice
Here's a minimal working example:
from langchain.chat_models import init_chat_model
from etter import GeoFilterParser
llm = init_chat_model(model="gpt-4o", temperature=0, api_key="...")
parser = GeoFilterParser(llm=llm)
result = parser.parse("on the shores of Lake Geneva")
# result.spatial_relation → "on_shores_of"
# result.reference_location → "Lake Geneva"
# result.confidence → 0.97Layer 2 and 3 resolve the location and produce the final geometry. The demo API ships with SwissNames3D and shows the complete end-to-end flow.
There's also streaming support, which is useful for responsive UIs — you get real-time reasoning transparency as the LLM processes the query, not just a final result.
What's not there yet
etter handles simple queries well. Compound queries ("north of Lausanne but not too close to the motorway") and split queries ("between Bern and Zurich") are not yet implemented. These are on the roadmap, but we wanted to get the core library out first.
Why Open Source
Camptocamp has been building with open source for over 25 years. We contribute to QGIS, OpenLayers, GeoServer, and many other projects. etter fits naturally into that tradition: we built something useful to solve a real problem, and we'd rather it be available to the community than sit in a private repo.
If you're building something with etter, we'd love to know. If you find it useful for a project and want expert geospatial engineering support around it, that's exactly what Camptocamp does. But there's no pressure either way — the library works on its own.
The GitHub repo is at https://github.com/geoblocks/etter. Documentation at https://geoblocks.github.io/etter/ .
Career
Interested in working in an inspiring environment and joining our motivated and multicultural teams?