Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ const getSelectedDatasetURL = () => {
return `${publicationInUse.url}${datasetInUse.filename}`;
};

const getSelectedDatasetMarkersURL = () => {
// Get the publication currently selected (date)
const publicationInUse = indexData.publications
.find(pub => pub.date === document.getElementById('day').value);
// Get the dataset currently selected (group of lines) within the publication selected
// Compute URL of dataset selected
return `${publicationInUse.url}markers.json`;
};

// Load the available line-directions within this group of lines
const loadAvailableLineDirections = () => {
fetch(getSelectedDatasetURL())
Expand Down Expand Up @@ -110,7 +119,7 @@ const processIndex = () => {
const defaultDatasetURL = `${publications[0].url}${publications[0].datasets[0].filename}`;
Object.assign(options, { selectedDate: publications[0].date });
fetch(defaultDatasetURL).then(r => r.json())
.then((defaultData) => { new PTDS(defaultData, options); });
.then((defaultData) => { new PTDS(defaultData, options, null); });
};

// Form submission handler
Expand Down Expand Up @@ -140,7 +149,12 @@ const formSubmit = (event) => {
options.mode = 'spiralSimulation';
}
Object.assign(options, { selectedDate: document.getElementById('day').value });
new PTDS(data, options);

const urlMarkersSelected = getSelectedDatasetMarkersURL();
fetch(urlMarkersSelected).then(r => r.json())
.then((markerdata) => {
new PTDS(data, options, markerdata);
});
});
};

Expand Down
3 changes: 2 additions & 1 deletion src/js/models/vehiclejourney.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ export default class VehicleJourney {
// Extract array of static schedule distances at each stop
const staticDistances = this.journeyPattern.distances;

return Object.values(this.rt).map(({ vehicleNumber, times, distances }) => ({
return Object.values(this.rt).map(({ vehicleNumber, times, distances, markers }) => ({
vehicleNumber,
markers,
// Enrich the vehicles position data with the distance since the last stop
// and the index of that stop, as well as the status compared to the schedule
positions: times.map((time, index) => {
Expand Down
44 changes: 43 additions & 1 deletion src/js/ptdataset.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ import TimeUtils from './timeutils';
* Class representing a public transport dataset
*/
export default class PTDataset {
constructor(inputData, referenceDate) {
constructor(inputData, referenceDate, markerData) {
this.referenceDate = referenceDate;
Object.assign(this, PTDataset.computeStopsAndStopAreas(inputData.scheduledStopPoints));
Object.assign(this, this.computeLinesJourneyPatterns(inputData.journeyPatterns));
this.vehicleJourneys = this.computeVehicleJourneys(inputData.vehicleJourneys);
this.stopsLinks = this.computeLinks();
this.markers = null;
if (markerData != null && markerData.markers != null) {
this.markers = this.computeMarkers(markerData.markers);
this.addMarkersToDataset(this.markers);
}

// Compute times of the first and last stop of any journey in the dataset
this.earliestTime = Math.min(...Object.values(this.journeyPatterns)
Expand All @@ -28,6 +33,33 @@ export default class PTDataset {
.map(jp => jp.firstAndLastTimes.last));
}

addMarkersToDataset(markers) {
for (const marker of markers) {
const { vehicleJourneyCode, vehicleNumber } = marker.reference;
if (Object.prototype.hasOwnProperty.call(this.vehicleJourneys, vehicleJourneyCode)) {
const vehicleJourneyData = this.vehicleJourneys[vehicleJourneyCode];
const { rt } = vehicleJourneyData;
if (rt != null && vehicleNumber != null
&& Object.prototype.hasOwnProperty.call(rt, vehicleNumber)) {
const vehicleData = rt[vehicleNumber];
if (Object.prototype.hasOwnProperty.call(vehicleData, 'markers')) {
const markersData = vehicleData.markers;
markersData.push(marker);
} else {
vehicleData.markers = [marker];
}
} else if (vehicleNumber == null) {
if (Object.prototype.hasOwnProperty.call(vehicleJourneyData, 'markers')) {
const markersData = vehicleJourneyData.markers;
markersData.push(marker);
} else {
vehicleJourneyData.markers = [marker];
}
}
}
}
}

/**
* Convert raw stops data into rich Stop and StopArea objects,
* storing them in an object indexed by their code for fast lookup
Expand Down Expand Up @@ -190,4 +222,14 @@ export default class PTDataset {
vehicleJourney => vehicleJourney.code,
);
}

computeMarkers(markers) {
return markers.map(({ id, reference, time, message, url }) => ({
id,
reference,
time: TimeUtils.secondsToDateObject(time, this.referenceDate),
message,
url,
}));
}
}
4 changes: 2 additions & 2 deletions src/js/ptds.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const d3 = Object.assign({}, {
* Main class
*/
export default class PTDS {
constructor(inputData, options) {
this.data = new PTDataset(inputData, options.selectedDate);
constructor(inputData, options, markerData) {
this.data = new PTDataset(inputData, options.selectedDate, markerData);
this.options = options;

if (['dual', 'marey'].includes(options.mode)) {
Expand Down
140 changes: 138 additions & 2 deletions src/js/viz_components/mareydiagram.js
Original file line number Diff line number Diff line change
Expand Up @@ -657,8 +657,9 @@ export default class MareyDiagram {
* @return {Array.<Object>} - Trip drawing information
*/
computeTrips() {
/*
// Compute drawing information for the trips of the reference journey pattern
const trips = this.journeyPatternMix.referenceJP.vehicleJourneys
const tripsOld = this.journeyPatternMix.referenceJP.vehicleJourneys
.map(({ code, staticSchedule, firstAndLastTimes, realTimeData }) => ({
code,
// For the reference journey pattern there is only one sequence
Expand All @@ -674,13 +675,65 @@ export default class MareyDiagram {
}))],
})),
firstAndLastTimes,
markers: null,
}));
*/

const trips = [];
// Compute drawing information for the trips of the reference journey pattern
for (const { code, staticSchedule, firstAndLastTimes, realTimeData, markers } of
this.journeyPatternMix.referenceJP.vehicleJourneys) {
const tripMarkers = [];
// If there are markers to add, add them
if (markers) {
// Deep clone markers
const leftOverMarkers = markers.slice();
for (const [indexP, { time, distance }] of staticSchedule.entries()) {
for (const [indexM, marker] of leftOverMarkers.entries()) {
if (indexP !== staticSchedule.length - 1) {
const nextPosition = staticSchedule[indexP + 1];
if (time <= marker.time && marker.time < nextPosition.time) {
const x1 = distance;
const y1 = time;
const x2 = nextPosition.distance;
const y2 = nextPosition.time;
const yM = marker.time;
const xM = (((x1 - x2) / (y1 - y2)) * yM)
- (((x1 * y2) - (x2 * y1)) / (y1 - y2));
marker.distance = xM;
tripMarkers.push(marker);
leftOverMarkers.splice(indexM, 1);
break;
}
}
}
}
}
trips.push({
code,
// For the reference journey pattern there is only one sequence
staticSequences: [staticSchedule.map(({ time, distance }) => ({ time, distance }))],
realtimeSequences: realTimeData.map(({ vehicleNumber, positions }) => ({
vehicleNumber,
// Again, only one sequence per vehicle for the reference journey pattern
sequences: [positions.map(({ time, distanceFromStart, status, prognosed }) => ({
time,
distance: distanceFromStart,
status,
prognosed,
}))],
})),
markers: tripMarkers,
firstAndLastTimes,
});
}

// Then compute the trip drawing information for the other journey patterns that share
// at least one link with the reference JP
for (const otherJP of this.journeyPatternMix.otherJPs) {
// Iterate over the trips of the journey pattern
for (const vehicleJourney of otherJP.journeyPattern.vehicleJourneys) {
const tripMarkers = [];
// Min and max time of every static/realtime position of the current journey,
// only for the shared segments
let minTime = null;
Expand Down Expand Up @@ -711,9 +764,37 @@ export default class MareyDiagram {
}));
}

// If there are markers to add, add them
if (vehicleJourney.markers) {
// Deep clone markers
const leftOverMarkers = vehicleJourney.markers.slice();
for (const staticSequence of staticSequences) {
for (const [indexP, { time, distance }] of staticSequence.entries()) {
for (const [indexM, marker] of leftOverMarkers.entries()) {
if (indexP !== staticSequence.length - 1) {
const nextPosition = staticSequence[indexP + 1];
if (time <= marker.time && marker.time < nextPosition.time) {
const x1 = distance;
const y1 = time;
const x2 = nextPosition.distance;
const y2 = nextPosition.time;
const yM = marker.time;
const xM = (((x1 - x2) / (y1 - y2)) * yM)
- (((x1 * y2) - (x2 * y1)) / (y1 - y2));
marker.distance = xM;
tripMarkers.push(marker);
leftOverMarkers.splice(indexM, 1);
break;
}
}
}
}
}
}

const realtimeSequences = [];
// Iterate over each of the real time vehicles
for (const { vehicleNumber, positions } of vehicleJourney.realTimeData) {
for (const { vehicleNumber, positions, markers } of vehicleJourney.realTimeData) {
const vehicleSequences = [];

// Iterate over the shared sequence
Expand Down Expand Up @@ -747,6 +828,32 @@ export default class MareyDiagram {
};
});

// If there are markers to add, add them
if (markers) {
// Deep clone markers
const leftOverMarkers = markers.slice();
for (const [indexP, { time, distance }] of vehicleSequence.entries()) {
for (const [indexM, marker] of leftOverMarkers.entries()) {
if (indexP !== vehicleSequence.length - 1) {
const nextPosition = vehicleSequence[indexP + 1];
if (time <= marker.time && marker.time < nextPosition.time) {
const x1 = distance;
const y1 = time;
const x2 = nextPosition.distance;
const y2 = nextPosition.time;
const yM = marker.time;
const xM = (((x1 - x2) / (y1 - y2)) * yM)
- (((x1 * y2) - (x2 * y1)) / (y1 - y2));
marker.distance = xM;
tripMarkers.push(marker);
leftOverMarkers.splice(indexM, 1);
break;
}
}
}
}
}

// Filter out sequences with zero length (can happen that a vehicle belonging to a
// journey pattern that shares >1 link(s) with the reference one does not have any
// positions to be drawn because the positions are not part of the shared links)
Expand All @@ -767,6 +874,7 @@ export default class MareyDiagram {
code: vehicleJourney.code,
staticSequences,
realtimeSequences,
markers: tripMarkers,
firstAndLastTimes: { first: minTime, last: maxTime },
});
}
Expand Down Expand Up @@ -976,5 +1084,33 @@ export default class MareyDiagram {
.attr('cx', ({ distance }) => this.xScale(distance))
// Trip enter + update > realtime vehicle sequences > realtime position enter
.attr('cy', ({ time }) => this.yScale(time));

// Draw the markers at the end so that they are on top of everything else
// Trip enter + update > marker selection
const tripMarkersSel = tripsEnterUpdateSel
.selectAll('g.marker')
.data(({ markers }) => (typeof markers !== 'undefined' ? markers : []));

// Trip enter + update > marker exit
tripMarkersSel.exit().remove();

// Trip enter + update > marker enter
const tripMarkersGroup = tripMarkersSel.enter().append('g').attr('class', 'marker');
tripMarkersGroup
.on('mouseover', function f() {
d3event.stopPropagation();
d3.select(this).append('text').attr('class', 'message').text(({ message, url }) => `${message} | ${url}`);
})
.on('mouseout', function f() {
d3event.stopPropagation();
d3.select(this).select('text.message').remove();
})
.merge(tripMarkersSel)
.attr('transform', ({ distance, time }) => `translate(${this.xScale(distance)},${this.yScale(time)})`);

tripMarkersGroup.append('image')
.attr('width', 15)
.attr('height', 15)
.attr('xlink:href', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAADC0lEQVR4nO2aPWgUQRiGn1XjT4yQwiBGCAhaxDQSBEEJYqNC1NhEbCQWYikEC1vBaBkQCSKWYqFY2Kig8a8QIdGAiI0/KIiCQUW8i4IJWYvZJXfnTG5mb3ZnQuaBD3Kb2W/f99293dlJIBAIBAKBQCAQCATmiIBjwBgwBcQLtMrAE2C/aQDDHoi3XWd0ze/2QGxepXUl3PRAaF71uNZsJAngC7BeJ6kFSBlYU7lhiWTQ2mK0OOG/Ey4L4HsBQlzxoXaDLIDRAoS4QstbNzCL+xuW7ZoBOnWTGvFAsO0a0TUPsBJ46oFoW/UCaDYJAKANeOeB+EbrE9Buaj6lHfE+4NpE1noJdGQ1n9IMXPXAjGldB1ZnMXxYsf0I8MMDY/XqJ3DU0FsVM8CA4ncbgHsemFTVQ9SX/AAwrRNA2uwS4mlQSwScBH57YDitP8Ap5BO75cDFirHaAcTAa8TESEYnMO6B+VfAVoXGLYhHYOV4owBi4C9iMWGpZOwy4HQypmjjs8AFYIVEVwScQL6aZRxAWs+ATYp9tgNvCjT/Edil0NKBuBeo9s0cQAz8QiQrYxXijOT9HnEDaFVo6Kf+k6qhANK6g3rRZB9iUcW28UngkOKYrcA1zT5WAoiBr0CfokcbcMui+buop7N7gc8GvawFkNYVoEXR6zhQasB4KekhowW4jPlXznoAMfAe2Knot5lsj8vxZF8ZO4C3GbXmEkCMmEGeR0w8amkChhAzsXp9ppOxTYo+55JjZdWZWwBpTQBdit7dzP92OYZ64tWV9G5UX+4BxIip6SDyZfcI6AVuI+7sk8nPvfOMH0x62tBWSABpjSJeoLKyDhGOTU11sXmwGPiGmKCY0p/sa1tP4QFUXg09GsfvAR7kqKMK2XdOK6UGeI5YrbnP3B8qNgJ7EAsW23I+fqT8kJB3AK6p8ixbQFhUhABcC3BNCMC1ANeEACTbpgpXURyl2g2yACYKEOIKLW995DcNdV0HdJMa8kCs7Tqraz7lIOL/6soeiM9aZeARBmc+EAgEAoHA4uEfmPh3WpWTDh8AAAAASUVORK5CYII=');
}
}
6 changes: 5 additions & 1 deletion src/scss/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ svg {
}

#marey {
g.marker text{
font-family: FontAwesome;
}

.marey-scroll {
rect.selection {
stroke: black;
Expand All @@ -71,7 +75,7 @@ svg {
display: none;
}
text {
transform: translate(4px, 0px);
transform: translate(8px, 0px);
}
}
rect.selection {
Expand Down