geolens-io

GeoLens

Community geolens-io
Updated

Self-hosted geospatial data catalog with semantic search (pgvector), OGC/STAC APIs, and map builder. Built on FastAPI, PostGIS, React, and MapLibre.

GeoLens

English | Español | Français | Deutsch

Your team's self-hosted spatial data hub: searchable, mappable, and shareable in one place.

GeoLens is an open-source spatial data hub for GIS and data teams: one place to find and work with data on infrastructure you control, with no telemetry. GeoLens itself phones home to nothing. (Features you opt into can make outbound calls: AI assist to your chosen OpenAI-compatible endpoint or Anthropic key, OAuth/OIDC sign-in, SMTP, basemap tiles, remote/S3 data sources, and off-site backups.) Upload files, create datasets in the browser, register tables already in GeoLens's own PostGIS database without copying them, import one-shot copies from WFS, ArcGIS FeatureServer, or OGC API Features, or reference remote STAC assets live. GeoLens records each dataset's origin, indexes catalog metadata with pg_trgm for fuzzy search out of the box (pgvector adds semantic ranking once you configure an embedding provider and enable semantic search), and serves OGC/STAC APIs that QGIS, ArcGIS, and MapLibre clients connect to natively. Compose, style, and share multi-layer maps right in the browser. Built on FastAPI and React. Deployed with one command.

No install required. Browse the sample catalog and maps without an account, or sign in with Google, GitHub, or Microsoft to try the map builder. Demo data may be wiped at any time.

CILicense: Apache 2.0Python: backend 3.13+ / SDK 3.10+PostgreSQL 18 + PostGIS 3.6OGC API

curl -fsSL https://getgeolens.com/install.sh | sh
# Open http://localhost:8080, then log in with the credentials you chose

The map builder: every Manhattan building extruded to its true roof height and colored by the era it was built, the subway threading beneath, built from open data with scripts/seed-showcase.py

[!NOTE]Early release. GeoLens is actively developed and maintained, and newlyopen-sourced. The self-hosted distribution is young and some features and APIsmay still change. Pleaseopen an issue if you hit a rough edge.

Documentation

Full user, admin, and API documentation lives at docs.getgeolens.com. The Reference table below links each guide.

Published artifacts

GeoLens is published through the standard package registries:

pip install geolens          # Python SDK
pip install geolens-cli      # CLI; installs the `geolens` command
pip install geolens-mcp      # MCP server for coding agents (read-only)
npm install @geolens/sdk     # TypeScript/JavaScript SDK

Prebuilt public API and frontend images are published to GitHub Container Registry:

docker pull ghcr.io/geolens-io/geolens-api:latest
docker pull ghcr.io/geolens-io/geolens-frontend:latest

The latest tag tracks the newest published stable release.

Why GeoLens?

Spatial data ends up scattered: shapefiles on shared drives, tables in database schemas, rasters in cloud buckets, metadata in spreadsheets. Finding the right dataset means asking Slack or grepping file servers. Sharing it means exporting, emailing, and hoping the CRS matches.

GeoLens replaces that workflow:

  • One data hub: upload files, create datasets, register tables already in GeoLens's database, import feature-service snapshots, or reference remote STAC assets — then search and preview them together
  • Source state, not guesswork: see how each dataset entered the catalog, when it was last refreshed or checked, how its last refresh compares with its declared cadence (fresh, due, overdue, or unknown), and whether a remote Service or STAC origin is still reachable
  • Works with your tools: OGC API Features/Records, STAC API 1.0, direct tile URLs for QGIS, ArcGIS, and MapLibre
  • No lock-in: your catalog and the copies GeoLens manages stay on infrastructure you control and leave through open formats. Vector datasets export to GeoPackage, GeoJSON, Shapefile, CSV, or GeoParquet; rasters download as Cloud-Optimized GeoTIFF; and any OGC API client reads the catalog directly
  • Semantic and spatial search: pg_trgm fuzzy matching out of the box; add an embedding provider and enable semantic search to rank datasets by meaning (pgvector)
  • Built-in map builder: compose multi-layer maps, style them, and share via public link or embeddable iframe
  • AI-assisted (optional): chat with your maps, auto-generate descriptions, search by natural language. Bring an OpenAI-compatible endpoint or Anthropic key, or skip it entirely

See it in action

The examples below use a JWT bearer token. Mint one against the local stack (the login endpoint accepts an OAuth2 password form, so use -d with form fields, not JSON). Substitute your admin username and the password from .env (grep '^GEOLENS_ADMIN_PASSWORD=' .env):

TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login/ \
  -d 'username=admin&password=<your-admin-password>' | jq -r '.access_token')

Semantic search takes a one-time admin setup: an embedding provider and the AI + Semantic Search toggles in the admin AI settings, plus an embedding backfill for data ingested before setup (the search guide walks through it). Once that's on, search datasets by meaning instead of exact keyword matches:

# Semantic search ranks by meaning: "hydrology" surfaces the lake and river
# network datasets whose titles never mention the word
curl "http://localhost:8080/api/search/datasets/?q=hydrology&limit=3" \
  -H "Authorization: Bearer $TOKEN" | jq '.features[].properties.title'

One search-endpoint behavior to know when consuming it programmatically: thefirst page augments the dataset results with up to five matching collections,so numberReturned can exceed limit on page 0 only. That is deliberate, nota bug — limit still bounds the number of datasets per page.

Every dataset is also a standard OGC API Features endpoint:

# Grab a public collection id from the catalog. Search anonymously (no token) so
# the id is one anyone can read, matching the unauthenticated items request below.
CID=$(curl -s "http://localhost:8080/api/search/datasets/?q=countries&limit=1" \
  | jq -r '.features[0].id')

# GeoJSON features with a bbox filter, works in QGIS, ArcGIS, any OGC client
curl "http://localhost:8080/api/collections/$CID/items?bbox=-10,35,30,60&limit=5"

PostGIS and pgvector share one database, so with semantic search enabled you can rank datasets by meaning inside a spatial window in a single query. See the search guide for how semantic and spatial search work together.

Connect directly from QGIS: Layer > Add WFS / OGC API Features and point at http://localhost:8080/api/.

The same endpoints from the tools you already use: geolens-examples holds single-file MapLibre, Leaflet, OpenLayers and ArcGIS JS pages, QGIS and DuckDB walkthroughs, both GeoLens SDKs, a semantic catalog search, a STAC browser, a saved-map embed, a Python/GeoPandas analysis, a catalog-as-code manifest for the CLI, and an MCP setup. The read-only ones run against the live demo, and CI replays them there on every push and once a week, so what you copy is code that worked this week. Browse the gallery.

Features

Each example above has a full guide in the docs. What GeoLens reads, writes, and exposes:

Data ingestion and export

  • Five source modes: Uploaded and Created data are managed locally; Register Table serves an existing table in GeoLens's own PostGIS database in place; Service imports are one-shot local copies; STAC datasets keep a live reference to the remote asset
  • Vector: Shapefile, GeoPackage, GeoJSON, GeoParquet, CSV, XLSX
  • Raster: GeoTIFF and Cloud-Optimized GeoTIFF (COG) with automatic conversion
  • Mosaics: VRT-based raster mosaics from multiple source files
  • Export: GeoJSON, Shapefile, GeoPackage, CSV, with CRS reprojection
  • Source state: origin and last-refreshed/last-checked timestamps, cadence-based source freshness, and on-demand health checks for Service and STAC origins
  • Provenance tracking and metadata editing

Analysis

  • Buffer (metres, kilometres, feet, or miles), centroid, clip by a drawn area or by another polygon layer, and dissolve with an optional group-by column; spatial join and select by location match features on intersection, measure adds area_sqm and length_m columns, and intersect writes the pairwise overlay with attributes from both sides
  • All operations preview on the map except dissolve, which is materialize-only; previews are capped at 500 features. Create dataset then runs any of the eight over every feature as a background job, within per-operation source limits (250k features for dissolve, 500k for buffer)
  • The output is an ordinary vector dataset — styleable, exportable, and served through the OGC API endpoints like any other
  • The chat assistant can run buffer, centroid, and layer-based clip previews on request

Standards and interop

  • OGC API - Features and OGC API - Records; STAC API 1.0 catalog endpoint
  • Direct tile URLs and per-user API keys for QGIS, ArcGIS, MapLibre, and any OGC client
  • Vector tiles omit attribute columns below zoom 10 to keep low-zoom tiles small; add the cols=<column>,<column> query parameter to a tile URL to opt specific columns in at every zoom (names are validated against the dataset's columns, unknown names are dropped)
  • JWT + OAuth 2.0/OIDC, RBAC with per-dataset permissions
Security
  • JWT authentication with refresh tokens
  • API key management per user
  • OAuth 2.0 / OIDC support (Google, Microsoft, generic providers)
  • Role-based access control (RBAC) with per-dataset permissions
  • Self-serve registration is off by default; when enabled with SMTP verification,registration email delivery is uniform for new and colliding submissions
  • Audit logging for all administrative actions
  • Internationalization: English, Spanish, French, German

Screenshots

Find: search by meaning. "Tallest peaks in Europe" finds the Matterhorn terrain model even though no result contains any of those words, alongside type, location, and temporal filters

Inspect: every dataset gets a map preview, schema stats, and typed metadata. Here, 6,000 years of significant volcanic eruptions from NOAA NCEI

Ask your data: question a dataset in natural language. "How many meteorites were seen falling versus found later?" comes back with the answer, the counts (1,096 vs 31,090), and a one-click jump into the builder

Build: compose multi-layer maps in the browser with a drag-orderable layer stack and per-layer editors (here: the Matterhorn as a 3D terrain mesh from swissALTI3D lidar)

Ask AI: edit maps in natural language. "Label the volcanoes with their names" adds readable labels to the Restless Earth map (optional: bring an OpenAI-compatible endpoint or Anthropic key)

Operate: the built-in admin plane covers live health, usage, users, jobs, audit log, and AI status — nothing extra to stand up

Quick start

Prerequisites: Docker Engine 24+ and Docker Compose v2. The bundled stackships PostgreSQL 18. If you point GeoLens at an externally managed database, itmust be PostgreSQL 13+ (for gen_random_uuid()) with pgvector 0.5+ (forHNSW semantic-search indexes), plus PostGIS, pg_trgm, and unaccent. The API andworker run in containers (Python 3.14 bundled, no host Python needed). Theoptional CLI runs on your host and requires Python 3.11+; the Python SDK andseed scripts require Python 3.10+.

The one-line install pulls the prebuilt, version-pinned images and starts the stack:

curl -fsSL https://getgeolens.com/install.sh | sh

Prefer to read the script or build from source first? Clone the repo and run the same installer. It builds the images locally instead of pulling them:

git clone https://github.com/geolens-io/geolens.git
cd geolens
bash scripts/install.sh

Either way, scripts/install.sh copies .env.example to .env, generates a JWT signingsecret, sets up admin credentials, and runs docker compose up -d. The admin usernamedefaults to admin; the admin password is auto-generated as a strong random value(written to .env, never printed to your terminal) unless you supply your own.For unattended installs, set GEOLENS_ADMIN_USERNAME and GEOLENS_ADMIN_PASSWORD in theenvironment before running and the prompts are skipped. Re-running the script is idempotent:existing values in .env are preserved.

Wait about 60 seconds for services to start, then open http://localhost:8080.Log in with your admin username and the generated password (retrieve it withgrep '^GEOLENS_ADMIN_PASSWORD=' geolens/.env — the one-line installer clonesinto geolens/ under the directory you ran it from; inside a source checkoutit's just .env).

Verify all services are healthy:

docker compose ps

First-run notes: the one-line install pulls prebuilt images and is up in abouta minute (only the small PostGIS + pgvector database layer builds locally). Cloningand running bash scripts/install.sh instead builds every image from source:5-10 minutes on the first run (GDAL + Postgres extensions + the frontend bundle);subsequent starts settle in ~60 seconds either way. If ports 5434/8001/8080 arealready taken, change DB_PORT, API_PORT,or FRONTEND_PORT in .env. For port conflicts, stuck startups, out-of-memory,and migration warnings, see the Troubleshooting guide.

For production deployment, see the Install Guide. A community-maintained Kubernetes Helm chart lives in the separate geolens-deployments repo.

Verify the installer

Each GitHub Release attaches a SHA256SUMSfile generated by CI alongside install.sh. To confirm a downloaded installer was not tamperedwith before running it, download both assets from the same release and place them in the samedirectory, then run:

# Linux / Windows WSL
sha256sum -c SHA256SUMS

# macOS
shasum -a 256 -c SHA256SUMS

A passing check prints install.sh: OK.

Upgrading

To upgrade a prebuilt install, run ./scripts/upgrade.sh from your installdirectory. It backs up the database, pulls the new images, runs migrationsbehind a health gate, and prints a rollback recipe if anything fails. SeeUPGRADING.md for the prebuilt and source-build flows plusrollback, or the online Upgrade Guide.

Add your first dataset

The repo ships a small city-parks.geojson. Upload and publish it in one command with the GeoLens CLI:

pip install geolens-cli                              # installs the `geolens` command
geolens login http://localhost:8080/api              # use your admin username + password
geolens publish examples/manifests/first-catalog/city-parks.geojson --name "City Parks"

geolens publish runs the upload → preview → commit ingest flow and prints the new dataset's URL. One command takes a local file to a published, mappable dataset.

For repeatable, multi-dataset catalogs, describe your sources in a manifest (geolens.yaml) and apply it with geolens apply. Manifest sources are referenced by HTTP(S) URL, S3 URI, or a path already staged on the server; the examples in examples/manifests/ are templates to adapt. Scaffold a fresh one with geolens init and edit it for your sources:

geolens init                       # writes geolens.yaml in the current directory
geolens validate geolens.yaml      # local schema check, no API call
geolens apply geolens.yaml         # validates + applies via /ingest/manifest/apply

See the CLI guide for the full manifest schema, source kinds, and CI integration patterns.

Seed data

scripts/seed-showcase.py builds seven showcase maps from public open data: a globaltectonics story over real ocean-floor relief, the Manhattan 3D skyline colored byconstruction era (the hero above), Atlantic hurricane tracks since 1950, clusteredmeteorite falls, the Matterhorn in 2 m lidar 3D terrain, by-reference Sentinel-2imagery of New York, and a hurricane-exposure map computed in place from the stormtracks with buffer, intersect and dissolve:

pip install httpx
python scripts/seed-showcase.py --username admin --password "$(grep '^GEOLENS_ADMIN_PASSWORD=' .env | cut -d= -f2-)"

Requires internet access to the upstream open-data sources. Seescripts/README.md for flags (--no-terrain, --prune, …).

Architecture

GeoLens is a small set of services around a single PostgreSQL/PostGIS database: theAPI serves the catalog, search, and OGC/STAC endpoints; a worker handles ingestion;and Titiler serves raster tiles from object storage.

flowchart TB
    B["Browser: React + MapLibre app"]
    OGC["QGIS · ArcGIS · OGC/STAC clients"]

    NG["Nginx reverse proxy<br/>serves the React build, routes /api and tiles"]

    subgraph Application
      API["FastAPI<br/>catalog · semantic search · OGC/STAC · vector tiles"]
      W["Worker<br/>GDAL/ogr2ogr ingestion"]
      TT["Titiler<br/>COG raster tiles"]
    end

    subgraph store [Data and storage]
      PG[("PostgreSQL 18<br/>PostGIS · pgvector · pg_trgm<br/>+ Procrastinate queue")]
      OBJ[("Object storage<br/>local files or S3/MinIO")]
      CACHE[("Valkey cache")]
    end

    B --> NG
    OGC --> NG
    NG --> API
    NG --> TT
    API <--> PG
    API --> OBJ
    API -. tile/query cache .-> CACHE
    PG == job ==> W
    W --> PG
    W --> OBJ
    TT --> OBJ
Component Technology
Frontend React 19, Vite, MapLibre GL v5, TanStack Query, Tailwind CSS
Backend API FastAPI (Python), GDAL/ogr2ogr, Procrastinate (task queue)
Raster Tiles Titiler (COG tile server)
Object Storage MinIO (S3-compatible, local dev) or any S3 provider
Cache Valkey (tile and query cache)
Database PostgreSQL 18 + PostGIS 3.6 + pgvector + pg_trgm (minimum: PostgreSQL 13, pgvector 0.5)
Reverse Proxy Nginx (production) / Vite dev proxy (development)

Configuration

All configuration is managed through environment variables in .env. See the Configuration Reference for the full list of options with defaults and descriptions.

Connection pool budget

GeoLens ships tuned for a single PostgreSQL instance: the API, worker, and adminpools fit within 70 of 80 max_connections out of the box (Postgresmax_connections is set to 80), sized by DB_POOL_SIZE (pool_size) andDB_MAX_OVERFLOW (max_overflow, default 3). SeeConnection Pool Tuningfor the per-process budget and how to raise the ceiling.

Backups

Automated, scheduled backups run by default. You do not need a --profile backup flag.The backup service starts alongside api, worker, and db on everydocker compose up and runs pg_dump on a daily/weekly schedule alongside anarchive of the object-storage staging volume, so a restore reproduces a workinginstance (DB + uploaded files).

Off-site (S3) upload is additionally gated on BACKUP_S3_ENABLED=true. Thebuilt-in uploader signs requests with AWS Signature V4 (awscli), compatiblewith Cloudflare R2, modern AWS S3, and MinIO. A failed upload surfaces a visibleERROR in container logs (not a swallowed warning), so silent offsite backuploss is detectable immediately.

For day-2 operations, restore procedures, and incident response, seeRUNBOOK.md. For provider-specific configuration options, seeBackups & Restore.

Monitoring

The API and worker export Prometheus metrics out of the box (HTTP rate/latency/errors, job-queue depth, DB pool, tile-cache). Reference scrape config, alertrules, and a Grafana dashboard ship in infra/monitoring/;see RUNBOOK.md §4 for the setup steps.

Reference

Guide Description
Install Guide Step-by-step deployment with Docker Compose
Upgrade Guide Upgrading between versions with rollback procedures
Configuration Reference All environment variables and their defaults
Admin Guide User management, datasets, system health
Self-host on AWS, GCP, or DigitalOcean Managed database, object storage, and cache deployment guides
CLI & Manifests Publish files and manage catalogs with the geolens CLI
API Reference Auto-generated reference at docs.getgeolens.com; interactive Swagger UI at /api/docs when running
Manifest examples Template geolens.yaml manifests to adapt: public-cog (remote COG), url-source, s3-source, publication-states
Client examples Runnable browser, QGIS, DuckDB, SDK, CLI, embed, Python, and MCP examples; the read-only ones are verified against the live demo in CI (gallery)

Community

  • GitHub Discussions: questions, ideas, show and tell
  • Support: where to ask for help and how problems get routed
  • Contributing Guide: development setup, code style, and PR guidelines

Known limitations

  • Single PostgreSQL instance, with no built-in high availability or clustering.
  • GeoLens is designed for one organization per self-hosted deployment.
  • Terrain rendering assumes DEM units are in meters; datasets in other vertical units may render exaggerated.
  • The self-hosted distribution is young and some features and APIs may still change (see the Early release note above).

License

GeoLens is licensed under the Apache License 2.0. The GeoLens name, logo, and brand assets are not covered by this license. See TRADEMARKS.md. Third-party sample-data attribution is in THIRD_PARTY_DATA.md.

Project policies: governance · maintainers · contributing · security · release process · egress & air-gap.

MCP Server · Populars

MCP Server · New

    weed33834

    🛡️ AgentSeed

    AgentSeed - anti-hallucination guardrails for AI coding agents: hybrid Skill + MCP plugin (Agent Plugins 1.0.0) that forces spec-driven development and verifies code before it is marked done.

    Community weed33834
    geolens-io

    GeoLens

    Self-hosted geospatial data catalog with semantic search (pgvector), OGC/STAC APIs, and map builder. Built on FastAPI, PostGIS, React, and MapLibre.

    Community geolens-io
    leonardosepulvedat

    MCP n8n Server

    Complete n8n API integration for Claude Desktop and Cursor - 100 workflow templates with intelligent matching

    Community leonardosepulvedat
    maximhq

    Bifrost AI Gateway

    The Fastest LLM Gateway with built in OTel observability and MCP gateway

    Community maximhq
    crisnahine

    rails-ai-context

    45 MCP tools that give AI coding agents ground truth about your Rails app: schema, models, routes, controllers, views, jobs, conventions. Works with Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI. MCP or CLI, in-Gemfile or standalone, and it still answers when the app can't boot.

    Community crisnahine