A bun monorepo for managing Geo data within the E-PICSA App
| Component | Stack | Deployment |
|---|---|---|
API (api/) |
Bun + TypeScript | Google Cloud Run via Docker |
Web (web/) |
React + Vite + Tailwind CSS | GitHub Pages |
| Variable | Description |
|---|---|
PORT |
The port the API server listens on (default: 8080) |
OVERPASS_CACHE_BUCKET |
GCS bucket name for caching raw Overpass responses. If not set, caching is disabled. |
VITE_API_URL |
(Frontend build-time) The production API URL. Defaults to /api for local dev. |
To speed up requests and prevent rate-limiting against the Overpass API, raw OSM responses are cached in Google Cloud Storage before TopoJSON conversion.
Setup Requirements:
- A GCS bucket.
- Provide the bucket name via the
OVERPASS_CACHE_BUCKETenvironment variable. - Configure Object Lifecycle Management on the bucket to automatically delete objects older than 90 days.
- The compute identity running this service (e.g., Cloud Run service account) needs
roles/storage.objectAdminon the target bucket.
- Fetches OSM relation boundaries via Overpass API for
admin_levels 2 through 8. - Converts fetched GeoJSON natively into TopoJSON.
- Uses Mapshaper (
-clean,-simplify,-filter-islands) to aggressively reduce file size and complexity. - Validates requests via Zod.
- Interactive React frontend with Leaflet map visualization and TopoJSON download.
This service extracts boundary data from OpenStreetMap (OSM) using the admin_level tag. In OSM, the admin_level values (1-11) have different meanings depending on the specific country's geopolitical structure.
As a general guideline:
admin_level=2: National borders (Country level).admin_level=3: First-level subnational divisions (e.g., states, provinces, regions — used in some countries).admin_level=4: First-level subnational divisions (e.g., states, provinces, regions — most common level for this).admin_level=5: Second-level subnational divisions (e.g., counties, districts, departments).
The API does not currently support levels greater than 5, as these are not used within the E-PICSA app.
Further Reference: OSM Wiki for admin_level.
Prerequisites: Bun installed. The API uses sharp for converting PNG map tiles into WebP images. Node's package manager handles the precompiled binaries during install automatically.
# Install dependencies (both workspaces)
bun install
# Run both API and frontend concurrently
bun startThe Vite dev server proxies /api requests to the Bun API at localhost:8080, so the frontend works seamlessly in development without CORS issues.
The API is containerized via Docker for deployment to Google Cloud Run.
# Build the Docker image
docker build -t epicsa-geo-api .
# Run locally
docker run --rm -p 8080:8080 epicsa-geo-api
# Test the health endpoint
curl http://localhost:8080/healthThe easiest way to deploy is through the Google Cloud Run Console using Developer Connect:
- Go to Cloud Run in the Google Cloud Console.
- Click Create Service → Continuously deploy from a repository.
- Select Developer Connect and link your repository.
- Cloud Run will automatically detect the
Dockerfileat the repo root. - Set environment variables (
OVERPASS_CACHE_BUCKET, etc.) as needed. - Cloud Run will automatically build and deploy on every push to your selected branch.
The API is currently deployed at https://geo-data-api.picsa.app.
The frontend is deployed automatically to GitHub Pages via the .github/workflows/deploy-frontend.yml workflow.
How it works:
- On push to
main(whenweb/files change), the workflow builds the Vite app. VITE_API_URLis set to the Cloud Run URL at build time, so API calls go directly to Cloud Run.- The built SPA is deployed to GitHub Pages.
Custom Domain Setup:
- In your GitHub repo, go to Settings → Pages.
- Under Custom domain, enter your subdomain (e.g.,
geo-data.picsa.app). - Add a
CNAMEDNS record pointing your subdomain toe-picsa.github.io. - Enable Enforce HTTPS.
Endpoint: GET / or GET /health
Response: HTTP 200 OK
{ "status": "ok" }Endpoint: POST /
Request Body:
{
"country_code": "MW",
"admin_level": 2
}Response: HTTP 200 OK
{
"country_code": "MW",
"admin_level": 2,
"source": "generated",
"size_kb": 128,
"feature_count": 1,
"bbox": [32.668, -17.129, 35.92, -9.364],
"topojson": { ... }
}Note
This endpoint is only available when NODE_ENV=development.
Endpoint: POST /admin/clear-cache
Response: HTTP 200 OK
{ "status": "success", "message": "Cache cleared" }Endpoint: POST /export-tiles
Request Body:
{
"country_code": "MW",
"minZoom": 0,
"maxZoom": 8
}Response: HTTP 200 OK
Returns a binary blob stream Content-Type: application/gzip representing a .tar.gz archive of downloaded and converted WebP tiles.
Important
The maxZoom parameter is restricted to a maximum value of 8 to prevent abuse and respect OSM tile usage policies.
