Pivot
Pivot is a high-performance real time analytics database on open data formats.
Population density map of 37 countries in interactive 3D: the US, China, India, Bangladesh, Japan, Germany and more. Hover any hexagon for its population.
DataHTML · RGitHub @niloy-biswasOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
3D population density maps from Kontur H3 hexagon data: ray-traced renders in R (rayshader) and an interactive web explorer (deck.gl + MapLibre).
Taken from the repository README
Loading the full README.
Read the full README ↗3D population density maps from Kontur population data, in two forms:
Both read the same country list in scripts/countries.R, but they run independently: making a rendered map never touches web/, and the explorer never needs Rayshader.
Population/
├── run_country.R # Entry point for rendered maps
├── scripts/
│ ├── countries.R # All country configs (both outputs read this)
│ ├── helpers.R # Palette creation utilities
│ ├── create_pop_matrix.R
│ ├── plot_population_3d.R
│ ├── annotate_population_map.R
│ ├── export_web_data.R # Country data + pages for the explorer (web/)
│ ├── templates/ # explorer.html: template for every explorer page
│ ├── download_kontur.R # Fetch Kontur files by ISO code into data/kontur/
│ ├── setup_packages.R # Run once to install dependencies
│ └── legacy/ # Old per-country scripts (reference only)
├── data/ # Kontur .gpkg population + boundary files
│ └── kontur/ # Downloaded by download_kontur.R (gitignored)
├── output/
│ ├── plots/ # Raw 3D renders
│ └── annotated/ # Final annotated maps
├── web/ # Interactive explorer (static, deployed to GitHub Pages)
│ ├── app.js, style.css
│ ├── index.html # Landing page (generated)
│ ├── <country>/ # One page per country, e.g. sri-lanka/ (generated)
│ ├── sitemap.xml # Every page, for search engines (generated)
│ └── data/
│ ├── countries.json # Search index: countries + their views (generated)
│ └── <country>/ # hexes.json + boundaries.geojson (generated)
└── .github/workflows/pages.yml # Deploys web/ on push to main
1. Install R Download from cran.r-project.org (v4.4+)
2. Install XQuartz (macOS only — required for rgl) Download from xquartz.org, install, then restart Mac.
3. Install R packages
Open scripts/setup_packages.R and run it once.
Open run_country.R, uncomment the country you want, and source the file:
run_country("bangladesh")
# run_country("finland")
# run_country("india")
# ... etc
Output saves automatically to output/annotated/<Country>_population_density.png.
Download the Kontur population and boundary files by ISO 3166 code:
Rscript scripts/download_kontur.R GB DE # → data/kontur/
This fetches kontur_population_XX_20231101.gpkg and kontur_boundaries_XX_20230628.gpkg from Kontur's public bucket (the same files HDX links to) and skips files you already have. data/kontur/ is gitignored: some files are over GitHub's 100 MB limit, and a fresh clone can re-download them.
Add a config block to scripts/countries.R:
my_country = list(
name = "My Country",
hex_file = "data/kontur/kontur_population_XX_20231101.gpkg",
admin_file = "data/kontur/kontur_boundaries_XX_20230628.gpkg",
crs = 3857, # find at epsg.io
size = 3000,
palette = list(source = "metbrewer", name = "Benedictus", direction = -1),
zscale = 40,
camera = list(theta = 0, phi = 55, zoom = 0.75, fov = 100),
light_dir = 315,
width = 1400, height = 1800,
solid = FALSE, shadow_depth = 0
)
run_country.R:run_country("my_country")
See the comment block at the top of scripts/countries.R for what each parameter does.
web/ is a static page with no build step. deck.gl draws each Kontur H3 hexagon (about 0.74 km²) as a 3D column over a MapLibre basemap from OpenFreeMap. The page reads web/data/countries.json for the search box (every country plus its cities and regions, so typing "Dhaka" offers Dhaka city in Bangladesh), then loads one country's folder at a time. Every country has its own page, so links are shareable and search engines can find each one: bangladesh/, bangladesh/#dhaka-city. Old #bangladesh links still work.
scripts/export_web_data.R writes that data. For each country it:
Rscript scripts/export_web_data.R bangladesh # one country
Rscript scripts/export_web_data.R bangladesh nepal # several
Each run rebuilds countries.json and the pages (web/index.html, web/<country>/index.html, from scripts/templates/explorer.html) from the folders in web/data/, so search always matches what's exported. To rebuild only those, run Rscript scripts/export_web_data.R --manifest. Edit the template, not the generated pages.
Preview locally: python3 -m http.server -d web, then open localhost:8000.
Publish: push changes under web/ to main. The workflow deploys them to niloy.tech/Population-Density-Map.
Make sure the country has a block in scripts/countries.R with hex_file and admin_file (see Adding a New Country). The export only reads name, those two files and the optional web settings. A block with just those is enough for the explorer (most of the newer countries are like this); run_country() refuses it until you add the Rayshader fields.
Check which Kontur admin levels it uses. They differ between countries:
a <- sf::st_read("data/kontur/kontur_boundaries_XX_20230628.gpkg")
table(a$admin_level)
| Country | region | district | subdistrict |
|---|---|---|---|
| Austria | 4 | 8 | 9 |
| Bangladesh | 4 | 6 | 8, 10 |
| Belgium | 4 | 7 | 9 |
| Bhutan | 4 | 6 | none |
| Canada | 4 | 8 | 10 |
| China | 3 | 4 | 6 |
| Czechia | 7 | 8 | 9 |
| Denmark | 4 | 8 | none |
| Finland | 6 | 7 | 8 |
| France | 5 | 6 | 8 |
| Germany | 4 | 7 | 10 |
| Greece | 5 | 6 | 9 |
| Hungary | 6 | 7 | 9 |
| India | 4 | 5 | 7 |
| Ireland | 6 | 10 | 11 |
| Italy | 4 | 7 | 9 |
| Japan | 5 | 9 | 10 |
| Maldives | 4 | none | 9 |
| Myanmar | 3 |
To remove a country, delete its web/data/<country>/ folder and run the export for any remaining country to rebuild the index.
Countries range from under 50 KB of JSON (Maldives, Singapore) to about 18 MB (France, Nigeria, Canada, each around 600k hexagons); GitHub Pages compresses the largest to about 3.3 MB as served.
Tiled countries. The US (4.2M hexagons), China (3.1M) and India (2.1M) are too big for one file, so they set tiles = TRUE. The export then sums the hexagons into coarser H3 cells and cuts every level into map tiles under web/data/<country>/tiles/:
| Map zoom | Hexagons | Area each | US cells |
|---|---|---|---|
| up to 5 | resolution 6 | ~36 km² | 190k |
| 6–7 | resolution 7 | ~5 km² | 990k |
| 8 and up | resolution 8 | ~0.74 km² | 4.2M |
The browser fetches only the tiles on screen: the whole US as about 190k coarse cells, a city as a few full-resolution tiles (each under 400 KB, ~70 KB as served). The legend follows the level in view. This needs the h3jsr package. A tiled export takes 10 to 30 minutes and several GB of memory.
Fork the repo, add your country config to scripts/countries.R, and submit a pull request.
| 7 |
| 9 |
| Nepal | 3 | 7 | 8 |
| Netherlands | 5 | 10 | 12 |
| Nigeria | 4 | 7 | 9 |
| Norway | 4 | 8 | none |
| Pakistan | 3 | 6 | 7 |
| Poland | 4 | 8 | 9 |
| Portugal | 4, 7 | 8 | 10 |
| Romania | 4 | none | 9 |
| Singapore | 7 | none | none |
| Spain | 4 | 7 | 9 |
| Sri Lanka | 4 | 6 | 7 |
| Sweden | 4 | 8 | none |
| Switzerland | 4 | 8 | 11 |
| Ukraine | 4 | 6 | 7 |
| United Kingdom | 4 | 7 | 10 |
| United States | 4 | 6 | 8 |
Use character(0) for a tier the file lacks. The Israel and Palestine boundary files carry no area names, so those maps show the columns and the country view without region buttons, outlines or place names.
The defaults are Bangladesh's. For anything else, add a web list to the country's block:
nepal = list(
...
web = list(levels = list(region = "3", district = "7", subdistrict = "8"))
)
web also takes views (extra camera presets, like Bangladesh's "Dhaka city"), palette (the country's default column colours; visitors can also switch to the Bangladesh, India or Nepal render palettes from the map's layers button) and crop (a lon/lat box; hexagons and areas outside it are dropped). France, the Netherlands, Norway and the UK use crop to leave out overseas territories, Svalbard and Rockall, which would otherwise stretch the country view across oceans. bounds sets only the first (country) view and keeps everything else: Spain, Portugal and Japan use it so the Canaries, Azores, Madeira and Okinawa stay on the map (with their own region buttons) without shrinking the mainland; the US uses it for the lower 48 (Alaska and Hawaii are region buttons), Canada to keep the Arctic islands from pushing the south off screen. For very large countries, mask_km coarsens the land mask (Canada, the US, China, India), simplify_m simplifies thousands of district outlines, and tiles = TRUE exports a tile pyramid (below). See the comment at the top of countries.R.
Export it, preview, and push:
Rscript scripts/export_web_data.R nepal