mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
fix(tours): Google Maps follows a tour on foot or by bike, CoMaps only gets a route it can follow (#2812)
The Google link carried no mode, so a hike opened as a drive, and a stop on the equator or the prime meridian was dropped. CoMaps routes a start and an end only; a tour with stops between would reach it as unrouted pins.
This commit is contained in:
@@ -256,6 +256,19 @@ describe('generateGoogleMapsUrl', () => {
|
||||
expect(result).toContain('48.85,2.35')
|
||||
expect(result).toContain('48.86,2.36')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-015b: a walk or ride carries its mode, a drive keeps the path link', () => {
|
||||
const stops = [{ lat: 48.85, lng: 2.35 }, { lat: 48.86, lng: 2.36 }, { lat: 48.87, lng: 2.37 }]
|
||||
expect(generateGoogleMapsUrl(stops, 'walking')).toBe(
|
||||
'https://www.google.com/maps/dir/?api=1&origin=48.85,2.35&destination=48.87,2.37&waypoints=48.86%2C2.36&travelmode=walking',
|
||||
)
|
||||
expect(generateGoogleMapsUrl(stops, 'cycling')).toContain('&travelmode=bicycling')
|
||||
expect(generateGoogleMapsUrl(stops, 'driving')).toBe('https://www.google.com/maps/dir/48.85,2.35/48.86,2.36/48.87,2.37')
|
||||
})
|
||||
|
||||
it('FE-COMP-ROUTECALCULATOR-015c: a stop on the equator or the prime meridian is kept', () => {
|
||||
expect(generateGoogleMapsUrl([{ lat: 0, lng: 0 }, { lat: 0, lng: 1 }])).toBe('https://www.google.com/maps/dir/0,0/0,1')
|
||||
})
|
||||
})
|
||||
|
||||
// ── withHotelBookends (#1275: draw the hotel → first / last → hotel legs) ────────
|
||||
|
||||
@@ -382,14 +382,28 @@ export function withHotelBookends<T extends { lat: number; lng: number }>(
|
||||
return out
|
||||
}
|
||||
|
||||
export function generateGoogleMapsUrl(places: Waypoint[]): string | null {
|
||||
const valid = places.filter((p) => p.lat && p.lng)
|
||||
/** Google's directions modes for the profiles it has; anything else leaves the mode to Google. */
|
||||
const GOOGLE_TRAVEL_MODE: Partial<Record<RouteProfileKey, string>> = { walking: 'walking', cycling: 'bicycling' }
|
||||
|
||||
/**
|
||||
* Google Maps for a list of stops. A walking or cycling route goes as the
|
||||
* documented directions link, which carries the mode; a drive keeps the short
|
||||
* path link it always had. A stop on the equator or the prime meridian counts.
|
||||
*/
|
||||
export function generateGoogleMapsUrl(places: Waypoint[], profile?: RouteProfileKey): string | null {
|
||||
const valid = places.filter((p) => p.lat != null && p.lng != null)
|
||||
if (valid.length === 0) return null
|
||||
if (valid.length === 1) {
|
||||
return `https://www.google.com/maps/search/?api=1&query=${valid[0].lat},${valid[0].lng}`
|
||||
}
|
||||
const stops = valid.map((p) => `${p.lat},${p.lng}`).join('/')
|
||||
return `https://www.google.com/maps/dir/${stops}`
|
||||
const mode = profile && GOOGLE_TRAVEL_MODE[profile]
|
||||
const point = (p: Waypoint) => `${p.lat},${p.lng}`
|
||||
if (mode) {
|
||||
const between = valid.slice(1, -1).map(point).join('|')
|
||||
return `https://www.google.com/maps/dir/?api=1&origin=${point(valid[0])}&destination=${point(valid[valid.length - 1])}`
|
||||
+ (between ? `&waypoints=${encodeURIComponent(between)}` : '') + `&travelmode=${mode}`
|
||||
}
|
||||
return `https://www.google.com/maps/dir/${valid.map(point).join('/')}`
|
||||
}
|
||||
|
||||
/** A stop that can carry its name into a deep link that has somewhere to put one. */
|
||||
|
||||
@@ -72,7 +72,7 @@ export function dayRouteTargets(stops: NamedWaypoint[], profile: RouteProfileKey
|
||||
const push = (target: Omit<DayRouteTarget, 'url'>, url: string | null) => {
|
||||
if (url) out.push({ ...target, url });
|
||||
};
|
||||
push({ id: 'google', label: 'Google Maps' }, generateGoogleMapsUrl(stops));
|
||||
push({ id: 'google', label: 'Google Maps' }, generateGoogleMapsUrl(stops, profile));
|
||||
if (showsAppleMaps()) push({ id: 'apple', label: 'Apple Maps' }, generateAppleMapsUrl(stops, profile));
|
||||
push({ id: 'comaps', label: 'CoMaps' }, generateCoMapsUrl(stops, profile));
|
||||
const located = stops.filter((p) => p.lat != null && p.lng != null).length;
|
||||
|
||||
@@ -39,6 +39,16 @@ describe('tourRouteApps (#2812)', () => {
|
||||
const targets = tourRouteTargets('bike', line(2), 'Ride');
|
||||
expect(targets.map((target) => target.id)).toEqual(['google', 'comaps']);
|
||||
expect(targets.find((target) => target.id === 'comaps')?.url).toContain('type=bicycle');
|
||||
expect(targets.find((target) => target.id === 'google')?.url).toBe('https://www.google.com/maps/dir/0,0/0,0.01');
|
||||
expect(targets.find((target) => target.id === 'google')?.url).toBe(
|
||||
'https://www.google.com/maps/dir/?api=1&origin=0,0&destination=0,0.01&travelmode=bicycling'
|
||||
);
|
||||
});
|
||||
|
||||
it('FE-TOUR-ROUTEAPPS-005: a longer tour goes to Google on foot with its stops between, and not to CoMaps', () => {
|
||||
const targets = tourRouteTargets('hike', line(5), 'Ridge');
|
||||
expect(targets.map((target) => target.id)).toEqual(['google']);
|
||||
const url = targets[0].url;
|
||||
expect(url).toContain('&travelmode=walking');
|
||||
expect(url).toContain('&waypoints=0%2C0.01%7C0%2C0.02%7C0%2C0.03');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,8 +67,9 @@ function spreadIndices(line: ReadonlyArray<readonly [number, number]>, max: numb
|
||||
|
||||
/**
|
||||
* The map apps a tour's route can be opened in, with the tour's mode. Only the
|
||||
* apps that take the whole route: one that only gets the start (Waze, a geo:
|
||||
* link) is what the tour's Navigation button already offers.
|
||||
* apps that route the whole way: one that only gets the start (Waze, a geo:
|
||||
* link) is what the tour's Navigation button already offers, and CoMaps routes
|
||||
* a start and an end only, so with stops between it would get unrouted pins.
|
||||
*/
|
||||
export function tourRouteTargets(
|
||||
type: TourTypeKey,
|
||||
@@ -77,5 +78,8 @@ export function tourRouteTargets(
|
||||
): DayRouteTarget[] {
|
||||
const profile = tourAppProfile(type);
|
||||
if (!profile) return [];
|
||||
return dayRouteTargets(tourRouteStops(line, name), profile).filter((target) => !target.firstStopOnly);
|
||||
const stops = tourRouteStops(line, name);
|
||||
return dayRouteTargets(stops, profile).filter(
|
||||
(target) => !target.firstStopOnly && !(target.id === 'comaps' && stops.length > 2)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user