innhold/custom/plugins/page/petiton-map.docs.md
Ruben a7829982d0 Add petition map to medical cannabis petition page
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
2026-02-25 23:11:35 +01:00

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)

  • full or semi: first name shown on map
  • anonymous: shown as mask icon, no name
  • Only confirmed status 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-size L.marker in a custom dots pane (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.