Skip to content

Commit

Permalink
Show distance along route in hover popup
Browse files Browse the repository at this point in the history
Display train ID
Filter out past trains which still get returned by OBA
Improve focus behavior, turn off popup focus steal which was messing with zooming
Store map parameters in session instead of URL so hash to be used for intra-page links
Add recommended stops for solo runners to logistics FAQ
Add convenience shops to lower-zoom-visible POIs
Cartography updates to give map more texture at low zooms
  • Loading branch information
nickswalker committed Aug 24, 2024
1 parent 13d5908 commit 20aa66a
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 105 deletions.
106 changes: 82 additions & 24 deletions js/RelayMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Protocol } from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm';

const mapboxKey = 'pk.eyJ1Ijoibmlja3N3YWxrZXIiLCJhIjoiY2t0ZjgyenE4MDR1YjJ1cno0N3hxYzI4YSJ9.ivPdsoEtV9TaLGbOOfFXKA'
import pmtiles from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'

import {distance, point, nearestPointOnLine, lineString} from '@turf';
const transformRequest = (url, resourceType) => {
if (isMapboxURL(url)) {
return transformMapboxUrl(url, resourceType, mapboxKey)
Expand Down Expand Up @@ -107,7 +109,23 @@ export class RelayMap extends HTMLElement {
this.mapReady.then(() => {
this.map.getSource("rail-lines").setData(railLines)
})

}
computeDistanceAlongLegs(queryPoint, legs) {
let totalDistance = 0
// Merge all legs into a single line
let line = lineString(legs.reduce((acc, leg) => acc.concat(leg.geometry.coordinates), []))
// Get nearest point on line
let nearest = nearestPointOnLine(line, queryPoint, {units: 'meters'})
// Calculate cumulative distance up to index
for (let i = 1; i < line.geometry.coordinates.length; i++) {
let segmentDist = distance(line.geometry.coordinates[i-1], line.geometry.coordinates[i], {units: 'meters'})
totalDistance += segmentDist
if (i === nearest.properties.index) {
totalDistance += distance(nearest.geometry.coordinates, line.geometry.coordinates[i], {units: 'meters'})
break
}
}
return [totalDistance, {lng: nearest.geometry.coordinates[0], lat: nearest.geometry.coordinates[1]}]
}
addRelayLine(legs, exchanges, exchangeNames, useStationCodes=false, lineColors={}, imgBasePath="") {

Expand Down Expand Up @@ -201,16 +219,16 @@ export class RelayMap extends HTMLElement {
let currentActiveLeg = null
let currentLegPopup = null
map.on('click', 'legs', (e) => {
const coordinates = e.features[0].geometry.coordinates;
const leg = e.features[0]
let leg = legsData.slice().filter(l => l.properties.id === e.features[0].id)[0]
let coordinates = leg.geometry.coordinates
const bounds = coordinates.reduce((bounds, coord) => {
return bounds.extend(coord);
}, new maplibregl.LngLatBounds(coordinates[0], coordinates[0]));
currentLegPopup = new maplibregl.Popup({
anchor: "bottom-left",
offset: [16, 0],
className: "leg-popup",
focusAfterOpen: true
focusAfterOpen: false
})
.setLngLat([bounds.getEast(), bounds.getCenter().lat])
.setMaxWidth("300px")
Expand All @@ -225,19 +243,44 @@ export class RelayMap extends HTMLElement {
profile.style.width = "100%"
profile.style.height = "64px"
// Maplibre strips out elevation (and any further data) per point. Get data straight from legs
profile.elevationData = legs.features.filter(l => l.properties.id === leg.id)[0].geometry.coordinates
profile.elevationData = leg.geometry.coordinates
map.fitBounds(bounds, {
padding: 32
});
this.highlightLeg(leg.id)
})
// Create a popup, but don't add it to the map yet.
const distancePopup = new maplibregl.Popup({
closeButton: false,
closeOnClick: false,
focusAfterOpen: false,
className: 'distance-popup'
});

map.on('mouseenter', 'legs', () => {
map.on('mouseenter', 'legs', (e) => {
map.getCanvas().style.cursor = 'pointer';

const coordinates = [e.lngLat.lng, e.lngLat.lat];
let leg = legsData.slice().filter(l => l.properties.id === e.features[0].id)
const [distanceAlongLine, _] = this.computeDistanceAlongLegs(point(coordinates), legsData.slice())
const [distanceAlongLeg, nearestPoint] = this.computeDistanceAlongLegs(point(coordinates), leg)

distancePopup
.setLngLat(nearestPoint)
.setHTML(`${(distanceAlongLine / 1609.34).toFixed(2)}mi <br\><span class="leg-dist">${e.features[0].id + 1}: ${(distanceAlongLeg / 1609.34).toFixed(2)}mi</span>`)
.addTo(map);
});
map.on('mousemove', 'legs', (e) => {
const coordinates = [e.lngLat.lng, e.lngLat.lat];
let leg = legsData.slice().filter(l => l.properties.id === e.features[0].id)
const [distanceAlongLine, _] = this.computeDistanceAlongLegs(point(coordinates), legsData)
const [distanceAlongLeg, nearestPoint] = this.computeDistanceAlongLegs(point(coordinates), leg)
distancePopup
.setLngLat(nearestPoint)
.setHTML(`${(distanceAlongLine / 1609.34).toFixed(2)}mi <br\><span class="leg-dist">${e.features[0].id + 1}: ${(distanceAlongLeg / 1609.34).toFixed(2)}mi</span>`)
});
map.on('mouseleave', 'legs', () => {
map.getCanvas().style.cursor = '';
distancePopup.remove()
});
})
}
Expand Down Expand Up @@ -282,8 +325,8 @@ export class RelayMap extends HTMLElement {
const { stopCodeNorth, stopCodeSouth } = exchange.properties;

const updateArrivals = async () => {
const northboundArrivals = await endpoint(stopCodeNorth);
const southboundArrivals = await endpoint(stopCodeSouth);
let northboundArrivals = await endpoint(stopCodeNorth);
let southboundArrivals = await endpoint(stopCodeSouth);

const currentTime = new Date();

Expand All @@ -292,7 +335,7 @@ export class RelayMap extends HTMLElement {
const isRealtime = arrival.predictedArrivalTime !== null;
const minutesUntilArrival = Math.round((new Date(arrivalTime) - currentTime) / 60000);
let duration = `${minutesUntilArrival} min`;
if (minutesUntilArrival <= 0) {
if (minutesUntilArrival === 0) {
duration = 'now';
}
let realtimeSymbol = '';
Expand All @@ -302,9 +345,14 @@ export class RelayMap extends HTMLElement {
return {
time: new Date(arrivalTime),
realtime: isRealtime,
minutesUntilArrival: minutesUntilArrival,
html: `<div><span class="trip-destination float-start"><span class="line-marker line-${arrival.routeId}"></span> ${arrival.headsign}</span>&nbsp;&nbsp;<span class="trip-eta float-end">${realtimeSymbol}${duration}</span></div>`
};
}
// Filter out arrivals that have already passed
northboundArrivals = northboundArrivals.filter(arrival => new Date(arrival.predictedArrivalTime || arrival.scheduledArrivalTime) > currentTime);
southboundArrivals = southboundArrivals.filter(arrival => new Date(arrival.predictedArrivalTime || arrival.scheduledArrivalTime) > currentTime);


// At most, show next two arrivals for each direction
northboundArrivals.splice(2);
Expand All @@ -331,7 +379,7 @@ export class RelayMap extends HTMLElement {
};

// Create and show a single popup anchored at the top left
const popup = new maplibregl.Popup({ offset: [20, 40], anchor: 'top-left', className: 'arrivals-popup' })
const popup = new maplibregl.Popup({ offset: [20, 40], anchor: 'top-left', className: 'arrivals-popup', closeOnClick: false, focusAfterOpen: false})
.setLngLat(exchangeCoords)
.setHTML('Loading...')
.addTo(map);
Expand All @@ -346,7 +394,6 @@ export class RelayMap extends HTMLElement {
}
};

// Bind the handler to the map's moveend event
map.on('moveend', handleMapMoveEnd);

// Call the handler immediately to handle the initial load
Expand All @@ -371,8 +418,6 @@ relay-map {
height: 100%;
}
`
// Make the map focusable
this.tabIndex = 0
let centerValue = this.attributes.getNamedItem("center").value
let boundaryValue = this.attributes.getNamedItem("max-bounds").value
this.style.display = "block"
Expand All @@ -381,23 +426,36 @@ relay-map {
container: this,
attributionControl: false,
style: this.attributes.getNamedItem("style-href").value,
center: center,
zoom: 9,
center: sessionStorage.getItem('mapCenter') ? JSON.parse(sessionStorage.getItem('mapCenter')) : center,
zoom: Number(sessionStorage.getItem('mapZoom')) || 9,
pitch: Number(sessionStorage.getItem('mapPitch')) || 0,
bearing: Number(sessionStorage.getItem('mapBearing')) || 0,
minZoom: 8,
maxBounds: boundary,
hash: true,
hash: false,
transformRequest: transformRequest,
});
// Don't break basic page scrolling until the map is focused
map.scrollZoom.disable()
let container = map.getContainer();
// container needs to have tabindex=0 to be focusable
map.on("click", () => container.focus())
map.on("pitchstart", () => container.focus())
map.on("drag", () => container.focus())
let canvas = map.getCanvas()
map.on("click", () => canvas.focus())
map.on("pitchstart", () => canvas.focus())
map.on("drag", () => canvas.focus())
map.on("load", () => this._resolveMapReady())
container.addEventListener('focus', () => map.scrollZoom.enable());
container.addEventListener('blur', () => map.scrollZoom.disable());
map.on('moveend', () => {
// Store the current center, zoom, pitch, and bearing in session storage
sessionStorage.setItem('mapCenter', JSON.stringify(map.getCenter()));
sessionStorage.setItem('mapZoom', JSON.stringify(map.getZoom()));
sessionStorage.setItem('mapPitch', JSON.stringify(map.getPitch()));
sessionStorage.setItem('mapBearing', JSON.stringify(map.getBearing()));
});
canvas.addEventListener('focus', () => map.scrollZoom.enable());
canvas.addEventListener('blur', () => {
// Check whether focus is within container
if (!this.contains(document.activeElement))
map.scrollZoom.disable()
});

let nav = new maplibregl.NavigationControl();
map.addControl(nav, 'top-left');
map.addControl(new maplibregl.FullscreenControl({container: map.getContainer()}), 'top-left');
Expand Down
6 changes: 3 additions & 3 deletions js/TransitVehicleTracker.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ export class TransitVehicleTracker {
const vehicles = trips.map(trip => {
const status = trip.status;
const tripHeadsign = tripMap.get(status.activeTripId);

let lastIdNumber = status.activeTripId.split('_')
lastIdNumber = lastIdNumber[lastIdNumber.length - 1]
return {
id: status.activeTripId,
id: lastIdNumber,
lat: status.position.lat,
lon: status.position.lon,
bearing: status.orientation,
Expand All @@ -59,7 +60,6 @@ export class TransitVehicleTracker {
headsign: tripHeadsign
};
});

this.vehicles = vehicles;
this.emitVehicleData(vehicles);
}
Expand Down
Loading

0 comments on commit 20aa66a

Please sign in to comment.