Add anonymous SVG icon for anonymous signers Add Norway fylker GeoJSON for map boundaries Add CSS styles for petition map Add JavaScript for interactive petition map Add .htaccess to block direct access to data files Add petition-map plugin to process and display map data Add documentation for the petition-map plugin Add mock petition data generator tool
3.8 KiB
petition-map plugin
Page plugin that renders an interactive Leaflet map of petition signers across Norway's 15 fylker.
Enable
In a page's metadata.ini (must also have petition-form loaded first):
plugins = "petition-form, petition-map"
Embed in a content file (e.g. 85-kart.php):
<?= $petition_map ?? '' ?>
Architecture
petition-form CSV
│
▼
petition-map.php (PHP, page plugin)
├── Reads CSV, extracts: first name, anonymous flag, region, confirmed status
├── Writes private cache: custom/data/petition-map-cache.json (60s TTL)
└── Writes public JSON: custom/assets/petition-map-data.json (served at /petition-map-data.json)
│
▼
petition-map.js (client-side)
├── Fetches /petition-map-data.json + /norway-fylker.geojson
├── Renders Leaflet map with CartoDB Positron tiles (muted, no labels)
├── Draws 15 fylke outlines from GeoJSON
├── Places name labels (L.tooltip permanent) randomly within each fylke polygon
└── Polls every 60s for new signers
Files
| File | Location | Purpose |
|---|---|---|
petition-map.php |
custom/plugins/page/ |
PHP plugin: cache logic, data builder, HTML widget renderer, TEMPLATE_VARS hook |
petition-map.js |
custom/assets/ |
Client JS: Leaflet map, dot placement, point-in-polygon, polling |
petition-map.css |
custom/assets/ |
Map and label styles |
norway-fylker.geojson |
custom/assets/ |
Simplified fylke boundaries (15 current fylker, from OpenStreetMap Overpass, RDP simplified to ~69KB) |
petition-map-data.json |
custom/assets/ |
Auto-generated public signer data (written by PHP, served as static asset) |
petition-map-cache.json |
custom/data/ |
Private server-side cache (same data, controls rebuild frequency) |
anon.svg |
custom/assets/ |
Mask icon for anonymous signers |
generate-mock-petitions.php |
custom/tools/ |
Dev-only CLI to generate mock CSV data |
Public JSON format
{
"generated": 1772052913,
"total": 500,
"regions": {
"oslo": {
"count": 57,
"signers": [
{"n": "Kari", "a": false},
{"n": null, "a": true}
]
}
}
}
n= first name (null if anonymous or empty)a= anonymous boolean- No emails, surnames, tokens, or IP hashes ever leave the server
Privacy rules (from CSV display column)
fullorsemi: first name shown on mapanonymous: shown as mask icon, no name- Only
confirmedstatus rows are included
Region keys
15 keys matching the petition-form select options and GeoJSON name property:
agder, akershus, buskerud, finnmark, innlandet, more_og_romsdal, nordland, oslo, rogaland, telemark, troms, trondelag, vestfold, vestland, ostfold
JS internals
- Point placement: rejection sampling within fylke polygon bounding box, ray-casting point-in-polygon test, 4% edge inset
- Label rendering:
L.tooltip({ permanent: true })bound to invisible zero-sizeL.markerin a customdotspane (z-index 450) - Initial animation: all labels shuffled (Fisher-Yates) and staggered over 4.5s
- Polling: fetches data JSON every 60s with cache-bust timestamp; new signers added immediately
- Fylke interaction: hover highlights polygon, click shows popup with fylke name + count
Cache invalidation
Delete custom/data/petition-map-cache.json to force a rebuild on next page load. The mock generator does this automatically. The public JSON is rewritten whenever the cache rebuilds.
Dev tools
Generate mock data (run inside the container):
podman exec stopplidelsen.no php /var/www/custom/tools/generate-mock-petitions.php
Interactive: asks for count and regional variance. Writes CSV and invalidates cache files.