# Heartmaps

In opdracht · 2025–2026 · Starling Studio · tags: web

URL: https://jim.vanhekke.nl/heartmaps/  
Bijgewerkt: 2026-10-07

Bewoners lopen een route door hun buurt en laten op elke plek achter wat ze ervan vinden.

Een onderzoeker zet een route uit op een kaart, met punten op plekken die ertoe doen. Open de kaart op je telefoon, loop de route en reageer op de plek zelf. Een kompas en een looplijn wijzen de weg; een punt wordt actief als je er dichtbij bent. De onderzoeker ziet alle reacties terug op de kaart, precies waar ze zijn achtergelaten.

Heartmaps is een teamproject. Mijn deel: de route-editor, de reacties met locatie, de navigatie voor de deelnemer en het zelfstandig uitrollen van de applicatie. De backend is Django met GeoDjango en PostGIS, met GraphQL en REST; de frontend TypeScript met Mapbox GL, Three.js en deck.gl, gebouwd op gedeelde modules. Een punt wordt actief binnen vijftien meter. Deelnemers hebben geen account nodig. De applicatie draait als container op Scaleway, bouwt op een versie-tag. Voor elke release lopen Playwright-tests op telefoon- en schermformaat.

## Colofon

- **Getoond:** Groene Westen, Amsterdam, demo december 2025
- **Studio:** Starling Studio
- **Mijn aandeel:** route-editor, reacties met locatie, navigatie, deelnemerskant, uitrol

## Code

Hoe een reactie aan een plek hangt, ingekort: bij elke positiebepaling zoekt de telefoon het dichtstbijzijnde punt dat nog niet gepasseerd is, en opent het reactiescherm één keer per punt. De reactie wordt met het punt en de coördinaten van de deelnemer opgeslagen.

```ts
// In updateNavigation(lat, lng): het dichtstbijzijnde punt dat nog voor je ligt
let minDistance = Infinity
let nearestIndex = currentIndex
for (let i = currentIndex; i < route.points.length; i++) {
  const point = route.points[i]
  const dist = haversineDistanceMeters(lat, lng, point.latitude, point.longitude)
  if (dist < minDistance) { minDistance = dist; nearestIndex = i }
  if (i === currentIndex && dist < WAYPOINT_REACHED_THRESHOLD) {
    nearestIndex = Math.min(i + 1, route.points.length - 1)
    break
  }
}

// Dichtbij genoeg en nog niet getoond voor dit punt: vraag om een reactie
const currentPoint = route.points[nearestIndex]
if (minDistance < REACTION_DISTANCE_THRESHOLD && currentPoint.id !== lastReactionPointId) {
  lastReactionPointId = currentPoint.id
  const existing = store.myReactions?.get(currentPoint.id)
  if (existing) showReactionPanelForEdit(currentPoint, existing)
  else showReactionPanel(currentPoint)
}

// Bij het opslaan: de reactie hangt aan het punt, met waar de deelnemer stond
const variables = { routePointId: pointId, participantId, smiley, text: text || null,
                    longitude: coords[0], latitude: coords[1] }
```

## Beelden

- ![De route lopen met de telefoon, Groene Westen, december 2025 (still uit video) — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-01-demo-lopen.webp)
- ![Een vraag op de plek zelf: de muurschildering van Keith Haring (still uit video) — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-02-demo-vraag.webp)
- ![De muurschildering van Keith Haring, een punt op de route (still uit video) — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-03-demo-haring.webp)
- [Video: Demo Groene Westen, december 2025 — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-04-demo.mp4)
- ![Poster voor de video — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-05-poster-featured_image.webp)
- ![heartmaps.io: de 3D-kaart van Amsterdam met de welkomstkaart — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-06-3d-kaart.webp)
- ![De route Groene Westen met de reacties op de kaart — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-07-route-reacties.webp)
- ![Op de telefoon: de kaart — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-08-mobiel-kaart.webp)
- ![Op de telefoon: een route kiezen, met de reacties per punt — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-09-mobiel-route.webp)
- ![Op de telefoon: navigatie naar het volgende punt — foto: Starling Studio](https://jim.vanhekke.nl/heartmaps/images/heartmaps-10-mobiel-navigatie.webp)
