Open-source biodiversity monitoring tool powered by satellite imagery and open data.
BioTrack aggregates free data sources (Sentinel-2, iNaturalist, GBIF, OpenStreetMap) to provide ecological health assessments for any user-defined zone. The interface is designed to be accessible to everyone, including children.
- Interactive map — Full-screen MapLibre GL map with OpenFreeMap tiles
- Tile grid selection — Click on 10×10 km tiles (max 4) to define analysis zones, latitude-adjusted grid visible at zoom >= 9
- Location search — Geocoding via Nominatim (OpenStreetMap)
- Zone management — Save, list, view, and delete zones via REST API
- Vegetation index (NDVI) — Monthly NDVI analysis from Sentinel-2 satellite imagery with temporal slider, raster overlay, and animated GIF export
- Species diversity — Parallel aggregation of observations from iNaturalist and GBIF, with colored map markers, species cards, expandable taxonomy details, and category filters
- French UI — Fully localized interface with vulgarized labels accessible to all audiences
| Component | Technology |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript, Tailwind CSS 4 |
| Maps | MapLibre GL JS, Turf.js (area, bbox, union) |
| Icons | Lucide React |
| Backend | FastAPI, Python 3.12 |
| Database | PostgreSQL + PostGIS |
| ORM | SQLAlchemy 2 + GeoAlchemy2 |
| Migrations | Alembic |
| Satellite | Copernicus CDSE (Sentinel-2 L2A), Rasterio, NumPy |
| Imaging | Pillow (GIF generation) |
| HTTP | httpx (external API calls) |
| Data sources | Sentinel-2, iNaturalist, GBIF, OpenStreetMap |
| Tool | Version | Notes |
|---|---|---|
| Node.js | >= 20 | JavaScript runtime |
| pnpm | >= 10 | Package manager for the frontend |
| Python | >= 3.12 | Backend runtime |
| PostgreSQL | >= 14 | Database with PostGIS extension |
git clone https://github.com/Teyk0o/biotrack.git
cd biotrackInstall PostgreSQL and PostGIS:
# Ubuntu / Debian
sudo apt update
sudo apt install postgresql postgresql-contrib postgis
# Start the service
sudo systemctl start postgresql
sudo systemctl enable postgresqlConnect as the postgres superuser and create the database:
sudo -u postgres psqlCREATE USER biotrack WITH PASSWORD 'biotrack';
CREATE DATABASE biotrack OWNER biotrack;
\c biotrack
CREATE EXTENSION IF NOT EXISTS postgis;For tests, create a separate database:
CREATE DATABASE biotrack_test OWNER biotrack;
\c biotrack_test
CREATE EXTENSION IF NOT EXISTS postgis;
\q- Download and install PostgreSQL (includes pgAdmin)
- During installation, note the superuser password you set
- Install PostGIS via the Stack Builder included with PostgreSQL, or download it from postgis.net
- Open pgAdmin or SQL Shell (psql) and run:
CREATE USER biotrack WITH PASSWORD 'biotrack';
CREATE DATABASE biotrack OWNER biotrack;
\c biotrack
CREATE EXTENSION IF NOT EXISTS postgis;For tests:
CREATE DATABASE biotrack_test OWNER biotrack;
\c biotrack_test
CREATE EXTENSION IF NOT EXISTS postgis;
\qcd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"cd backend
python -m venv .venv
.venv\Scripts\activate
pip install -e ".[dev]"Windows note: If you encounter a
UnicodeDecodeErrorwithpsycopg2, install psycopg v3 instead:pip install "psycopg[binary]>=3.1.0"Then use this driver in your
.env:DATABASE_URL=postgresql+psycopg://biotrack:biotrack@localhost:5432/biotrack
Create a backend/.env file:
DATABASE_URL=postgresql://biotrack:biotrack@localhost:5432/biotrack
COPERNICUS_CLIENT_ID=your_client_id
COPERNICUS_CLIENT_SECRET=your_client_secretCopernicus credentials are required for NDVI analysis. Create a free account at dataspace.copernicus.eu and register an OAuth client.
alembic upgrade head
uvicorn app.main:app --reloadThe API is now available at http://localhost:8000. Interactive docs at http://localhost:8000/docs.
cd app
pnpm installcd app
pnpm installWindows note: If you encounter
ERR_PNPM_VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF:Remove-Item -Recurse -Force node_modules $env:CI = "true" pnpm install
Copy the example file and adjust if needed:
cp .env.local.example .env.localDefault values:
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_MAP_STYLE=https://tiles.openfreemap.org/styles/libertypnpm devOpen http://localhost:3000.
- Open http://localhost:3000 — you should see the full-screen map
- Zoom in (level 9+) to see the tile grid appear
- Select 1 to 4 tiles and click Enregistrer to create a zone
- Click a zone in the sidebar — the NDVI panel appears at the bottom, the biodiversity panel on the right
cd backend
pytestRequires a running PostgreSQL instance with a
biotrack_testdatabase. SetTEST_DATABASE_URLto override the default connection string.
cd app
pnpm lint
pnpm build| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check |
| Method | Path | Description |
|---|---|---|
POST |
/api/zones |
Create a zone (GeoJSON Polygon/MultiPolygon) |
GET |
/api/zones |
List zones (paginated: skip, limit) |
GET |
/api/zones/{id} |
Get a zone by ID |
PUT |
/api/zones/{id} |
Update a zone |
DELETE |
/api/zones/{id} |
Delete a zone |
| Method | Path | Description |
|---|---|---|
POST |
/api/zones/{id}/ndvi |
Trigger NDVI analysis (start_date, end_date, interval) |
GET |
/api/zones/{id}/ndvi |
Get cached NDVI results (optional date range) |
GET |
/api/zones/{id}/ndvi/tile?date=YYYY-MM-DD |
Get NDVI PNG tile |
POST |
/api/zones/{id}/ndvi/preload |
Preload 12 months (Server-Sent Events progress) |
GET |
/api/zones/{id}/ndvi/animation?speed=500 |
Generate animated GIF |
DELETE |
/api/zones/{id}/ndvi |
Delete cached NDVI results |
| Method | Path | Description |
|---|---|---|
GET |
/api/zones/{id}/biodiversity |
Fetch species observations (source, limit, category) |
biotrack/
├── app/ # Next.js frontend
│ ├── app/ # Pages and layouts
│ ├── components/map/ # Map UI components
│ │ ├── map-view.tsx # MapLibre GL initialization
│ │ ├── map-context.tsx # React Context (map state)
│ │ ├── tile-grid-selector.tsx # Tile grid overlay + selection
│ │ ├── ndvi-panel.tsx # NDVI temporal slider
│ │ ├── biodiversity-panel.tsx # Species list + map markers
│ │ ├── zone-list.tsx # Sidebar zone management
│ │ ├── zone-save-dialog.tsx # Zone creation dialog
│ │ └── location-search.tsx # Geocoding search bar
│ ├── hooks/ # Custom React hooks
│ │ ├── use-tile-grid.ts # Grid display + selection logic
│ │ └── use-debounce.ts # Debounce utility
│ ├── lib/ # API client and services
│ │ ├── api.ts # Generic fetch wrapper
│ │ ├── zones.ts # Zone CRUD
│ │ ├── ndvi.ts # NDVI data + tile URLs
│ │ ├── biodiversity.ts # Biodiversity API calls
│ │ └── grid.ts # Tile grid math
│ └── types/ # TypeScript interfaces
│ ├── zone.ts # Zone types
│ ├── ndvi.ts # NDVI types
│ └── biodiversity.ts # Species + taxonomy types
│
├── backend/ # FastAPI backend
│ ├── alembic/ # Database migrations
│ ├── app/
│ │ ├── api/routes/ # API endpoints
│ │ │ ├── health.py # Health check
│ │ │ ├── zones.py # Zone CRUD (5 endpoints)
│ │ │ ├── ndvi.py # NDVI analysis (6 endpoints)
│ │ │ └── biodiversity.py # Biodiversity fetch
│ │ ├── core/ # Config + database engine
│ │ ├── models/ # SQLAlchemy ORM models
│ │ │ ├── zone.py # Zone model
│ │ │ ├── ndvi_result.py # NDVI statistics
│ │ │ └── ndvi_tile_cache.py # Cached PNG tiles (BLOB)
│ │ ├── schemas/ # Pydantic validation schemas
│ │ ├── services/ # Business logic
│ │ │ ├── zone_service.py # Zone operations
│ │ │ ├── ndvi_service.py # NDVI analysis + caching
│ │ │ ├── biodiversity_service.py # iNaturalist + GBIF (parallel)
│ │ │ ├── sentinel_hub_service.py # Sentinel Hub API
│ │ │ ├── copernicus_auth_service.py # OAuth token management
│ │ │ ├── gif_service.py # Animated GIF generation
│ │ │ └── exceptions.py # Custom exceptions
│ │ └── utils/ # Shared utilities
│ │ └── geometry.py # Geometry bbox helper
│ └── tests/ # pytest test suite
│
├── assets/ # Logo and screenshots
├── CONTRIBUTING.md # Contribution guidelines
└── LICENSE # MIT License
If you encounter UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 when connecting to PostgreSQL on Windows, install psycopg v3:
pip install "psycopg[binary]>=3.1.0"Then update backend/.env:
DATABASE_URL=postgresql+psycopg://biotrack:biotrack@localhost:5432/biotrackIf tables were created manually, Alembic doesn't know about them. Stamp the current state:
alembic stamp headDelete node_modules and reinstall:
Linux / macOS:
rm -rf node_modules
CI=true pnpm installWindows (PowerShell):
Remove-Item -Recurse -Force node_modules
$env:CI = "true"
pnpm installSee CONTRIBUTING.md for guidelines.

