Skip to content

Datasources ​

A datasource resolves a location name (e.g. "Lausanne") to a geometry. etter ships with four 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.

SwissBoundaries3D ​

Wraps the swisstopo swissBOUNDARIES3D dataset (Shapefile). Covers Swiss administrative boundaries: cantons, municipalities, and districts.

python
from etter.datasources import SwissBoundaries3DSource

source = SwissBoundaries3DSource("data/swissboundaries3d/")
results = source.search("Bern", type="canton")

If given a directory, automatically loads and concatenates all three boundary shapefiles (BEZIRKSGEBIET, HOHEITSGEBIET, KANTONSGEBIET). Handles EPSG:2056 → WGS84 reprojection and drops 3D coordinates. 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. Every source is queried in order and the results are concatenated. max_results is passed to each source individually, so the merged list can contain up to max_results features per source:

python
from etter.datasources import CompositeDataSource, SwissNames3DSource, SwissBoundaries3DSource, IGNBDCartoSource

source = CompositeDataSource(
    SwissNames3DSource("data/swissnames3d/"),
    SwissBoundaries3DSource("data/swissboundaries3d/"),
    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
mountainmountain, peak
naturalcave, forest, nature_reserve
islandisland, peninsula
settlementcity, town, village, hamlet
administrativecountry, canton, municipality, region
transporttrain_station, airport, road, bridge
buildingbuilding, tower, monument, fountain
amenityrestaurant, hospital, school, park
infrastructurepower_plant, landfill, quarry
otherviewpoint, field_name, historical_site
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:
        # Return a feature by its unique ID
        ...

See GeoDataSource for the full protocol definition. get_by_id() is part of the protocol and is required if the datasource is used inside a CompositeDataSource, which calls it on each underlying source.

Released under the BSD-3-Clause License.