Skip to content

iooner/Hackerspaces-World-Domination

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

27 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌍 Hackerspaces World Domination

Blog article (in french https://iooner.io/hswd/)

A live, interactive globe of hackerspaces around the world.

hswd.iooner.io β€” crafted with ❀️ in πŸ‡§πŸ‡ͺ by iooner @ LiΓ¨ge Hackerspace


What is this?

A real-time map aggregating hackerspaces from two sources:

  • SpaceAPI β€” an open standard that hackerspaces use to publish their live status (open/closed/limited). Updated every few hours via a cron job.
  • mapall.space / wiki.hackerspaces.org β€” static data for spaces without a SpaceAPI endpoint, shown as blue dots.

Nearby spaces cluster together at low zoom levels. Click a cluster to expand it, click a dot to see details.


Status colors

Color Meaning
🟒 Green Currently open (SpaceAPI live)
🟑 Yellow Open to members only β€” open_to_visitors: false (proposed for SpaceAPI schema v16)
πŸ”΄ Red Currently closed (SpaceAPI live)
βšͺ White API temporarily unavailable β€” space kept visible for 30 days
πŸ”΅ Blue Static data from wiki / mapall, no live status
🟠 Orange (footer) API unreachable for 30+ days β€” removed from the globe

My space isn't on the map

Two ways to get listed:

  1. SpaceAPI β€” implement the SpaceAPI standard and submit your endpoint to the directory. You'll get a live colored dot.
  2. Wiki β€” register on wiki.hackerspaces.org following their guidelines. You'll appear as a static blue dot.

Changes appear on the next cache update (every 10 minutes).

My space is wrong / misplaced

Coordinates and info come directly from the sources above β€” fix them at the source (your SpaceAPI endpoint or your wiki page) and the globe will update automatically on the next run.


Project structure

.
β”œβ”€β”€ index.html                   # Frontend β€” MapLibre GL JS globe, single file
β”œβ”€β”€ api.php                      # API endpoint (see below)
β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ countries-50m-hswd.json  # World atlas 50m, pre-processed (antimeridian fix)
β”‚   β”œβ”€β”€ countries-10m-hswd.json  # World atlas 10m, pre-processed (antimeridian fix)
β”‚   └── imin.gif                 # Rick & Morty asset, very important
└── cache/
    β”œβ”€β”€ update_cache.php         # Cache pipeline (run via cron)
    β”œβ”€β”€ hackerspaces_cache.json  # Generated β€” main data cache
    β”œβ”€β”€ run_history.json         # Generated β€” pipeline run history (7 days)
    β”œβ”€β”€ banlist.json             # Manual exclusion list
    β”œβ”€β”€ test_geojson.json        # Dev snapshot β€” ignored in production
    └── .htaccess                # Blocks direct access to cache files

Note: hackerspaces_cache.json, run_history.json, and test_geojson.json are generated at runtime and should be added to .gitignore.

countries-50m-hswd.json and countries-10m-hswd.json are pre-processed from world-atlas v2.0.2 to fix antimeridian artifacts (Russia, Fiji, Antarctica) in MapLibre's globe projection. They are committed to avoid requiring a Node.js build step on deploy.


API

api.php serves the cache. All endpoints return JSON with CORS headers (Access-Control-Allow-Origin: *).

GET api.php?format=geojson

Returns a GeoJSON FeatureCollection β€” the format consumed by the frontend.

Query parameters:

Parameter Values Description
format geojson GeoJSON FeatureCollection
format history Pipeline run history
state open limited closed unknown static Filter by state
limit integer ≀ 1008 Max runs to return (history only)

GeoJSON response:

{
  "type": "FeatureCollection",
  "metadata": {
    "last_update": "2026-06-13T16:20:02+00:00",
    "stats": {
      "open": 73, "limited": 1, "closed": 123,
      "unknown": 3, "static": 387, "down": 16,
      "expired": 0, "banned": 1, "no_coords": 2
    },
    "count": 587,
    "cache_age_hours": 0.1,
    "cache_age_text": "6 minutes ago"
  },
  "features": [
    {
      "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [5.5856, 50.6427] },
      "properties": {
        "name": "Liège Hackerspace",
        "state": "limited",
        "city": "Liège",
        "country": "",
        "address": "Rue de la Loi 16, 4020 Liège, Belgique",
        "message": "Ouvert aux membres uniquement.",
        "url": "https://lghs.be",
        "logo": "https://raw.githubusercontent.com/LgHS/branding/...",
        "lastchange": 1718290800,
        "last_seen": "2026-06-13T16:20:02+00:00"
      }
    }
  ]
}

Coordinates: GeoJSON order is [longitude, latitude].

GET api.php?format=history

Returns the pipeline run history for the stats modal.

{
  "runs": [
    {
      "ts": "2026-06-13T16:20:02+00:00",
      "dur": 73,
      "stats": { "open": 73, "limited": 1, "closed": 123, ... },
      "down": ["Reaktor 23", "Leitstelle511", ...],
      "expired": [{ "name": "OldSpace", "days": 45 }],
      "unknown": [{ "name": "FabLab AllgΓ€u", "days": 0, "last_seen": "..." }],
      "banned": [{ "name": "NSHkr", "reason": "...", "source": "mapall" }],
      "total": 587
    }
  ]
}

GET api.php (legacy)

Returns the raw cache JSON. Kept for backward compatibility.


Cache pipeline

cache/update_cache.php β€” run via cron (every 10 minutes at hswd.iooner.io).

Pipeline stages:

  1. Download the SpaceAPI directory (~246 spaces)
  2. Load the existing cache (for grace period logic)
  3. Fetch all SpaceAPI endpoints in parallel (10 concurrent curl requests, 5s timeout)
    • open: true + open_to_visitors: false β†’ state limited
    • API down + cached < 30 days β†’ state unknown (kept on globe)
    • API down + cached β‰₯ 30 days β†’ expired (removed)
    • API down + never cached β†’ Nominatim geocoding fallback β†’ state static or down
  4. Merge with mapall.space/wiki.json (~470 spaces) β€” deduplication by name similarity and geographic distance (< 1 km)
  5. Write hackerspaces_cache.json + append to run_history.json

Configuration (top of update_cache.php):

Variable Default Description
$timeout 5 Per-request curl timeout (seconds)
$maxConcurrent 10 Parallel curl requests
$expirationDays 30 Days before a silent space is removed

Run manually (browser or CLI):

# CLI
php cache/update_cache.php

# Browser β€” readable output with live progress
https://yourdomain.com/cache/update_cache.php

The script outputs text/plain with live progress when called from a browser (output buffering disabled).


Banlist

cache/banlist.json β€” manually maintained exclusion list. Checked before any space enters the cache.

{
  "_comment": "Two levels: spaces (full exclusion) and domains (URL hidden, space kept if coords known).",
  "spaces": [
    {
      "name": "ExactSpaceName",
      "reason": "Why it was banned",
      "since": "2026-06"
    }
  ],
  "domains": [
    {
      "domain": "example.com",
      "reason": "Domain squatted",
      "since": "2026-06"
    }
  ]
}

The name must match exactly what appears in the SpaceAPI directory or mapall (case-sensitive). Check the cron output logs to find the exact name.

Think a ban should be lifted? Open an issue or PR.


Tech stack

Layer Tech
Globe MapLibre GL JS v5.24 β€” globe projection
Map data world-atlas v2.0.2 (pre-processed, antimeridian-fixed)
Clustering MapLibre native cluster: true on GeoJSON source
Frontend Vanilla JS + CSS, single index.html, no build step
Backend PHP 8+
Data sources SpaceAPI directory + mapall.space/wiki.json
Geocoding OpenStreetMap Nominatim (fallback only)
Fonts IBM Plex Mono (Google Fonts)

Local development

No build step required. Clone and serve:

git clone https://github.com/iooner/Hackerspaces-World-Domination.git
cd Hackerspaces-World-Domination

# PHP built-in server
php -S localhost:8080

# Then open http://localhost:8080
# The frontend falls back to cache/test_geojson.json if api.php is unavailable

To generate a fresh cache:

php cache/update_cache.php

Contributing

Issues, PRs and feature requests are welcome at github.com/iooner/Hackerspaces-World-Domination.


Roadmap

Ideas and planned upgrades β€” contributions welcome!

Frontend

  • Search with fly-to β€” search field in the terminal prompt block, autocomplete over all space names, globe flies to the result on select
  • Clickable status bar β€” clicking a segment (open, limited, closed…) in the footer filters the globe directly, as a natural alternative to the [ALL] / [OPEN] buttons
  • Shareable URL β€” #space=Liege+Hackerspace opens the globe centered on a space with the info card expanded; useful for QR codes on hackerspace doors
  • Kiosk mode β€” ?kiosk query param: UI hidden, continuous rotation, fullscreen; designed for wall-mounted screens at hackerspaces
  • Live tab title β€” (59🟒) HSWD β€” updates the browser tab with the current open count

Data & pipeline

  • Linked spaces β€” display linked_spaces from SpaceAPI (e.g. LgHS) in the info card, with live status for each linked space and reverse links
  • Nominatim investigation β€” the Nominatim geocoding fallback currently fails 100% of the time server-side (~16 spaces lost as a result); needs a curl test in SSH to determine if the hosting provider blocks outbound requests to OSM
  • Dead code cleanup β€” remove test_geojson.json from the repo (already in .gitignore, needs a git rm --cached)

Stats modal

  • Fix run history endpoint β€” api.php?format=history returns "No history yet" despite run_history.json existing; path resolution issue between api.php (__DIR__) and cache/ to investigate

About

hswd.iooner.io

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors