A Python-first tool to generate land cover exports and quickly create a 3D-style, hillshaded PNG from Sentinel-2 land cover tiles and Copernicus elevation data — for any country, or a custom area you draw, upload, or type in yourself.
Inspired by rayshader, Tyler Morgan-Wall's R package for 2D/3D data visualization — this project's hillshade-and-color rendering technique is the same one rayshader uses.
Open https://dbishal13.github.io/3d-export/ — pick an area (draw it on the map, a country, a bounding box, pasted GeoJSON, or an uploaded KML/shapefile), optionally enter an email to have the finished map sent to you, and click Run workflow now. No GitHub account or token needed; the page tracks the run live and links you to the download when it's done.
cd d:\3d-land-cover-map
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python Python/generate_map.py --country BA --output out/land-cover-map.png --mode 3d --aggregate 5Then open out/land-cover-map-3d.png.
- Open the workflow dispatch page:
- Click Run workflow.
- Enter a
country_code(or anaoi_geojsonto override it with a custom area), chooseoutput_formatandmode, and set anaggregatevalue. Pickpdffor a print-ready page with a title and a real clickable credit link (PNG/JPG carry the same credit as plain, non-clickable watermark text). - Run the workflow.
- After the job completes, open the workflow run and download the generated artifact from the summary panel.
Python/generate_map.py/Python/map_utils.py— the export tool: resolves an area of interest (country or custom AOI), streams matching Sentinel-2 land cover tiles and Copernicus DEM elevation tiles directly from their remote sources (no bulk local download), and renders a hillshade-blended, legended PNG with real cast shadows (terrain occluding the sun darkens what's behind it, not just each pixel's own slope/aspect). Scales to any country size, including antimeridian-crossing ones like Russia..github/workflows/generate-map.yml— GitHub Actions workflow that runs the export tool on demand, with an optional step to email the result via Gmail.docs/index.html/docs/styles.css— the GitHub Pages UI: a full-viewport MapLibre map for drawing or previewing an AOI, and a floating panel to configure and trigger a run, track it live, and get to the download.cloudflare-worker/— a small Cloudflare Worker that holds a repo-scoped GitHub token server-side and proxies just theworkflow_dispatchcall, so visitors to the web UI never need a GitHub token of their own (rate-limited to guard the repo's own Actions quota).requirements.txt— Python dependencies for GIS raster processing.
- Install dependencies:
cd d:\3d-land-cover-map
python -m pip install --upgrade pip
python -m pip install -r requirements.txt- Run the generator:
python Python/generate_map.py --country BA --output out/land-cover-map.png --mode 3d --aggregate 5Or with a custom area instead of a country:
python Python/generate_map.py --aoi-geojson '{"type":"Polygon","coordinates":[[...]]}' --output out/land-cover-map.png --mode 3d- Find the generated file in
out/(3D-mode exports get a-3dsuffix).
A workflow is available at .github/workflows/generate-map.yml and can be triggered manually from the repository Actions tab, or via the web UI.
Input values:
country_code— country name or ISO code (ignored ifaoi_geojsonis set)aoi_geojson— a custom area of interest as a GeoJSON Geometry/Feature/FeatureCollection, overridingcountry_codeoutput_format—png,jpg, orpdf(a print-ready page: bordered frame, title, and a real clickable credit link — impossible in a raster image)mode—3d(hillshaded relief) or2d(flat)aggregate— downsample factor for output sizetitle— optional; printed at the top of a PDF export (ignored for png/jpg), defaults to the area nameemail— optional; if set (and the repo owner has configured Gmail secrets, see below), the finished file is emailed as an attachment
Every export (any format) carries a small "Bishal Dhungana · dbishal13.github.io" credit in the corner; in the PDF it's also a real clickable link.
The workflow uploads the generated image as an artifact regardless.
The static UI lives in docs/index.html, published via GitHub Pages from the docs/ folder. It lets visitors:
- Pick an area of interest by drawing a polygon/rectangle directly on the map, typing a country or bounding box, pasting GeoJSON, or uploading a KML/shapefile (converted to GeoJSON in the browser).
- Trigger the workflow with no GitHub account or token — the request goes through the Cloudflare Worker proxy in
cloudflare-worker/. - Watch the run's status live and get a link straight to the finished artifact.
Two optional features need the repo owner to configure secrets — everything else works without them:
- No-token dispatch (Cloudflare Worker): deploy
cloudflare-worker/dispatch-proxy.jsto Cloudflare Workers, bind a KV namespace asRATE_LIMIT_KV, and set aGITHUB_TOKENsecret (a fine-grained PAT scoped to this repo with Actions: Read and write). PointDISPATCH_PROXY_URLindocs/index.htmlat the deployed Worker's URL. - Email delivery (Gmail): add repo secrets
MAIL_USERNAME(a Gmail address) andMAIL_PASSWORD(a Gmail App Password, requires 2FA) under Settings → Secrets and variables → Actions.
This project owes its core rendering idea to rayshader by Tyler Morgan-Wall — an R package for producing 2D and 3D data visualizations via raytracing and hillshading. This repo reimplements that hillshade-and-color blend technique in Python as a standalone export tool, rather than depending on rayshader itself (which is R-only).

