Skip to content

Repository files navigation

KSA Ownership Map

An interactive map of the ~909 official zones where non-Saudis may own property under Saudi Arabia's new foreign-ownership law (Royal Decree M/14), built entirely from the public REGA regulator API. A small Python pipeline turns REGA's raw zone records into a web-ready GeoJSON — reconciling REGA's mixed m²/km² area units against each zone's own polygon — and a dependency-light, vendored-Leaflet front-end renders both a detailed category map and a regional choropleth.

Regional overview: the ~909 non-Saudi ownership zones aggregated across Saudi Arabia's 13 administrative regions, coloured by zone count — a self-contained, basemap-free slide graphic rendered from the public REGA dataset.


Why it's interesting

Most "map of the new law" write-ups are a screenshot of a press list. This is the regulator's own complete dataset, extracted from REGA's official JSON API, turned into a reproducible pipeline and a polished map — with the unglamorous data-quality problems solved honestly instead of papered over.

  • The regulator contradicts its own units — and we don't guess. REGA stores each zone's area in mixed units (m² for some records, km² for others, no structural rule), so the raw number is ambiguous by a factor of a million. The build picks the interpretation that matches REGA's own published polygon, uses the geometry only to choose the unit, and still reports REGA's stated figure — never a recomputation.
  • It reports the regulator's mistakes rather than silently "fixing" them. In 6 zones REGA's stated area disagrees with its own polygon by more than 15%; those are flagged and published as-is, with the polygon area alongside for reference.
  • Names are unreliable, so it joins on codes. REGA and the boundary source spell several provinces differently (Hail/Hayel, Asir/'Asir, Makkah / Makkah Al Mukarramah). Region aggregation joins on the ISO 3166-2:SA code, not the name.
  • A screenshot-ready slide graphic with no basemap. The regional overview drops the raster tiles entirely: a designed gradient "sea", muted Natural Earth neighbour silhouettes clipped to the peninsula, and the Kingdom lifted with a drop-shadow — so a single screenshot is a clean slide with no foreign city labels.
  • No build tooling, no CDN JavaScript. Leaflet is vendored locally; the front-end is three static files of vanilla JS/CSS.

The mechanics, precisely

No hand-waving — this is exactly what the pipeline does.

Problem How it's solved (in code)
Ambiguous area units (build_geojson.py) rega_area_km2() returns min((za, za/1e6), key=lambda v: abs(log(v / polygon_km2))) — of the two possible readings of REGA's zoneArea, it keeps the one whose log-ratio to the polygon area is smallest. The polygon disambiguates the unit; the value returned is REGA's own. Unknown → None/n/a.
Polygon area on a sphere A spherical shoelace over each ring: s += radians(Δlon)·(2 + sin(lat₁) + sin(lat₂)), then `area =
REGA self-conflicts A public zone is flagged when `
Region name mismatch (build_regions.py) REGA's region label → ISO 3166-2:SA code (REGA_TO_ISO), then matched to the boundary file's shapeISO. Asserts every REGA region maps and every mapped region has ≥1 zone, so a spelling drift fails loudly instead of silently dropping zones.
Slide-legible labels Per-province label anchors are hand-tuned (MANUAL_LABEL) to avoid collisions at country zoom; provinces fall back to the largest landmass's representative point so labels never land on an offshore islet. Smaller provinces get a higher zIndexOffset so their pills stack on top.
File weight Zone coordinates rounded to 5 dp (~1 m); region boundaries shapely.simplify(0.01°, preserve_topology=True) (~1.1 km, sub-pixel at country zoom) and rounded to 4 dp.

The two front-end maps are pure Leaflet:

  • index.html — the detailed map, one layer per REGA category with a clickable legend, EN/AR/code search, and a canvas renderer for the ~900 polygons. The two whole-city "comprehensive development" envelopes (L057 Riyadh, L058 Jeddah) are held in an OMIT set so the specific zones inside them stay legible; they remain in zones.geojson and the workbook.
  • regions.html — a 13-region choropleth with a By zones / By area toggle. Each metric carries its own sequential ramp and thresholds (a value lands in bucket i when v ≥ br[i]), e.g. zones br = [0,20,45,75,105,130], area br = [0,1000,2000,4000,8000,20000]. Dedicated Leaflet panes stack the neighbour context below the Kingdom, and the drop-shadow is applied to the whole SVG pane so only the country silhouette is shadowed.

How it works

REGA "Saudi Properties" portal API   (public JSON, geometry included)
        │   fetched once, saved verbatim
        ▼
 data/rega_zones_api_raw.json  +  data/official_zones_full_930.json
        │
        ├───────────────► build_geojson.py ──────► public/zones.geojson
        │                 unit disambiguation,      (909 zones, minified)
        │                 5-dp coord rounding
        │
        ├───────────────► build_reference_workbook.py ──► output/*.xlsx
        │                 Zones · Summary (live COUNTIFS/SUMIFS) · Source & Method
        │
        └─ + data/sau_adm1_geoboundaries.geojson   (geoBoundaries, CC BY 4.0)
           + data/ne_110m_admin_0_countries.geojson (Natural Earth, public domain)
                         │
                         ▼
                 build_regions.py ─────► public/regions.geojson  (13 provinces + stats)
                 ISO 3166-2 join,         public/context.geojson  (clipped neighbours)
                 shapely simplify,
                 per-region aggregates
                         │
                         ▼
        public/   static site — vanilla JS + vendored Leaflet (no CDN JS)
   index.html    detailed category map        (CARTO Positron basemap)
   regions.html  regional choropleth          (custom no-basemap slide graphic)
  • Build scripts (build_geojson.py, build_regions.py, build_reference_workbook.py) — pure Python; only shapely (region geometry) and openpyxl (the Excel workbook) are third-party. Every path resolves relative to the script, or via REGA_DATA_DIR / OUT_DIR.
  • public/ — the deployable static site. The rendered GeoJSON payload is shipped, so the maps run without rebuilding.
  • docs/REGA_data_provenance_briefing.md — a full provenance/method note (source, access method, the 909-vs-930 split, the area-unit rule, the 6 self-conflicts, reproducibility).

Tech stack

Python 3 · shapely · openpyxl · GeoJSON · Leaflet.js (vendored) · vanilla JS/HTML/CSS · CARTO Positron basemap · geoBoundaries / Natural Earth

Running it locally

The regional overview renders out of the box (its small aggregate payload ships with the repo):

python3 -m http.server --directory public 8083
# open http://localhost:8083/regions.html   (regional overview — works immediately)

The detailed zone map (index.html) needs the full zone dataset, which is not bundled — build it first from the public REGA source (one command, below). Then http://localhost:8083 shows the detailed map.

To build the data from source:

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# 1. Put the source files in ./data (or set REGA_DATA_DIR):
#      rega_zones_api_raw.json        — REGA API dump, 909 public zones WITH geometry
#      official_zones_full_930.json   — REGA API dump, all 930 zones
#      sau_adm1_geoboundaries.geojson — geoBoundaries gbOpen SAU ADM1 (CC BY 4.0)
#      ne_110m_admin_0_countries.geojson — Natural Earth 110m admin-0 (public domain)
#    (how to fetch the REGA dumps is in docs/REGA_data_provenance_briefing.md)

# 2. Build
python3 build_geojson.py            # -> public/zones.geojson
python3 build_regions.py            # -> public/regions.geojson + context.geojson
python3 build_reference_workbook.py # -> output/REGA_KSA_Ownership_Zones.xlsx

Environment variables (see .env.example): REGA_DATA_DIR, OUT_DIR.

Data, licensing & privacy

This repository contains code only — no bundled datasets. The full REGA zone dataset (public/zones.geojson) is not shipped; the pipeline reconstructs it from REGA's public, unauthenticated API, and docs/REGA_data_provenance_briefing.md documents exactly how to fetch the source JSON so the build is fully reproducible. A small aggregated regional payload (province outlines) is kept only so the regional overview renders as a demo; it is derived entirely from open, publicly-licensed geodata — geoBoundaries (CC BY 4.0) and Natural Earth (public domain). Leaflet is vendored under its BSD-2 licence. There is no client, proprietary, or personal data anywhere in the repo.

Project layout

build_geojson.py            REGA raw JSON -> public/zones.geojson (unit disambiguation)
build_regions.py            per-region aggregates + choropleth geometry + neighbour context
build_reference_workbook.py styled Excel workbook (Zones / Summary / Source & Method)
public/                     static site: index.html, regions.html, vendored Leaflet, *.geojson
docs/                       data-provenance & method note

License

MIT — see LICENSE.

About

Interactive map of the ~909 zones where non-Saudis may own property under Saudi Arabia's new foreign-ownership law - built from the public REGA API, with honest area-unit disambiguation.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages