IxD Experiments with coordinated views and GTFS data

I have a bunch of small ideas based on GTFS data and one of the things they have in common is that I want to combine Map and UI interaction. In this small experiment I wanted to figure out how to coordinate views between a map view and traditional web-components.

Note: The original version of this note contained interactive demos using Leaflet.js. These have been removed during migration. The code is preserved below for reference.

Experiment 1: Coordinated views between map route and range slider

The first experiment coordinates a map polyline (bus route) with a range slider. Dragging the slider moves a marker along the route, and clicking/dragging on the route updates the slider position.

Experiment 2: Coordinated view between map and list of bus stops

The second experiment coordinates a map with a scrollable table of bus stops. Hovering over a row in the table moves the marker to that stop on the map. Clicking or dragging on the route highlights the nearest stop in the table and scrolls it into view.

Details

I downloaded the Danish GTFS data from Rejseplanen. I wrangled the GTFS data into a separate file using a Python script for the transit route I'm interested in (Midttrafik).

I use Leaflet and Leaflet.GeometryUtil to render the route as a polyline and add the interactive elements.

Code Reference

The key interactions involve:

Experiment 1 - Slider coordination

// When slider changes, update marker position
line.oninput = function (e) {
    let p = L.GeometryUtil.interpolateOnLine(map, pline, this.value / 100)
    marker.setLatLng(p.latLng)
}

// When clicking on polyline, update slider
pline.on("click", function (e) {
    let f = L.GeometryUtil.locateOnLine(map, pline, e.latlng)
    marker.setLatLng(e.latlng)
    line.value = f * 100
})

Experiment 2 - Table coordination

// When hovering table row, update marker
function tableOver(evt) {
    let t = evt.target.nodeName == "TD" ? evt.target.parentNode : evt.target
    if (t.nodeName == "TH" || t.nodeName == "TABLE") return

    // Update focus styling
    let focus = document.querySelector("table tr.focus")
    if (focus) focus.classList = ""
    t.classList = "focus"

    // Move marker to stop location
    let latlng = L.latLng([t.dataset.lat, t.dataset.lng])
    marker.setLatLng(latlng)
}

// Find closest stop to a point on the line
function findClosestStopID(point, stops, map) {
    let lowestDistance = 1000
    let closestStopId = -1

    for (i in stops) {
        let s = stops[i]
        let distance = map.latLngToLayerPoint(
            L.latLng([s.lat, s.lng])
        ).distanceTo(point)
        if (distance < lowestDistance) {
            closestStopId = s.stop_id
            lowestDistance = distance
        }
    }
    return closestStopId
}

← Back to Notes