GeoViable is deployed as a three-container Docker Compose application running on an Oracle Cloud Always Free ARM instance. All external traffic passes through Cloudflare before reaching the host, where Nginx terminates SSL and routes requests — API calls to the FastAPI container, everything else to the pre-compiled React static files. The PostGIS database sits entirely within the internal Docker network and is never exposed to the internet. A cron job running inside the API container handles automated monthly layer updates without any external scheduler.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/danizd/geoviable/llms.txt
Use this file to discover all available pages before exploring further.
High-Level Architecture
Docker Containers
All three containers are defined indocker-compose.yml and communicate exclusively over the internal geoviable-net bridge network.
| Container | Base image | Internal port | Purpose |
|---|---|---|---|
geoviable-db | postgis/postgis:15-3.4 | 5432 | PostgreSQL 15 + PostGIS 3.4 spatial database |
geoviable-api | python:3.11-slim (custom build) | 8000 | FastAPI application, WeasyPrint PDF renderer, cron daemon |
geoviable-web | public.ecr.aws/docker/library/nginx:1.25-alpine | 80 | Reverse proxy + React static file serving |
Docker network
All containers are attached to a singlebridge network named geoviable-net:
geoviable-db:5432). Only geoviable-web exposes ports to the host — the database and API containers are not reachable from outside the Docker network in the default configuration.
Persistent volumes
| Volume | Mount path | Purpose |
|---|---|---|
pgdata (named) | /var/lib/postgresql/data | Persistent PostgreSQL data — survives container restarts |
./frontend/build (bind) | /usr/share/nginx/html | Pre-compiled React static assets served by Nginx |
./certs (bind) | /etc/letsencrypt | Let’s Encrypt SSL certificates |
Full Analysis Flow
The following sequence describes every step from the moment a user submits a polygon to the moment a PDF lands in their browser.- User draws or uploads a polygon in the React + Leaflet interface. GeoJSON files and Shapefiles are parsed entirely client-side — no file is sent to the server at this stage.
- Frontend validates the geometry: exactly 1 polygon, area < 100 km², vertex count < 10,000. Invalid geometries are rejected before any network request is made.
-
POST /api/v1/analyze— the frontend sends the validated polygon as a GeoJSONFeaturein EPSG:4326 (WGS84 longitude/latitude). -
FastAPI reprojects to EPSG:25830 using PostGIS
ST_Transform. All stored environmental data is in ETRS89 / UTM zone 30N (EPSG:25830), so reprojection must happen before any spatial comparison. -
PostGIS runs
ST_Intersects+ST_Areaagainst all seven environmental layers simultaneously. For each layer that intersects, the query also computes the intersection geometry, the area of that intersection in m², and the percentage of the parcel covered. - Results returned as JSON — the API response includes a per-layer breakdown of intersecting features, areas, and overlap percentages. The frontend overlays these geometries on the map using coloured polygons.
-
User clicks “Generate report” — the frontend calls
POST /api/v1/report/generate, sending the same GeoJSON polygon plus the project name and metadata. -
Backend generates the PDF:
- Re-runs the spatial analysis (for data consistency and PDF autonomy).
- Generates a static basemap image using contextily (tile download), matplotlib (rendering), and geopandas (vector overlay).
- Renders the full report HTML from a Jinja2 template, embedding the basemap, all layer results, the risk score, and project metadata.
- Converts the HTML to PDF using WeasyPrint.
-
PDF returned as
application/pdf— the browser triggers an automatic file download.
PostGIS Query Pattern
The spatial query follows the same pattern for each of the seven layers. The example below shows the Red Natura 2000 query as it appears in the system specifications:ST_GeomFromGeoJSON— parses the raw GeoJSON string into a PostGIS geometry.ST_SetSRID(..., 4326)— tags the geometry with its source CRS (WGS84).ST_Transform(..., 25830)— reprojects to ETRS89 / UTM zone 30N for metric area calculations.ST_Intersects— boolean spatial predicate; leverages the GIST index for performance.ST_Intersection— computes the actual overlapping geometry for area measurement.ST_Area— returns the area in square metres (since CRS is metric).
Coordinate Reference Systems
GeoViable uses three different CRS at different stages of the pipeline:| Stage | CRS | EPSG | Reason |
|---|---|---|---|
| User input (browser) | WGS84 geographic | EPSG:4326 | Standard for GeoJSON and web maps |
| Stored layer data | ETRS89 / UTM zone 30N | EPSG:25830 | Spanish national standard; metric units for area calculation |
| Map tile display | Web Mercator | EPSG:3857 | Required by OpenStreetMap / Leaflet tile providers |
ST_Transform at query time, so the client never needs to perform coordinate transformations.
Monthly Automated Data Update
Thegeoviable-api container runs a cron daemon alongside the Uvicorn server (configured via backend/scripts/crontab and launched by backend/scripts/entrypoint.sh).
| Aspect | Detail |
|---|---|
| Script | backend/scripts/update_layers.py |
| Schedule | Day 1 of every month at 03:00 UTC |
| Execution context | Inside the geoviable-api container |
| Download method | requests + BeautifulSoup to locate download links on MITECO/CNIG pages; Playwright headless fallback for JavaScript-rendered pages |
- Download the Shapefile/ZIP from the official MITECO or CNIG source URL.
- Decompress and read with GeoPandas.
- Reproject to EPSG:25830 if the source CRS differs.
- Filter records to the Galicia bounding box.
- TRUNCATE + INSERT inside a single SQL transaction — if anything fails,
ROLLBACKpreserves the previous data intact. - Run
VACUUM ANALYZEon the updated table. - Record the result (success or failure, timestamp, row count) in the
layer_update_logtable.
If a layer download fails — for example, because an upstream MITECO URL has changed — the cron job logs the error to
layer_update_log and leaves the existing data untouched. The system continues serving analyses with the previous dataset until the next successful update.Dual-Endpoint Design
GeoViable intentionally uses two separate endpoints in the standard user workflow (seeADR-001 in the source specifications):
POST /api/v1/analyze— runs the spatial analysis and returns JSON for map overlay. Used for interactive preview.POST /api/v1/report/generate— runs the full analysis again internally, generates the static map, and returns a PDF binary. The re-execution guarantees that the PDF is always self-consistent and does not depend on a prior/analyzecall.
Where to go next
Docker Setup
Production deployment on Oracle Cloud, SSL configuration with Let’s Encrypt, and Nginx Proxy Manager setup.
Updating Layers
Manual and automated environmental data loading, the TRUNCATE+INSERT strategy, and troubleshooting failed updates.