Skip to content

Datasources

A datasource resolves a location name (e.g. "Lausanne") to a geometry. etter ships with three implementations and a composable aggregator.

All datasources implement the GeoDataSource protocol — no inheritance required.

SwissNames3D

Wraps the swisstopo SwissNames3D dataset (Shapefile/GDB). Covers Switzerland with ~80 geographic feature types.

python
from etter.datasources import SwissNames3DSource

source = SwissNames3DSource("data/swissnames3d/")
results = source.search("Lausanne", type="settlement", max_results=5)

Handles EPSG:2056 → WGS84 reprojection and fuzzy name matching automatically. Data is loaded lazily on first use.

IGN BD-CARTO

Wraps the IGN BD-CARTO GeoPackage for France. Covers 14 thematic layers (administrative boundaries, hydrography, named places, protected areas, etc.).

python
from etter.datasources import IGNBDCartoSource

source = IGNBDCartoSource("data/bdcarto/")
results = source.search("Rhône", type="water")

Handles Lambert-93 (EPSG:2154) → WGS84 reprojection and French article stripping (le, la, l', les, de, du, des).

PostGIS

A generic PostGIS datasource that works with any table. The connection is validated at construction time.

python
from etter.datasources import PostGISDataSource

source = PostGISDataSource(
    connection="postgresql+psycopg2://...",
    table="public.my_geodata",
    type_map={"municipality": ["COMMUNE"], "river": ["COURS_EAU"]},
)
results = source.search("Genève", type="city")

The type_map maps normalized type names (as used by etter's type system) to lists of raw values in the database's type column — the same direction as SwissNames3DSource's OBJEKTART_TYPE_MAP.

Use the TypeMap type alias when defining your own map to get editor auto-complete and static validation of keys:

python
from etter.datasources import PostGISDataSource, TypeMap

my_map: TypeMap = {
    "municipality": ["COMMUNE"],
    "river": ["COURS_EAU"],
}
source = PostGISDataSource(
    connection="postgresql+psycopg2://...",
    table="public.my_geodata",
    type_map=my_map,
)

Keys must be valid etter type names (concrete types such as "lake" or category names such as "water"). An invalid key like "lac" is caught by static analysis tools (mypy, pyright) at type-check time rather than silently producing wrong results at runtime.

Install the extra for PostGIS support:

bash
uv sync --extra postgis

The search cascade is: exact match → fuzzy (pg_trgm) → ILIKE. CRS reprojection is done at query time via ST_Transform when the stored SRID differs from 4326.

See PostGISDataSource for the full constructor reference.

CompositeDataSource

Fan-out across multiple datasources. Sources are queried in order and results are accumulated until max_results is reached:

python
from etter.datasources import CompositeDataSource, SwissNames3DSource, IGNBDCartoSource

source = CompositeDataSource(
    SwissNames3DSource("data/swissnames3d/"),
    IGNBDCartoSource("data/bdcarto/"),
)
results = source.search("Geneva", type="settlement")

Type System

All datasources share a common type hierarchy for fuzzy type matching. Query with a category and it matches all concrete types within it:

CategoryConcrete types (examples)
waterlake, river, pond, spring, glacier
landformsmountain, peak, hill, pass, valley
settlementcity, town, village, hamlet
administrativecountry, canton, municipality, region
transporttrain_station, airport, road, bridge
buildingbuilding, tower, monument, fountain
amenityrestaurant, hospital, school, park
naturalcave, forest, nature_reserve
python
# Matches lake, river, pond, spring, ...
source.search("Morat", type="water")

# Matches only "lake"
source.search("Morat", type="lake")

See location_types for the complete hierarchy.

TypeMap

TypeMap is a type alias (dict[LocationTypeName, list[str]]) that restricts dictionary keys to known etter type names. Use it when defining a type_map for PostGISDataSource or when building your own datasource map:

python
from etter.datasources import TypeMap

# Editors auto-complete the keys; static checkers flag unknown keys.
my_map: TypeMap = {
    "lake": ["LAC", "RETENUE"],
    "river": ["COURS_EAU"],
}

LocationTypeName is the underlying Literal[...] type covering all concrete types (e.g. "lake", "mountain") and all category names (e.g. "water", "landforms"). Both are importable from etter.datasources.

Implementing a Custom Datasource

Any class with a search method and get_available_types method matching the protocol qualifies:

python
class MyDataSource:
    def get_available_types(self) -> list[str]:
        return ["city", "river", "lake"]

    def search(
        self,
        name: str,
        type: str | None = None,
        max_results: int = 10,
    ) -> list[dict]:
        # Return standard GeoJSON feature dicts
        ...

    def get_by_id(self, feature_id: str) -> dict | None:
        # Optional: return a feature by its unique ID
        ...

See GeoDataSource for the full protocol definition. The get_by_id() method is optional for custom datasources.

Released under the BSD-3-Clause License.