Open-source platform for opportunistic cetacean observation: a mobile app for on-board recording, a web frontend for review & analysis, and a backend built around the scientific workflow of M.E.E.R. e.V. off La Gomera.
User manual: EN · DE · ES | Data guide: EN · DE · ES | Wiki · API docs
- What MWPA does
- Quick start
- Architecture
- Features
- Tech stack
- Data & scientific use
- Documentation
- Project status
- Contributing & community
- Citing MWPA
- Supervisors
- License
Whale- and dolphin-watching tours generate huge amounts of valuable observation data. Most of it used to disappear in paper logbooks or a sprawling Access database. MWPA brings that data into one place so it can be reviewed, exported, and analysed.
The platform sits on top of the long-running research programme of M.E.E.R. e.V. (Marine Education, Environment, Research e.V.) for cetacean monitoring off La Gomera (Canary Islands, Spain).
Three audiences, one dataset:
- Observers on board record sightings (species, group size, behaviour, photos, GPS track) on the mobile app — even without network.
- Reviewers in the office verify, correct and complete records via the web frontend.
- Researchers / authorities export curated subsets for analysis or as regulatory deliverables (e.g. AROC).
Prerequisites: Node.js 18+, npm, Docker (for MariaDB).
# 1. Clone with submodules (the in-house bambooo widget library is one)
git clone --recurse-submodules https://github.com/stefanwerfling/mwpa.git
cd mwpa
# 2. Bring up the database
docker compose up -d mariadb
# 3. Install everything (npm workspaces)
npm install
# 4. Build: schemas -> backend -> frontend
npm run compile
# 5. Start the backend (binds https://localhost:3000 with a self-signed cert)
node backend/dist/main.js --config=config.dev.jsonOpen the web app at https://localhost:3000 (accept the self-signed certificate). The mobile app lives in a separate repository.
For deployment, configuration, the IM2020 legacy importer and the AROC report workflow see the project wiki.
┌──────────────────────┐ sync ┌──────────────────────┐
│ Mobile App (Flutter)│ ─────────► │ │
│ offline-first │ │ │
└──────────────────────┘ │ Express + figtree │
│ (TypeScript) │
┌──────────────────────┐ REST │ │
│ Web Frontend (SPA) │ ◄────────► │ /json/* /mobile/* │
│ AdminLTE + bambooo │ │ │
└──────────────────────┘ └──────────┬───────────┘
│ TypeORM
▼
┌──────────────────────┐
│ MariaDB │
│ observations, tracks│
│ derived analytics │
└──────────────────────┘
▲
│ enrich
┌──────────┴───────────┐
│ Background services │
│ depth · weather · │
│ movement analytics │
└──────────────────────┘
The repo is an npm workspaces monorepo with three packages that build in order:
| Package | Role |
|---|---|
schemas/ |
Runtime types & validators (using vts), generated from schemas/schema.json via vtseditor. Shared by backend & frontend. |
backend/ |
Node.js Express app on the figtree framework — services, repositories, REST routes. |
frontend/ |
Browser SPA — webpack bundle, AdminLTE 4 + jQuery + Bootstrap 5 + the in-house bambooo v2 widget library. |
- Offline-first sighting recording — species, group size, behaviour, photos
- Continuous GPS tracking during the tour
- Background sync once a network is available
- Sighting list with multi-column sort, in-header search, infinite scroll
- Map view with sighting markers + computed movement tracks (toggleable layer)
- Dashboard with crossfilter (date, species, organisation, vehicle, driver)
- Per-sighting analytics, year-on-year comparison, behavioural summaries
- Edit modal with photo gallery and validation against the shared schema
- Users, groups & roles (RBAC via
rbac-simple) — create/rename/delete custom roles and assign a tree of per-area rights (*_read/*_write/*_deleteacross sightings, tours, species, vehicles, users, organisations, exports, admin tools, …) through a checkbox dialog. Every authenticated REST route is gated by its required right (403 on miss); the web sidebar and a page-level navigation guard hide/deny what the user can't access. The built-inroot(full access) anduser(read + mobile-capture defaults) roles are seeded on boot and can't be renamed or deleted - Vehicles, drivers, organisations, species & species groups
- Encounter categories, behavioural states, external receivers (AROC)
- Orphan tracks admin page — buckets of pending GPS points whose parent tour never landed (typical when crew/boat is corrected after the fact). Includes a map preview of the actual points so the admin can decide assign-vs-delete before re-pointing them at a different
SightingTour. - Bulk re-derivation of analytics (e.g. movement tracks, sighting × earthquake correlations)
- Historical data import — a one-time importer (
backend/src/Scripts/ImportSightingOld.ts, run asnode backend/dist/Scripts/ImportSightingOld.js --excel <file> [--commit]after compiling) loads the hand-maintained pre-app records (a legacy spreadsheet export kept under the gitignoreddata/dir, ~14.5k sightings back to 1995) into a separate read-onlysighting_oldtable. It runs in two phases: a conflict gate first aborts the whole run (importing nothing) if any Excel row collides with an existing app sighting on date + species + time (±30 min), then a dry-run-by-default import maps every column, resolves species names to the app's species list, and bulk-inserts. See doc/sighting-old-import-mapping.md for the full column mapping and species synonym table.
- Excel export with column picker & coordinate-format selector (decimal / DMS / DM / all) — includes an Info sheet documenting the active filter
- AROC office-report export (filled
PLANTILLA_AVISTAMIENTOS_AROC.xlsx, one file per boat per half-year) - Printable Data Page (Map + Analytics + Year comparison) via the browser's PDF export
- Derived movement tracks per sighting: distance, speed, heading, turning-angle, with GPS-jump flag
- Per-sighting environmental enrichment — bathymetry, SST, chlorophyll-a, sea-surface salinity, sea-level anomaly, UV index (Open-Meteo + ERDDAP + NASA POWER), fishing effort (Global Fishing Watch)
- Seismic correlation — hourly cron pulls earthquakes from USGS + EMSC (FDSNWS) for every org's tracking area, joins to nearby sightings within ±14 days / 200 km, exposes them on the per-species profile and an interactive impact-analysis page (pick a window — ±24 h / 3 d / 7 d / 14 d — see affected sightings, their movement tracks, and analytics buckets by species / behaviour / encounter / hours-offset)
- Cross-species regression matrix with Simpson's-paradox visibility (per-species + pooled regression lines on the same plot)
- Effort-only tours — legs without sightings are still preserved server-side from the uploaded GPS track, so they appear in sighting-rate and survey-effort analyses
- Historical hand-recorded sightings (1995 → pre-app, imported into
sighting_old) are browsable in the same sightings list: a "show old data" filter toggle mixes them in by date, each flagged read-only with an "ALT" badge. ~83 % carry coordinates and so also appear on the map; the rest are still listed with species, date and counts
| Layer | Technology |
|---|---|
| Language | TypeScript (NodeNext ESM in backend, webpack bundle in frontend) |
| Backend framework | figtree (Express + service-manager + DBRepository + RouteLoader) |
| ORM | TypeORM on MariaDB |
| Schema validation | vts generated from JSON via vtseditor |
| Auth | Express session + custom RBAC (mwpa_schemas rights) |
| Frontend UI | AdminLTE 4, jQuery, Bootstrap 5, bambooo v2 widgets |
| Maps | OpenLayers + ol-layerswitcher, OSM tile cache |
| Analytics | d3 + dc.js (crossfilter), in-page charts and dashboards |
| Office exports | node-xlsx (sightings list), JSZip direct-XML patch (AROC template) |
| External data | Open-Meteo + NASA POWER (weather/UV), ERDDAP (SST/chl-a/SSS/SLA/currents), Global Fishing Watch 4Wings (fishing effort), USGS + EMSC FDSNWS (earthquakes), AISStream.io (live vessel positions) |
| Mobile | Flutter (separate repo) |
| Local infra | Docker Compose (MariaDB) |
A separate guide describes how data is collected, what every field means, how the derived movement tracks are computed, and how to use the dataset for scientific analysis — including a reproducibility checklist and an honest list of limitations.
- English — Data collection & scientific use
- Deutsch — Datenerhebung & wissenschaftliche Nutzung
- Español — Recogida de datos y uso científico
Topics covered:
- Data sources (mobile app, web frontend, importers, background services)
- Domain model — tour ▸ sighting ▸ tracking ▸ extended ▸ movement
- Field-by-field reference
- Coordinate & time conventions (WGS84 / UTC / local-time caveats)
- Derived movement tracks & quality flags
- Recommended analyses & their limitations
- Reproducibility checklist
- Citation guidance
| Resource | What's inside |
|---|---|
| User manual — EN · DE · ES | Page-by-page walkthrough of the web frontend with screenshots |
| Data & scientific use — EN · DE · ES | Field reference, derived analytics, reproducibility checklist |
| Project wiki | Installation, deployment, IM2020 importer, AROC workflow |
| REST API | Full endpoint reference (Stoplight) |
| Database schema | ER diagram of all entities |
| CLAUDE.md | Developer onboarding (used by Claude Code & humans) |
- Active — under continuous development. See issues for the current backlog.
- Backend migration — completed: the backend runs entirely on the figtree framework (
backend/); the legacy routing-controllers code has been removed. - Test suite — not yet. Contributions welcome.
The mobile app (Flutter) is in production with M.E.E.R. e.V., so any change touching /mobile/* REST endpoints must keep the wire format backward-compatible.
You're welcome to:
- File issues / feature requests on GitHub
- Send pull requests — please follow the existing ESLint rules (4 spaces, single quotes, strict TS) and the CLAUDE.md conventions for schemas, routes and migrations
- Chat on Gitter
- Support the underlying NGO at M.E.E.R. e.V. (donations, membership, on-site volunteering)
- Sponsor development: Buy me a coffee · Liberapay
|
Scientific partner |
"Working with the people was wonderful and important." MWPA exists because of the volunteers of M.E.E.R. e.V., who collect this data under real-world conditions tour after tour. Please support their work. |
If you publish work based on data from this system, please:
- Acknowledge M.E.E.R. e.V. as the data provider — m-e-e-r.de.
- Cite the collection period (start / end date of the included sightings), not the tool version.
- Contact M.E.E.R. e.V. for a project-specific data-use agreement before publication.
See the data & scientific use guide for the recommended attribution wording and the reproducibility checklist.
- Christina Sommer — M.E.E.R. e.V.
- Stefan Werfling — Pegenau GmbH & Co. KG
This project is licensed under the GNU General Public License v3.0. See the LICENSE file for the full text.

