Community-contributed parking zone data, served via GitHub raw content.
Zone data is stored as static JSON files keyed by geohash for fast spatial lookups. Submissions are made through GitHub Issues (or in-app via the ParkingWarden mobile app) and automatically validated, deduplicated, and converted into pull requests by GitHub Actions.
Submit zone via GitHub Issue / ParkingWarden app
|
v
GitHub Action validates (Zod schema)
Checks for duplicates (proximity + name similarity)
Encodes location to geohash (precision 6, ~1.2km cells)
Creates PR with updated zone file
|
v
PR merged to dev
|
v
manifest.json auto-regenerated
|
v
Clients fetch data via commit-pinned raw.githubusercontent.com URLs
Clients resolve the latest commit SHA via the GitHub API, then fetch manifest.json and zone files at that SHA. This ensures newly merged zones are available within ~60 seconds (the GitHub API cache window), since each commit produces unique URLs that bypass browser caching.
Raw content base URL: https://raw.githubusercontent.com/BenDol/Parking-Zones/{sha}/
manifest.json # Auto-generated region index
zones/
{CC}/{region}/zones.json # Zone data keyed by geohash
config/
settings.json # Global settings (autoMerge flag)
auto-merge-emails.json # Trusted submitter emails
scripts/
process-submission.mjs # Issue -> validate -> PR pipeline
update-manifest.mjs # Regenerates manifest.json
validate-zone.mjs # CLI validation tool
schemas.mjs # Vendored Zod schemas
.github/
ISSUE_TEMPLATE/zone-submission.yml # Structured submission form
workflows/
process-submission.yml # Runs on issue open/label
update-manifest.yml # Runs on push to main/dev (zones/**)
{
"regions": [
{
"country": "GB",
"region": "scotland",
"bbox": { "north": 56.0, "south": 55.9, "east": -3.1, "west": -3.3 },
"zoneCount": 3
}
],
"lastUpdated": "2026-02-25T12:00:00.000Z"
}{
"country": "NZ",
"region": "auckland",
"lastUpdated": "2026-02-25T12:00:00.000Z",
"zoneCount": 2,
"zones": {
"rckhqv": [
{
"id": "cdn-NZ-auckland-seed-001",
"name": "Britomart Transport Centre",
"center": { "lat": -36.8442, "lng": 174.7681 },
"radius": 55,
"enforcementType": "council",
"enforcementMethod": "ticket_machine",
"freeMinutes": 15,
"maxStayMinutes": 120,
"chargePerHour": 6.0,
"currency": "NZD",
"country": "NZ",
"region": "auckland",
"city": "Auckland",
"verified": false,
"version": 1
}
]
}
}The zones object maps 6-character geohash keys to arrays of parking zones. This enables efficient spatial queries — clients compute the geohash for their location and look up the surrounding 9-cell neighbourhood.
- Open the app and go to Submit a Zone
- GPS coordinates, country, region, and currency are auto-detected via reverse geocoding
- Fill in zone name, radius, enforcement type/method, and free minutes
- Submit to Community — this creates a GitHub Issue automatically
- The automation validates, deduplicates, and creates a PR
- Go to Issues > New Issue > Zone Submission
- Paste your zone data as JSON:
{
"name": "Example Car Park",
"center": { "lat": 55.9533, "lng": -3.1883 },
"radius": 50,
"enforcementType": "private",
"enforcementMethod": "camera_anpr",
"freeMinutes": 120,
"country": "GB",
"region": "scotland"
}- The automation will validate the data, check for duplicates, and create a PR.
| Field | Type | Description |
|---|---|---|
name |
string | Human-readable zone name |
center |
{ lat, lng } |
Centre point (WGS84) |
radius |
number | Radius in metres (1-5000) |
enforcementType |
enum | government, private, council, hospital, university, airport, shopping_centre, residential, other |
enforcementMethod |
enum | camera_anpr, physical_warden, ticket_machine, pay_and_display, barrier, clamp, tow, mixed, unknown |
country |
string | ISO 3166-1 alpha-2 country code |
region |
string | Region/state slug (lowercase, hyphens) |
| Field | Type | Description |
|---|---|---|
description |
string | Additional details |
freeMinutes |
number | Free parking allowance |
maxStayMinutes |
number | Maximum stay duration |
chargePerHour |
number | Hourly rate |
currency |
string | ISO 4217 currency code |
city |
string | City name |
operatingHours |
array | Per-day enforcement schedule |
npm ci
# Validate a JSON file
npm run validate -- path/to/zone.json
# Validate inline JSON
npm run validate -- '{"name":"Test","center":{"lat":0,"lng":0},"radius":50,"enforcementType":"private","enforcementMethod":"unknown","country":"XX","region":"test"}'
# Regenerate manifest.json
npm run update-manifestThe automation rejects submissions that match existing zones:
- Name + proximity: Jaccard name similarity >= 0.6 within 50m
- Location only: Within 20m and radius within 30%
Rejected submissions are commented with the matching zone and closed.
Submissions can be auto-merged (skipping manual review) when:
config/settings.jsonhas"autoMerge": true, OR- The submitter's email (from the optional Email field) is listed in
config/auto-merge-emails.json
By default, auto-merge is disabled — all submissions require manual PR review.
The ParkingWarden app uses commit-pinned URLs to ensure fast propagation of new zones:
- GitHub API (
git/refs/heads/dev) resolves the latest commit SHA — cached 60 seconds server-side - Raw content fetched at
raw.githubusercontent.com/{owner}/{repo}/{sha}/...— unique URL per commit, so browser cache never serves stale data - In-memory manifest cache (1 hour) — prevents redundant network requests within a session
- Orchestrator fetch record (1 hour for github-cdn) — prevents re-querying the same location too frequently
After merging a PR, new zones are available to app users within ~60 seconds on next app launch.
Previously this repo used jsDelivr CDN (cdn.jsdelivr.net) which has a 7-day browser cache (max-age=604800), making newly merged zones invisible for up to a week. The switch to raw.githubusercontent.com with commit-pinned URLs resolved this.
Zone data is contributed by the community. By submitting a zone, you agree that the data may be freely used by the ParkingWarden app and its users.