/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
/* vim: set et ts=4 sw=4: */
/*
* Copyright (c) 2019 Marcus Lundblad
*
* GNOME Maps is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* GNOME Maps is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with GNOME Maps; if not, see .
*
* Author: Marcus Lundblad
*/
/**
* Utilities to use GraphHopper to perform walking routes for use in
* transit itineraries, for plugins not natively supporting turn-by-turn
* routing for walking legs
*/
import Champlain from 'gi://Champlain';
import {Application} from './application.js';
import {Location} from './location.js';
import {Place} from './place.js';
import {RouteQuery, QueryPoint} from './routeQuery.js';
import {Leg} from './transitPlan.js';
/* Creates a new walking leg given start and end places, and a route
* obtained from GraphHopper. If the route is undefined (which happens if
* GraphHopper failed to obtain a walking route, approximate it with a
* straight line. */
export function createWalkingLeg(from, to, fromName, toName, route) {
let fromLocation = from.place.location;
let toLocation = to.place.location;
let fromCoordinate = [fromLocation.latitude, fromLocation.longitude];
let toCoordinate = [toLocation.latitude, toLocation.longitude];
let polyline = route ? route.path :
createStraightPolyline(fromLocation, toLocation);
let distance = route ? route.distance :
fromLocation.get_distance_from(toLocation) * 1000;
/* as an estimate for approximated straight-line walking legs,
* assume a speed of 1 m/s to allow some extra time */
let duration = route ? route.time / 1000 : distance;
let walkingInstructions = route ? route.turnPoints : null;
return new Leg({ fromCoordinate: fromCoordinate,
toCoordinate: toCoordinate,
from: fromName,
to: toName,
isTransit: false,
polyline: polyline,
duration: duration,
distance: distance,
walkingInstructions: walkingInstructions });
}
// create a straight-line "as the crow flies" polyline between two places
function createStraightPolyline(fromLoc, toLoc) {
return [new Champlain.Coordinate({ latitude: fromLoc.latitude,
longitude: fromLoc.longitude }),
new Champlain.Coordinate({ latitude: toLoc.latitude,
longitude: toLoc.longitude })];
}
var _walkingRoutes = [];
/* fetches walking route and stores the route for the given coordinate
* pair to avoid requesting the same route over and over from GraphHopper
*/
export function fetchWalkingRoute(points, callback) {
let index = points[0].place.location.latitude + ',' +
points[0].place.location.longitude + ';' +
points[1].place.location.latitude + ',' +
points[1].place.location.longitude;
let route = _walkingRoutes[index];
if (!route) {
Application.routingDelegator.graphHopper.fetchRouteAsync(points,
RouteQuery.Transportation.PEDESTRIAN,
(newRoute) => {
_walkingRoutes[index] = newRoute;
callback(newRoute);
});
} else {
callback(route);
}
}
// create a query point from a bare coordinate (lat, lon pair)
export function createQueryPointForCoord(coord) {
let location = new Location({ latitude: coord[0],
longitude: coord[1],
accuracy: 0 });
let place = new Place({ location: location });
let point = new QueryPoint();
point.place = place;
return point;
}
/**
* Refine itineraries with walking legs retrieved from GraphHopper.
* Intended for use by transit plugins where the source API doesn't give
* full walking turn-by-turn routing
*/
export function addWalkingToItineraries(itineraries, callback) {
_addWalkingToItinerariesRecursive(itineraries, 0, callback);
}
function _addWalkingToItinerariesRecursive(itineraries, index, callback) {
if (index === itineraries.length) {
callback();
} else {
let itinerary = itineraries[index];
_addWalkingToLegsRecursive(itinerary.legs, 0, () => {
_addWalkingToItinerariesRecursive(itineraries, index + 1, callback);
});
}
}
function _addWalkingToLegsRecursive(legs, index, callback) {
if (index === legs.length) {
callback();
} else {
let leg = legs[index];
if (!leg.transit) {
let from = createQueryPointForCoord(leg.fromCoordinate);
let to = createQueryPointForCoord(leg.toCoordinate);
fetchWalkingRoute([from, to], (route) => {
if (route) {
let duration = route.time / 1000;
/* for walking legs not in the start or end
* only replace with the retrieved one if it's not
* longer in duration that the previous (straight-line)
* one.
*/
if (index === 0 || index === legs.length - 1 ||
duration <= leg.duration) {
leg.distance = route.distance;
leg.walkingInstructions = route.turnPoints;
leg.polyline = route.path;
}
}
_addWalkingToLegsRecursive(legs, index + 1, callback);
});
} else {
_addWalkingToLegsRecursive(legs, index + 1, callback);
}
}
}