From c84fd8ff5fd9ce230d584d1c05e058276418df3d Mon Sep 17 00:00:00 2001 From: Christian Beiwinkel Date: Tue, 23 Jul 2024 18:52:43 +0200 Subject: [PATCH 1/2] feat: add waypoint support, mainly for valhalla --- packages/core/BaseRouter.ts | 15 +++-- packages/core/Client.ts | 2 +- packages/core/index.ts | 2 +- packages/graphhopper/index.ts | 38 +++++++------ packages/ors/index.ts | 33 ++++++----- packages/osrm/index.ts | 23 +++++--- packages/valhalla/index.ts | 97 ++++++++++++++++++++------------- packages/valhalla/parameters.ts | 2 +- 8 files changed, 128 insertions(+), 84 deletions(-) diff --git a/packages/core/BaseRouter.ts b/packages/core/BaseRouter.ts index 422d058..a9da2a4 100644 --- a/packages/core/BaseRouter.ts +++ b/packages/core/BaseRouter.ts @@ -48,7 +48,12 @@ export interface ClientConstructorArgs { readonly axiosOpts?: AxiosRequestConfig } -export interface BaseRouter { +export interface Waypoint { + lat: number + lon: number +} + +export interface BaseRouter { client: Client< Record, Record | undefined, @@ -56,21 +61,21 @@ export interface BaseRouter { > directions: ( - locations: number[][], + locations: ([number, number] | T)[], profile: string, directionsOpts?: Record, dryRun?: boolean ) => Promise | string> matrix: ( - locations: [number, number][], + locations: ([number, number] | T)[], profile: string, matrixOpts?: JSONObject, dryRun?: boolean ) => Promise | string> reachability?: ( - location: [number, number], + location: [number, number] | T, profile: string, intervals: number[], isochronesOpts?: JSONObject, @@ -78,7 +83,7 @@ export interface BaseRouter { ) => Promise | string> mapMatch?: ( - locations: [number, number][], + locations: ([number, number] | T)[], profile: string, mapMatchOpts?: JSONObject, dryRun?: boolean diff --git a/packages/core/Client.ts b/packages/core/Client.ts index 8252963..338f09c 100644 --- a/packages/core/Client.ts +++ b/packages/core/Client.ts @@ -101,7 +101,7 @@ class Client< ? (error) => isNetworkOrIdempotentRequestError(error) || error.response?.status == 429 - : undefined, + : () => false, retryDelay: axiosRetry.exponentialDelay, onRetry: (number, error) => console.log( diff --git a/packages/core/index.ts b/packages/core/index.ts index ce9aeb6..479ed88 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -1,4 +1,4 @@ -export { BaseRouter, ClientConstructorArgs } from "./BaseRouter" +export { BaseRouter, ClientConstructorArgs, Waypoint } from "./BaseRouter" export { default as Client } from "./Client" export { Direction, Directions, DirectionFeat } from "./Direction" export { default as Matrix } from "./Matrix" diff --git a/packages/graphhopper/index.ts b/packages/graphhopper/index.ts index 48a3f6a..856684b 100644 --- a/packages/graphhopper/index.ts +++ b/packages/graphhopper/index.ts @@ -10,6 +10,7 @@ import { Isochrones, Matrix, Client, + Waypoint, } from "@routingjs/core" import { LineString } from "geojson" import { @@ -119,7 +120,7 @@ export type GraphHopperClient = Client< * * For the full documentation, see {@link https://docs.graphhopper.com}. */ -export class GraphHopper implements BaseRouter { +export class GraphHopper implements BaseRouter { client: GraphHopperClient apiKey?: string @@ -161,26 +162,28 @@ export class GraphHopper implements BaseRouter { * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging */ directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, directionsOpts?: GraphHopperDirectionsOpts, dryRun?: false ): Promise directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, directionsOpts: GraphHopperDirectionsOpts, dryRun: true ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, directionsOpts?: GraphHopperDirectionsOpts, dryRun?: boolean | undefined ): Promise { const params: GraphHopperRouteParams = { profile, - points: locations.map(([lat, lon]) => [lon, lat]), + points: locations.map((c) => + Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] + ), ...directionsOpts, } @@ -263,28 +266,30 @@ export class GraphHopper implements BaseRouter { * @see {@link https://docs.graphhopper.com/#tag/Isochrone-API} for the full documentation. */ reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: GraphHopperProfile, intervals: [number], isochronesOpts?: GraphHopperIsochroneOpts, dryRun?: false ): Promise reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: GraphHopperProfile, intervals: [number], isochronesOpts: GraphHopperIsochroneOpts, dryRun: true ): Promise public async reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: GraphHopperProfile, intervals: [number], isochronesOpts?: GraphHopperIsochroneOpts, dryRun?: boolean | undefined ): Promise { const params: GraphHopperIsochroneGetParams = { - point: location.join(","), + point: Array.isArray(location) + ? `${location[0]}, ${location[1]}` + : `${location.lat},${location.lon}`, profile, } @@ -333,13 +338,13 @@ export class GraphHopper implements BaseRouter { */ public static parseIsochroneResponse( response: GraphHopperIsochroneResponse, - center: [number, number], + center: [number, number] | Waypoint, intervalType: "time" | "distance" ): GraphHopperIsochrones { const isochrones: Isochrone[] = response.polygons.map((poly) => { return new Isochrone( - center, + Array.isArray(center) ? center : [center.lat, center.lon], poly.properties.bucket, intervalType, poly @@ -367,19 +372,19 @@ export class GraphHopper implements BaseRouter { * */ matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, matrixOpts?: GraphHopperMatrixOpts, dryRun?: false ): Promise matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, matrixOpts: GraphHopperMatrixOpts, dryRun: true ): Promise public async matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: GraphHopperProfile, matrixOpts?: GraphHopperMatrixOpts, dryRun?: boolean | undefined @@ -392,7 +397,7 @@ export class GraphHopper implements BaseRouter { return matrixOpts.sources.includes(i) } else return true }) - .map(([lat, lon]) => [lon, lat]), // reverse order for POST requests + .map((c) => (Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat])), // reverse order for POST requests to_points: locations .filter((coords, i) => { @@ -400,7 +405,8 @@ export class GraphHopper implements BaseRouter { return matrixOpts.destinations.includes(i) } else return true }) - .map(([lat, lon]) => [lon, lat]), + .map((c) => (Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat])), // reverse order for POST requests + ...matrixOpts, } diff --git a/packages/ors/index.ts b/packages/ors/index.ts index 41d6f50..977d400 100644 --- a/packages/ors/index.ts +++ b/packages/ors/index.ts @@ -10,6 +10,7 @@ import { Matrix, Isochrone, Isochrones, + Waypoint, } from "@routingjs/core" import { @@ -85,7 +86,7 @@ export type ORSClient = Client< ORSRouteParams | ORSMatrixParams | ORSIsochroneParams > -export class ORS implements BaseRouter { +export class ORS implements BaseRouter { client: ORSClient apiKey?: string constructor(clientArgs?: ClientConstructorArgs) { @@ -118,21 +119,21 @@ export class ORS implements BaseRouter { ) } directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, directionsOpts?: ORSDirectionsOpts, dryRun?: false, format?: ORSFormat ): Promise directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, directionsOpts: ORSDirectionsOpts, dryRun: true, format?: ORSFormat ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, directionsOpts: ORSDirectionsOpts = {}, dryRun?: boolean, @@ -156,7 +157,9 @@ export class ORS implements BaseRouter { } const params: ORSRouteParams = { - coordinates: locations.map(([lat, lon]) => [lon, lat]), + coordinates: locations.map((c) => + Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] + ), ...directionsOpts, } @@ -245,21 +248,21 @@ export class ORS implements BaseRouter { } reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: string, intervals: number[], isochronesOpts?: ORSIsochroneOpts, dryRun?: false ): Promise reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: string, intervals: number[], isochronesOpts: ORSIsochroneOpts, dryRun: true ): Promise async reachability( - location: [number, number], + location: [number, number] | Waypoint, profile: string, intervals: number[], isochronesOpts: ORSIsochroneOpts = {}, @@ -267,7 +270,9 @@ export class ORS implements BaseRouter { ): Promise { const { range_type, ...rest } = isochronesOpts const params: ORSIsochroneParams = { - locations: [[location[1], location[0]]], // format must be lon/lat + locations: Array.isArray(location) + ? [[location[1], location[0]]] + : [[location.lon, location.lat]], // format must be lon/lat range: intervals, ...rest, } @@ -313,25 +318,27 @@ export class ORS implements BaseRouter { } matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, matrixOpts?: ORSMatrixOpts, dryRun?: false ): Promise matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, matrixOpts: ORSMatrixOpts, dryRun: true ): Promise async matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: ORSProfile, matrixOpts: ORSMatrixOpts = {}, dryRun?: boolean ): Promise { const params: ORSMatrixParams = { - locations: locations.map(([lat, lon]) => [lon, lat]), + locations: locations.map((c) => + Array.isArray(c) ? [c[1], c[0]] : [c.lon, c.lat] + ), ...matrixOpts, } diff --git a/packages/osrm/index.ts b/packages/osrm/index.ts index 2444d6e..204544f 100644 --- a/packages/osrm/index.ts +++ b/packages/osrm/index.ts @@ -9,6 +9,7 @@ import { Matrix, BaseRouter, ClientConstructorArgs, + Waypoint, } from "@routingjs/core" import { @@ -101,7 +102,7 @@ export type OSRMClient = Client< Partial | Partial > -export class OSRM implements BaseRouter { +export class OSRM implements BaseRouter { client: OSRMClient apiKey?: string constructor(clientArgs?: ClientConstructorArgs) { @@ -141,25 +142,27 @@ export class OSRM implements BaseRouter { * @param dryRun - if true, will not make the request and instead return an info string containing the URL and request parameters; for debugging */ public async directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: string, directionsOpts?: OSRMDirectionsOpts, dryRun?: false ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: string, directionsOpts: OSRMDirectionsOpts, dryRun: true ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile = "driving", directionsOpts: OSRMDirectionsOpts = {}, dryRun = false ): Promise { const coords = locations - .map((tuple) => `${tuple[1]},${tuple[0]}`) + .map((l) => + Array.isArray(l) ? `${l[1]},${l[0]}` : `${l.lon},${l.lat}` + ) .join(";") const params = OSRM.getDirectionParams(directionsOpts) @@ -278,25 +281,27 @@ export class OSRM implements BaseRouter { } public async matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: string, matrixOpts?: OSRMMatrixOpts, dryRun?: false ): Promise public async matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: string, matrixOpts: OSRMMatrixOpts, dryRun: true ): Promise public async matrix( - locations: [number, number][], + locations: ([number, number] | Waypoint)[], profile: string, matrixOpts: OSRMMatrixOpts = {}, dryRun?: boolean ): Promise { const coords = locations - .map((tuple) => `${tuple[1]},${tuple[0]}`) + .map((l) => + Array.isArray(l) ? `${l[1]},${l[0]}` : `${l.lon},${l.lat}` + ) .join(";") const params = OSRM.getMatrixParams(matrixOpts) diff --git a/packages/valhalla/index.ts b/packages/valhalla/index.ts index 0578373..6a6d391 100644 --- a/packages/valhalla/index.ts +++ b/packages/valhalla/index.ts @@ -277,7 +277,7 @@ export type ValhallaClient = Client< | ValhallaTraceRouteParams > -export class Valhalla implements BaseRouter { +export class Valhalla implements BaseRouter { client: ValhallaClient apiKey?: string constructor(clientArgs?: ClientConstructorArgs) { @@ -317,19 +317,19 @@ export class Valhalla implements BaseRouter { * @see {@link ValhallaCostingType} for available profiles */ public async directions( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, directionsOpts?: ValhallaDirectionOpts, dryRun?: false ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, directionsOpts: ValhallaDirectionOpts, dryRun: true ): Promise public async directions( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, directionsOpts: ValhallaDirectionOpts = {}, dryRun = false @@ -365,7 +365,7 @@ export class Valhalla implements BaseRouter { } public static getDirectionParams( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, directionsOpts: ValhallaDirectionOpts = {} ): ValhallaRouteParams { @@ -537,21 +537,21 @@ export class Valhalla implements BaseRouter { * @see {@link ValhallaCostingType} for available profiles */ public async reachability( - location: [number, number], + location: [number, number] | ValhallaLocation, profile: ValhallaCostingType, intervals: number[], isochronesOpts?: ValhallaIsochroneOpts, dryRun?: false ): Promise public async reachability( - location: [number, number], + location: [number, number] | ValhallaLocation, profile: ValhallaCostingType, intervals: number[], isochronesOpts: ValhallaIsochroneOpts, dryRun: true ): Promise public async reachability( - location: [number, number], + location: [number, number] | ValhallaLocation, profile: ValhallaCostingType, intervals: number[], isochronesOpts: ValhallaIsochroneOpts = {}, @@ -592,7 +592,7 @@ export class Valhalla implements BaseRouter { } public static getIsochroneParams( - location: [number, number], + location: [number, number] | ValhallaLocation, profile: ValhallaCostingType, intervals: number[], isochroneOpts: ValhallaIsochroneOpts = {} @@ -700,17 +700,18 @@ export class Valhalla implements BaseRouter { public static parseIsochroneResponse( response: ValhallaIsochroneResponse, - location: [number, number], + location: [number, number] | ValhallaLocation, intervals: number[], intervalType: "time" | "distance" ): ValhallaIsochrones { const isochrones: Isochrone[] = [] response.features.forEach((feature, index) => { - // TODO: convert to loop if (feature.geometry.type !== "Point") { isochrones.push( new Isochrone( - location, + Array.isArray(location) + ? location + : [location.lat, location.lon], intervals[index], intervalType, feature as Feature @@ -732,19 +733,19 @@ export class Valhalla implements BaseRouter { * @see {@link ValhallaCostingType} for available profiles */ public async matrix( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, matrixOpts?: ValhallaMatrixOpts, dryRun?: false ): Promise public async matrix( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, matrixOpts: ValhallaMatrixOpts, dryRun: true ): Promise public async matrix( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, matrixOpts: ValhallaMatrixOpts = {}, dryRun?: boolean @@ -775,7 +776,7 @@ export class Valhalla implements BaseRouter { } public static getMatrixParams( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, matrixOpts: ValhallaMatrixOpts = {} ): ValhallaMatrixParams { @@ -896,19 +897,19 @@ export class Valhalla implements BaseRouter { * @see {@link ValhallaCostingType} for available profiles */ public async mapMatch( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, mapMatchOpts?: ValhallaTraceRouteOpts, dryRun?: false ): Promise public async mapMatch( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, mapMatchOpts: ValhallaTraceRouteOpts, dryRun: true ): Promise public async mapMatch( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, traceRouteOpts: ValhallaTraceRouteOpts = {}, dryRun?: boolean @@ -943,7 +944,7 @@ export class Valhalla implements BaseRouter { } public static getTraceRouteParams( - locations: [number, number][], + locations: ([number, number] | ValhallaLocation)[], profile: ValhallaCostingType, traceRouteOpts: ValhallaTraceRouteOpts = {} ): ValhallaTraceRouteParams { @@ -990,30 +991,50 @@ export class Valhalla implements BaseRouter { } public static _buildLocations( - coordinates: [number, number][] + coordinates: + | ([number, number] | ValhallaLocation)[] + | [number, number] + | ValhallaLocation ): ValhallaLocation[] public static _buildLocations( - coordinates: [number, number] + coordinates: + | ([number, number] | ValhallaLocation)[] + | [number, number] + | ValhallaLocation ): [ValhallaLocation] public static _buildLocations( - coordinates: [number, number][] | [number, number] + coordinates: + | ([number, number] | ValhallaLocation)[] + | [number, number] + | ValhallaLocation ): ValhallaLocation[] { - if (Array.isArray(coordinates[0])) { - const locations: ValhallaLocation[] = [] - ;(coordinates as number[][]).forEach((coordPair) => { - // TODO: convert to loop - const locObj = { lon: coordPair[1], lat: coordPair[0] } - locations.push(locObj) - }) - return locations + if (Array.isArray(coordinates)) { + if (Array.isArray(coordinates[0])) { + // [[lat, long], [lat, long], ...] + const locations: ValhallaLocation[] = [] + ;(coordinates as number[][]).forEach((coordPair) => { + const locObj = { lon: coordPair[1], lat: coordPair[0] } + locations.push(locObj) + }) + return locations + } else { + if (typeof coordinates[0] == "number") { + // [lat, lng] + const location: [ValhallaLocation] = [ + { + lat: (coordinates as [number, number])[0], + lon: (coordinates as [number, number])[1], + }, + ] + return location + } else { + // location objects + return coordinates as ValhallaLocation[] + } + } } else { - const location: [ValhallaLocation] = [ - { - lat: (coordinates as number[])[0], - lon: (coordinates as number[])[1], - }, - ] - return location + // single location obj + return [coordinates] } } } diff --git a/packages/valhalla/parameters.ts b/packages/valhalla/parameters.ts index fa3faf4..3ec565a 100644 --- a/packages/valhalla/parameters.ts +++ b/packages/valhalla/parameters.ts @@ -109,7 +109,7 @@ export type ValhallaRequestUnit = "mi" | "km" | "miles" | "kilometers" export interface ValhallaIsochroneParams extends ValhallaRequestParams { /** Only single location can be specified. */ - locations: [ValhallaLocation] + locations: ValhallaLocation[] /** * The local date and time at the location. These parameters apply only for multimodal * requests and are not used with other costing methods. From 2e79200d93fe59c9fc5dce478135fef046355289 Mon Sep 17 00:00:00 2001 From: Christian Beiwinkel Date: Tue, 23 Jul 2024 18:55:00 +0200 Subject: [PATCH 2/2] docs: changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4869bdb..377b71a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Expose parse\* methods as static methods for Valhalla ([#35](https://github.com/gis-ops/routingjs/pull/35)) - This Changelog ;-) ([#35](https://github.com/gis-ops/routingjs/pull/35)) +- `/trace_route` support for Valhalla [#34](https://github.com/gis-ops/routingjs/pull/34) +- Basic waypoint support (mainly for Valhalla) [#40](https://github.com/gis-ops/routingjs/pull/40) ### Fixed - Remove `directionsOpts` as top level options in `ValhallaTraceRouteOpts` ([#35](https://github.com/gis-ops/routingjs/pull/35)) - Remove User-Agent header ([#33](https://github.com/gis-ops/routingjs/pull/33)) -