UNPKG

@maplibre/maplibre-gl-directions

Version:

A plugin to show routing directions on a MapLibre GL JS map. Supports any [OSRM](http://project-osrm.org/) or [Mapbox Directions API](https://docs.mapbox.com/api/navigation/directions/) compatible Routing-provider.

1 lines 231 kB
{"version":3,"file":"maplibre-gl-directions.cjs","sources":["../src/directions/events.ts","../src/directions/types.ts","../src/directions/layers.ts","../node_modules/nanoid/url-alphabet/index.js","../node_modules/nanoid/index.browser.js","../node_modules/@placemarkio/polyline/dist/polyline.es.mjs","../src/directions/helpers.ts","../src/directions/utils.ts","../src/directions/main.ts","../node_modules/svelte/src/runtime/internal/utils.js","../node_modules/svelte/src/runtime/internal/dom.js","../node_modules/svelte/src/runtime/internal/lifecycle.js","../node_modules/svelte/src/runtime/internal/scheduler.js","../node_modules/svelte/src/runtime/internal/transitions.js","../node_modules/svelte/src/runtime/internal/each.js","../node_modules/svelte/src/runtime/internal/Component.js","../node_modules/svelte/src/shared/version.js","../node_modules/svelte/src/runtime/internal/disclose-version/index.js","../src/controls/loading-indicator/LoadingIndicatorControl.svelte","../src/controls/loading-indicator/types.ts","../src/controls/loading-indicator/main.ts","../src/controls/bearings/BearingsControl.svelte","../src/controls/bearings/types.ts","../src/controls/bearings/main.ts"],"sourcesContent":["import type { Map, MapMouseEvent, MapTouchEvent } from \"maplibre-gl\";\nimport type { Directions } from \"./types\";\n\nexport class MapLibreGlDirectionsEvented {\n constructor(map: Map) {\n this.map = map;\n }\n\n protected readonly map: Map;\n\n private listeners: ListenersStore = {};\n private oneTimeListeners: ListenersStore = {};\n\n protected fire<T extends keyof MapLibreGlDirectionsEventType>(event: MapLibreGlDirectionsEventType[T]) {\n event.target = this.map;\n\n const type: T = event.type as T;\n\n this.listeners[type]?.forEach((listener) => listener(event));\n this.oneTimeListeners[type]?.forEach((listener) => {\n listener(event);\n\n const index = this.oneTimeListeners[type]?.indexOf(listener);\n if (index !== undefined && ~index) this.oneTimeListeners[type]?.splice(index, 1);\n });\n }\n\n /**\n * Registers an event listener.\n */\n on<T extends keyof MapLibreGlDirectionsEventType>(type: T, listener: MapLibreGlDirectionsEventListener<T>) {\n this.listeners[type] = this.listeners[type] ?? [];\n this.listeners[type]!.push(listener);\n }\n\n /**\n * Un-registers an event listener.\n */\n off<T extends keyof MapLibreGlDirectionsEventType>(type: T, listener: (e: MapLibreGlDirectionsEventType[T]) => void) {\n const index = this.listeners[type]?.indexOf(listener);\n if (index !== undefined && ~index) this.listeners[type]?.splice(index, 1);\n }\n\n /**\n * Registers an event listener to be invoked only once.\n */\n once<T extends keyof MapLibreGlDirectionsEventType>(type: T, listener: MapLibreGlDirectionsEventListener<T>) {\n this.oneTimeListeners[type] = this.oneTimeListeners[type] ?? [];\n this.oneTimeListeners[type]!.push(listener);\n }\n}\n\n/**\n * Supported event types.\n */\nexport interface MapLibreGlDirectionsEventType {\n /**\n * Emitted after the waypoints are set using the {@link default.setWaypoints|`setWaypoints`} method.\n */\n setwaypoints: MapLibreGlDirectionsWaypointEvent;\n\n /**\n * Emitted after the waypoints' bearings values are changed using the\n * {@link default.waypointsBearings|`waypointsBearings`} setter.\n */\n rotatewaypoints: MapLibreGlDirectionsWaypointEvent;\n\n /**\n * Emitted when a waypoint is added.\n */\n addwaypoint: MapLibreGlDirectionsWaypointEvent;\n\n /**\n * Emitted when a waypoint is removed.\n */\n removewaypoint: MapLibreGlDirectionsWaypointEvent;\n\n /**\n * Emitted when a waypoint is moved. __Note__ that the event is not emitted if the waypoint has been dragged for an\n * amount of pixels less than specified by the {@link MapLibreGlDirectionsConfiguration.dragThreshold|`dragThreshold`}\n * configuration property.\n */\n movewaypoint: MapLibreGlDirectionsWaypointEvent;\n\n /**\n * Emitted when there appears an ongoing routing-request.\n */\n fetchroutesstart: MapLibreGlDirectionsRoutingEvent;\n\n /**\n * Emitted after the ongoing routing-request has finished.\n */\n fetchroutesend: MapLibreGlDirectionsRoutingEvent;\n}\n\nexport type MapLibreGlDirectionsEventListener<T extends keyof MapLibreGlDirectionsEventType> = (\n event: MapLibreGlDirectionsEventType[T],\n) => void;\n\ntype ListenersStore = Partial<{\n [T in keyof MapLibreGlDirectionsEventType]: MapLibreGlDirectionsEventListener<T>[];\n}>;\n\nexport interface MapLibreGlDirectionsEvent<TOrig, T extends keyof MapLibreGlDirectionsEventType> {\n type: T;\n target: Map;\n originalEvent: TOrig;\n}\n\nexport interface MapLibreGlDirectionsWaypointEventData {\n /**\n * Index of the added/removed/moved waypoint.\n *\n * Never presents for {@link MapLibreGlDirectionsEventType.setwaypoints|`setwaypoints`} and\n * {@link MapLibreGlDirectionsEventType.rotatewaypoints|`rotatewaypoints`} events.\n */\n index: number;\n\n /**\n * Coordinates from which the waypoint has been moved.\n *\n * Only presents when it's the {@link MapLibreGlDirectionsEventType.movewaypoint|`movewaypoint`} event.\n */\n initialCoordinates: [number, number];\n}\n\nexport class MapLibreGlDirectionsWaypointEvent\n implements\n MapLibreGlDirectionsEvent<\n MapMouseEvent | MapTouchEvent | undefined,\n \"setwaypoints\" | \"rotatewaypoints\" | \"addwaypoint\" | \"removewaypoint\" | \"movewaypoint\"\n >\n{\n /**\n * @private\n */\n constructor(\n type: \"setwaypoints\" | \"rotatewaypoints\" | \"addwaypoint\" | \"removewaypoint\" | \"movewaypoint\",\n originalEvent: MapMouseEvent | MapTouchEvent | undefined,\n data?: Partial<MapLibreGlDirectionsWaypointEventData>,\n ) {\n this.type = type;\n this.originalEvent = originalEvent;\n this.data = data;\n }\n\n type;\n target!: Map;\n originalEvent: MapMouseEvent | MapTouchEvent | undefined;\n data?: Partial<MapLibreGlDirectionsWaypointEventData>;\n}\n\nexport type MapLibreGlDirectionsRoutingEventData = Directions;\n\nexport class MapLibreGlDirectionsRoutingEvent\n implements MapLibreGlDirectionsEvent<MapLibreGlDirectionsWaypointEvent, \"fetchroutesstart\" | \"fetchroutesend\">\n{\n /**\n * @private\n */\n constructor(\n type: \"fetchroutesstart\" | \"fetchroutesend\",\n originalEvent: MapLibreGlDirectionsWaypointEvent,\n data?: MapLibreGlDirectionsRoutingEventData,\n ) {\n this.type = type;\n this.originalEvent = originalEvent;\n this.data = data;\n }\n\n type;\n target!: Map;\n originalEvent: MapLibreGlDirectionsWaypointEvent;\n /**\n * The server's response.\n *\n * Only presents when it's the {@link MapLibreGlDirectionsEventType.fetchroutesend|`fetchroutesend`} event, but might\n * be `undefined` in case the request to fetch directions failed.\n *\n * @see http://project-osrm.org/docs/v5.24.0/api/#responses\n */\n data?: MapLibreGlDirectionsRoutingEventData;\n}\n","import type { LayerSpecification } from \"maplibre-gl\";\n\n/**\n * The {@link default|MapLibreGlDirections} configuration object's interface.\n */\nexport interface MapLibreGlDirectionsConfiguration {\n /**\n * An API-provider URL to make the routing requests to.\n *\n * Any {@link http://project-osrm.org/|OSRM}-compatible or\n * {@link https://docs.mapbox.com/api/navigation/directions/|Mapbox Directions API}-compatible API-provider is\n * supported.\n *\n * @default `\"https://router.project-osrm.org/route/v1\"`\n *\n * @example\n * ```\n * api: \"https://router.project-osrm.org/route/v1\"\n * ```\n *\n * @example\n * ```\n * api: \"https://api.mapbox.com/directions/v5\"\n * ```\n */\n api: string;\n\n /**\n * A routing profile to use. The value depends on the API-provider of choice.\n *\n * @see {@link http://project-osrm.org/docs/v5.24.0/api/#requests|OSRM #Requests}\n * @see {@link https://docs.mapbox.com/api/navigation/directions/#routing-profiles|Mapbox Direction API #Routing profiles}\n *\n * @default `\"driving\"`\n *\n * @example\n * ```\n * api: \"https://router.project-osrm.org/route/v1\",\n * profile: \"driving\"\n * ```\n *\n * @example\n * ```\n * api: \"https://api.mapbox.com/directions/v5\",\n * profile: \"mapbox/driving-traffic\"\n * ```\n */\n profile: string;\n\n /**\n * A list of the request-payload parameters that are passed along with routing requests.\n *\n * __Note__ that the `access-token` request-parameter has a special treatment when used along with\n * {@link makePostRequest|`makePostRequest: true`}. It's automatically removed from the `FormData` and passed as a URL\n * query-parameter as the Mapbox Directions API {@link https://docs.mapbox.com/api/navigation/http-post/|requires}.\n *\n * @default `{}`\n *\n * @example\n * ```\n * requestOptions: {\n * overview: \"full\",\n * steps: \"true\"\n * }\n * ```\n *\n * @example\n * ```\n * api: \"https://api.mapbox.com/directions/v5\",\n * profile: \"mapbox/driving-traffic\",\n * requestOptions: {\n * access_token: \"<mapbox-access-token>\",\n * annotations: \"congestion\",\n * geometries: \"polyline6\"\n * }\n * ```\n */\n requestOptions: Partial<Record<string, string>>;\n\n /**\n * A timeout in ms after which a still-unresolved routing-request automatically gets aborted.\n *\n * @default `null` (no timeout)\n *\n * @example\n * ```\n * // abort requests that take longer then 5s to complete\n * requestTimeout: 5000\n * ```\n */\n requestTimeout: number | null;\n\n /**\n * Whether to make a {@link https://docs.mapbox.com/api/navigation/http-post/|POST request} instead of a GET one.\n *\n * __Note__ that this is only supported by the Mapbox Directions API. Don't set the value to `true` if using an\n * OSRM-compatible API-provider.\n *\n * @default `false`\n *\n * @example\n * ```\n * api: \"https://api.mapbox.com/directions/v5\",\n * profile: \"mapbox/driving-traffic\",\n * makePostRequest: true\n * ```\n */\n makePostRequest: boolean;\n\n /**\n * A name of the source used by the instance. Also used as a prefix for the default layers' names.\n *\n * __Note__ that if you decide to set this field to some custom value, you'd also need to update the following\n * settings accordingly: {@link sensitiveWaypointLayers}, {@link sensitiveSnappointLayers},\n * {@link sensitiveRoutelineLayers} and {@link sensitiveAltRoutelineLayers}.\n *\n * @default `\"maplibre-gl-directions\"`\n *\n * @example\n * ```\n * sourceName: \"my-directions\"\n * ```\n */\n sourceName: string;\n\n /**\n * The layers used by the plugin.\n *\n * @default The value returned by the {@link layersFactory|`layersFactory`} invoked with the passed\n * {@link pointsScalingFactor|`options.pointsScalingFactor`} and\n * {@link linesScalingFactor|`options.linesScalingFactor`}\n *\n * __Note__ that you don't have to create layers with the {@link layersFactory|`layersFactory`}. Any\n * `LayerSpecification[]` value is OK.\n *\n * __Note__ that if you add custom layers then you'd most probably want to register them as sensitive layers using\n * the {@link sensitiveWaypointLayers|`options.sensitiveWaypointLayers`},\n * {@link sensitiveSnappointLayers|`options.sensitiveSnappointLayers`},\n * {@link sensitiveAltRoutelineLayers|`options.sensitiveAltRoutelineLayers`} and\n * {@link sensitiveRoutelineLayers|`options.sensitiveRoutelineLayers`} options.\n *\n * @example\n * ```\n * // Use the default layers with all the points increased by 1.5 times and all the lines increased by 2 times and an additional `\"my-custom-layer\"` layer.\n * {\n * layers: layersFactory(1.5, 2).concat([\n * {\n * id: \"my-custom-layer\",\n * // ...\n * }\n * ])\n * }\n * ```\n */\n layers: LayerSpecification[];\n\n /**\n * A factor by which all the default points' dimensions should be increased. The value is passed as is to the\n * {@link layersFactory|`layersFactory`}'s first argument.\n *\n * __Note__ that the option has no effect when the `layers` option is provided.\n *\n * @default `1`\n *\n * @example\n * ```\n * // Increase all the points by 1.5 times when the map is used on a touch-enabled device\n * linesScalingFactor: isTouchDevice ? 1.5 : 1\n * ```\n */\n pointsScalingFactor: number;\n\n /**\n * A factor by which all the default lines' dimensions should be increased. The value is passed as is to the\n * {@link layersFactory|`layersFactory`}'s second argument.\n *\n * __Note__ that the option has no effect on the snaplines.\n *\n * __Note__ that the option has no effect when the `layers` option is provided.\n *\n * @default `1`\n *\n * @example\n * ```\n * // Increase all the lines by 2 times when the map is used on a touch-enabled device\n * linesScalingFactor: isTouchDevice ? 2 : 1\n * ```\n */\n linesScalingFactor: number;\n\n /**\n * IDs of the layers that are used to represent the waypoints which should be interactive.\n *\n * @default `[\"maplibre-gl-directions-waypoint\", \"maplibre-gl-directions-waypoint-casing\"]`\n *\n * @example\n * ```\n * sensitiveSnappointLayers: [\n * \"maplibre-gl-directions-waypoint\",\n * \"maplibre-gl-directions-waypoint-casing\",\n * \"my-custom-waypoint-layer\"\n * ]\n * ```\n */\n sensitiveWaypointLayers: string[];\n\n /**\n * IDs of the layers that are used to represent the snappoints which should be interactive.\n *\n * @default `[\"maplibre-gl-directions-snappoint\", \"maplibre-gl-directions-snappoint-casing\"]`\n *\n * @example\n * ```\n * sensitiveSnappointLayers: [\n * \"maplibre-gl-directions-snappoint\",\n * \"maplibre-gl-directions-snappoint-casing\",\n * \"my-custom-snappoint-layer\"\n * ]\n * ```\n */\n sensitiveSnappointLayers: string[];\n\n /**\n * IDs of the layers that are used to represent the selected route line which should be interactive.\n *\n * @default `[\"maplibre-gl-directions-routeline\", \"maplibre-gl-directions-routeline-casing\"]`\n *\n * @example\n * ```\n * sensitiveRoutelineLayers: [\n * \"maplibre-gl-directions-routeline\",\n * \"maplibre-gl-directions-routeline-casing\",\n * \"my-custom-routeline-layer\"\n * ]\n * ```\n */\n sensitiveRoutelineLayers: string[];\n\n /**\n * IDs of the layers that are used to represent the alternative route lines which should be interactive.\n *\n * @default `[\"maplibre-gl-directions-alt-routeline\", \"maplibre-gl-directions-alt-routeline-casing\"]`\n *\n * @example\n * ```\n * sensitiveAltRoutelineLayers: [\n * \"maplibre-gl-directions-alt-routeline\",\n * \"maplibre-gl-directions-alt-routeline-casing\",\n * \"my-custom-alt-routeline-layer\"\n * ]\n * ```\n */\n sensitiveAltRoutelineLayers: string[];\n\n /**\n * A minimal amount of pixels a waypoint or the hoverpoint must be dragged in order for the drag-event to be\n * respected, and for network requests to be made when using {@link refreshOnMove|`refreshOnMove: true`}. Should be a number >= `0`.\n * Any negative value is treated as `0`.\n *\n * @default `10`\n *\n * @example\n * ```\n * // Don't respect drag-events where a point was dragged for less than 5px away from its initial location\n * dragThreshold: 5\n * ```\n */\n dragThreshold: number;\n\n /**\n * Whether to update a route while dragging a waypoint/hoverpoint instead of only when dropping it\n *\n * @default `false`\n *\n * @example\n * ```\n * // make the route update while dragging\n * refreshOnMove: true\n * ```\n */\n refreshOnMove: boolean;\n\n /**\n * Whether to support waypoints' {@link https://docs.mapbox.com/api/navigation/directions/#optional-parameters|bearings}.\n *\n * @see {@link http://project-osrm.org/docs/v5.24.0/api/#requests|OSRM #Requests}\n * @see {@link https://docs.mapbox.com/api/navigation/directions/#optional-parameters|Mapbox Direction API #Optional parameters}\n *\n * @default `false`\n *\n * @example\n * ```\n * // enable the bearings support\n * bearings: true\n * ```\n */\n bearings: boolean;\n}\n\nexport const MapLibreGlDirectionsDefaultConfiguration: Omit<MapLibreGlDirectionsConfiguration, \"layers\"> = {\n api: \"https://router.project-osrm.org/route/v1\",\n profile: \"driving\",\n requestOptions: {},\n requestTimeout: null, // can't use Infinity here because of this: https://github.com/denysdovhan/wtfjs/issues/61#issuecomment-325321753\n makePostRequest: false,\n sourceName: \"maplibre-gl-directions\",\n pointsScalingFactor: 1,\n linesScalingFactor: 1,\n sensitiveWaypointLayers: [\"maplibre-gl-directions-waypoint\", \"maplibre-gl-directions-waypoint-casing\"],\n sensitiveSnappointLayers: [\"maplibre-gl-directions-snappoint\", \"maplibre-gl-directions-snappoint-casing\"],\n sensitiveRoutelineLayers: [\"maplibre-gl-directions-routeline\", \"maplibre-gl-directions-routeline-casing\"],\n sensitiveAltRoutelineLayers: [\"maplibre-gl-directions-alt-routeline\", \"maplibre-gl-directions-alt-routeline-casing\"],\n dragThreshold: 10,\n refreshOnMove: false,\n bearings: false,\n};\n\nexport type PointType = \"WAYPOINT\" | \"SNAPPOINT\" | \"HOVERPOINT\" | string;\n\n// server response. Only the necessary for the plugin fields\n\nexport interface Directions {\n code: \"Ok\" | string;\n message?: string;\n routes: Route[];\n waypoints: Snappoint[];\n}\n\nexport type Geometry = PolylineGeometry | GeoJSONGeometry;\nexport type GeoJSONGeometry = {\n coordinates: [number, number][];\n};\nexport type PolylineGeometry = string;\n\nexport interface Route {\n [P: string]: unknown;\n geometry: Geometry;\n legs: Leg[];\n}\n\nexport interface Leg {\n [P: string]: unknown;\n annotation?: {\n congestion?: (\"unknown\" | \"low\" | \"moderate\" | \"heavy\" | \"severe\")[];\n congestion_numeric?: (number | null)[];\n };\n}\n\nexport interface Snappoint {\n [P: string]: unknown;\n location: [number, number];\n}\n","import type { LayerSpecification, LineLayerSpecification } from \"maplibre-gl\";\nimport type { CircleLayerSpecification } from \"@maplibre/maplibre-gl-style-spec\";\n\nexport const colors = {\n snapline: \"#34343f\",\n altRouteline: \"#9e91be\",\n routelineFoot: \"#3665ff\",\n routelineBike: \"#63c4ff\",\n routeline: \"#7b51f8\",\n congestionLow: \"#42c74c\",\n congestionHigh: \"#d72359\",\n hoverpoint: \"#30a856\",\n snappoint: \"#cb3373\",\n snappointHighlight: \"#e50d3f\",\n waypointFoot: \"#3665ff\",\n waypointFootHighlight: \"#0942ff\",\n waypointBike: \"#63c4ff\",\n waypointBikeHighlight: \"#0bb8ff\",\n waypoint: \"#7b51f8\",\n waypointHighlight: \"#6d26d7\",\n};\n\nconst routelineColor: NonNullable<LineLayerSpecification[\"paint\"]>[\"line-color\"] = [\n \"case\",\n [\"==\", [\"get\", \"profile\", [\"get\", \"arriveSnappointProperties\"]], \"foot\"],\n colors.routelineFoot,\n [\"==\", [\"get\", \"profile\", [\"get\", \"arriveSnappointProperties\"]], \"bike\"],\n colors.routelineBike,\n [\n \"interpolate-hcl\",\n [\"linear\"],\n [\"get\", \"congestion\"],\n 0,\n colors.routeline,\n 1,\n colors.congestionLow,\n 100,\n colors.congestionHigh,\n ],\n];\n\nconst waypointColor: NonNullable<CircleLayerSpecification[\"paint\"]>[\"circle-color\"] = [\n \"case\",\n [\"==\", [\"get\", \"profile\"], \"foot\"],\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], colors.waypointFootHighlight, colors.waypointFoot],\n [\"==\", [\"get\", \"profile\"], \"bike\"],\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], colors.waypointBikeHighlight, colors.waypointBike],\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], colors.waypointHighlight, colors.waypoint],\n];\n\nconst snappointColor: NonNullable<CircleLayerSpecification[\"paint\"]>[\"circle-color\"] = [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], false],\n colors.snappointHighlight,\n colors.snappoint,\n];\n\n/**\n * Builds the\n * {@link https://github.com/smellyshovel/maplibre-gl-directions/blob/main/src/directions/layers.ts#L3|standard\n * `MapLibreGlDirections` layers} with optionally scaled features.\n *\n * @param pointsScalingFactor A number to multiply the initial points' dimensions by\n * @param linesScalingFactor A number to multiply the initial lines' dimensions by\n * @param sourceName A name of the source used by the instance and layers names' prefix\n */\nexport default function layersFactory(\n pointsScalingFactor = 1,\n linesScalingFactor = 1,\n sourceName = \"maplibre-gl-directions\",\n): LayerSpecification[] {\n const pointCasingCircleRadius: NonNullable<CircleLayerSpecification[\"paint\"]>[\"circle-radius\"] = [\n \"interpolate\",\n [\"exponential\", 1.5],\n [\"zoom\"],\n // don't forget it's the radius! The visible value is diameter (which is 2x)\n // on zoom levels 0-5 should be 5px more than the routeline casing. 7 + 5 = 12.\n // When highlighted should be +2px more. 12 + 2 = 14\n 0,\n // highlighted to default ratio (epsilon) = 14 / 12 ~= 1.16\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 14 * pointsScalingFactor,\n 12 * pointsScalingFactor,\n ],\n 5,\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 14 * pointsScalingFactor,\n 12 * pointsScalingFactor,\n ],\n // exponentially grows on zoom levels 5-18 finally becoming the same 5px wider than the routeline's casing on\n // the same zoom level: 23 + 5 = 28px\n 18,\n // highlighted = default ~= 33\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 33 * pointsScalingFactor,\n 28 * pointsScalingFactor,\n ],\n ];\n\n const pointCircleRadius: NonNullable<CircleLayerSpecification[\"paint\"]>[\"circle-radius\"] = [\n \"interpolate\",\n [\"exponential\", 1.5],\n [\"zoom\"],\n // on zoom levels 0-5 - 5px smaller than the casing. 12 - 5 = 7.\n 0,\n // feature to casing ratio (psi) = 7 / 12 ~= 0.58\n // highlighted to default ratio (epsilon) = 9 / 7 ~= 1.28\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 9 * pointsScalingFactor,\n 7 * pointsScalingFactor,\n ],\n 5,\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 9 * pointsScalingFactor,\n 7 * pointsScalingFactor,\n ],\n // exponentially grows on zoom levels 5-18 finally becoming psi times the casing\n 18,\n // psi * 28 ~= 16\n // when highlighted multiply by epsilon ~= 21\n [\n \"case\",\n [\"boolean\", [\"get\", \"highlight\"], [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"]],\n 21 * pointsScalingFactor,\n 16 * pointsScalingFactor,\n ],\n ];\n\n const lineWidth: NonNullable<LineLayerSpecification[\"paint\"]>[\"line-width\"] = [\n \"interpolate\",\n [\"exponential\", 1.5],\n [\"zoom\"],\n // on zoom levels 0-5 - 4px smaller than the casing (2px on each side). 7 - 4 = 3.\n // Doesn't change when highlighted\n 0,\n // feature to casing ratio (psi) = 3 / 7 ~= 0.42\n 3 * linesScalingFactor,\n 5,\n 3 * linesScalingFactor,\n // exponentially grows on zoom levels 5-18 finally becoming psi times the casing\n 18,\n // psi * 23 ~= 10\n 10 * linesScalingFactor,\n ];\n\n const lineCasingWidth: NonNullable<LineLayerSpecification[\"paint\"]>[\"line-width\"] = [\n \"interpolate\",\n [\"exponential\", 1.5],\n [\"zoom\"],\n // on zoom levels 0-5 - 7px by default and 10px when highlighted\n 0,\n // highlighted to default ratio (epsilon) = 10 / 7 ~= 1.42\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], 10 * linesScalingFactor, 7 * linesScalingFactor],\n 5,\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], 10 * linesScalingFactor, 7 * linesScalingFactor],\n // exponentially grows on zoom levels 5-18 finally becoming 32px when highlighted\n 18,\n // default = 32 / epsilon ~= 23\n [\"case\", [\"boolean\", [\"get\", \"highlight\"], false], 32 * linesScalingFactor, 23 * linesScalingFactor],\n ];\n\n return [\n {\n id: `${sourceName}-snapline`,\n type: \"line\",\n source: sourceName,\n layout: {\n \"line-cap\": \"round\",\n \"line-join\": \"round\",\n },\n paint: {\n \"line-dasharray\": [3, 3],\n \"line-color\": colors.snapline,\n \"line-opacity\": 0.65,\n \"line-width\": 3,\n },\n filter: [\"==\", [\"get\", \"type\"], \"SNAPLINE\"],\n },\n\n {\n id: `${sourceName}-alt-routeline-casing`,\n type: \"line\",\n source: sourceName,\n layout: {\n \"line-cap\": \"butt\",\n \"line-join\": \"round\",\n },\n paint: {\n \"line-color\": colors.altRouteline,\n \"line-opacity\": 0.55,\n \"line-width\": lineCasingWidth,\n },\n filter: [\"==\", [\"get\", \"route\"], \"ALT\"],\n },\n {\n id: `${sourceName}-alt-routeline`,\n type: \"line\",\n source: sourceName,\n layout: {\n \"line-cap\": \"butt\",\n \"line-join\": \"round\",\n },\n paint: {\n \"line-color\": colors.altRouteline,\n \"line-opacity\": 0.85,\n \"line-width\": lineWidth,\n },\n filter: [\"==\", [\"get\", \"route\"], \"ALT\"],\n },\n\n {\n id: `${sourceName}-routeline-casing`,\n type: \"line\",\n source: sourceName,\n layout: {\n \"line-cap\": \"butt\",\n \"line-join\": \"round\",\n },\n paint: {\n \"line-color\": routelineColor,\n \"line-opacity\": 0.55,\n \"line-width\": lineCasingWidth,\n },\n filter: [\"==\", [\"get\", \"route\"], \"SELECTED\"],\n },\n {\n id: `${sourceName}-routeline`,\n type: \"line\",\n source: sourceName,\n layout: {\n \"line-cap\": \"butt\",\n \"line-join\": \"round\",\n },\n paint: {\n \"line-color\": routelineColor,\n \"line-opacity\": 0.85,\n \"line-width\": lineWidth,\n },\n filter: [\"==\", [\"get\", \"route\"], \"SELECTED\"],\n },\n\n {\n id: `${sourceName}-hoverpoint-casing`,\n type: \"circle\",\n source: sourceName,\n paint: {\n \"circle-radius\": pointCasingCircleRadius,\n \"circle-color\": colors.hoverpoint,\n \"circle-opacity\": 0.65,\n },\n filter: [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"],\n },\n {\n id: `${sourceName}-hoverpoint`,\n type: \"circle\",\n source: sourceName,\n paint: {\n // same as snappoint, but always hig(since it's always highlighted while present on the map)\n \"circle-radius\": pointCircleRadius,\n \"circle-color\": colors.hoverpoint,\n },\n filter: [\"==\", [\"get\", \"type\"], \"HOVERPOINT\"],\n },\n\n {\n id: `${sourceName}-snappoint-casing`,\n type: \"circle\",\n source: sourceName,\n paint: {\n \"circle-radius\": pointCasingCircleRadius,\n \"circle-color\": snappointColor,\n \"circle-opacity\": 0.65,\n },\n filter: [\"==\", [\"get\", \"type\"], \"SNAPPOINT\"],\n },\n {\n id: `${sourceName}-snappoint`,\n type: \"circle\",\n source: sourceName,\n paint: {\n \"circle-radius\": pointCircleRadius,\n \"circle-color\": snappointColor,\n },\n filter: [\"==\", [\"get\", \"type\"], \"SNAPPOINT\"],\n },\n\n {\n id: `${sourceName}-waypoint-casing`,\n type: \"circle\",\n source: sourceName,\n paint: {\n \"circle-radius\": pointCasingCircleRadius,\n \"circle-color\": waypointColor,\n \"circle-opacity\": 0.65,\n },\n filter: [\"==\", [\"get\", \"type\"], \"WAYPOINT\"],\n },\n\n {\n id: `${sourceName}-waypoint`,\n type: \"circle\",\n source: sourceName,\n paint: {\n \"circle-radius\": pointCircleRadius,\n \"circle-color\": waypointColor,\n },\n filter: [\"==\", [\"get\", \"type\"], \"WAYPOINT\"],\n },\n ] satisfies LayerSpecification[];\n}\n","export const urlAlphabet =\n 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'\n","import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js'\nexport { urlAlphabet } from './url-alphabet/index.js'\nexport let random = bytes => crypto.getRandomValues(new Uint8Array(bytes))\nexport let customRandom = (alphabet, defaultSize, getRandom) => {\n let mask = (2 << Math.log2(alphabet.length - 1)) - 1\n let step = -~((1.6 * mask * defaultSize) / alphabet.length)\n return (size = defaultSize) => {\n let id = ''\n while (true) {\n let bytes = getRandom(step)\n let j = step | 0\n while (j--) {\n id += alphabet[bytes[j] & mask] || ''\n if (id.length >= size) return id\n }\n }\n }\n}\nexport let customAlphabet = (alphabet, size = 21) =>\n customRandom(alphabet, size | 0, random)\nexport let nanoid = (size = 21) => {\n let id = ''\n let bytes = crypto.getRandomValues(new Uint8Array((size |= 0)))\n while (size--) {\n id += scopedUrlAlphabet[bytes[size] & 63]\n }\n return id\n}\n","// https://github.com/mapbox/polyline/blob/master/src/polyline.js\n// Based off of [the offical Google document](https://developers.google.com/maps/documentation/utilities/polylinealgorithm)\n//\n// Some parts from [this implementation](http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.js)\n// by [Mark McClure](http://facstaff.unca.edu/mcmcclur/)\nfunction py2_round(value) {\n // Google's polyline algorithm uses the same rounding strategy as Python 2,\n // which is different from JS for negative values\n return Math.floor(Math.abs(value) + 0.5) * (value >= 0 ? 1 : -1);\n}\nfunction encodeNumber(current, previous, factor) {\n current = py2_round(current * factor);\n previous = py2_round(previous * factor);\n let coordinate = current - previous;\n coordinate <<= 1;\n if (current - previous < 0) {\n coordinate = ~coordinate;\n }\n let output = \"\";\n while (coordinate >= 0x20) {\n output += String.fromCharCode((0x20 | (coordinate & 0x1f)) + 63);\n coordinate >>= 5;\n }\n output += String.fromCharCode(coordinate + 63);\n return output;\n}\nfunction resultChange(result) {\n return result & 1 ? ~(result >> 1) : result >> 1;\n}\n/**\n * Decodes any string into a [longitude, latitude] coordinates array.\n *\n * Any string is a valid polyline, but if you provide this\n * with an arbitrary string, it'll produce coordinates well\n * outside of the normal range.\n */\nfunction decode(str, precision = 5) {\n const factor = Math.pow(10, precision);\n let index = 0;\n let lat = 0;\n let lng = 0;\n const coordinates = [];\n let shift = 0;\n let result = 0;\n let byte = null;\n let latitude_change;\n let longitude_change;\n // Coordinates have variable length when encoded, so just keep\n // track of whether we've hit the end of the string. In each\n // loop iteration, a single coordinate is decoded.\n while (index < str.length) {\n // Reset shift, result, and byte\n byte = null;\n shift = 0;\n result = 0;\n do {\n byte = str.charCodeAt(index++) - 63;\n result |= (byte & 0x1f) << shift;\n shift += 5;\n } while (byte >= 0x20);\n latitude_change = resultChange(result);\n shift = result = 0;\n do {\n byte = str.charCodeAt(index++) - 63;\n result |= (byte & 0x1f) << shift;\n shift += 5;\n } while (byte >= 0x20);\n longitude_change = resultChange(result);\n lat += latitude_change;\n lng += longitude_change;\n coordinates.push([lng / factor, lat / factor]);\n }\n return coordinates;\n}\n/**\n * Encodes the given [latitude, longitude] coordinates array.\n *\n * @param coordinates Coordinates, in longitude, latitude order\n * @returns encoded polyline\n */\nfunction encode(coordinates, precision = 5) {\n if (!coordinates.length) {\n return \"\";\n }\n const factor = Math.pow(10, precision);\n let output = encodeNumber(coordinates[0][1], 0, factor) +\n encodeNumber(coordinates[0][0], 0, factor);\n for (let i = 1; i < coordinates.length; i++) {\n const a = coordinates[i];\n const b = coordinates[i - 1];\n output += encodeNumber(a[1], b[1], factor);\n output += encodeNumber(a[0], b[0], factor);\n }\n return output;\n}\n/**\n * Encodes a GeoJSON LineString feature/geometry.\n *\n * @param geojson A LineString\n */\nfunction geoJSONToPolyline(geojson, precision = 5) {\n return encode(geojson.coordinates, precision);\n}\n/**\n * Decodes to a GeoJSON LineString geometry.\n *\n * @param str An encoded polyline as a string.\n */\nfunction polylineToGeoJSON(str, precision = 5) {\n const coords = decode(str, precision);\n return {\n type: \"LineString\",\n coordinates: coords,\n };\n}\n\nexport { decode, encode, geoJSONToPolyline, polylineToGeoJSON };\n//# sourceMappingURL=polyline.es.mjs.map\n","import type { GeoJSONGeometry, Geometry, Leg, MapLibreGlDirectionsConfiguration, PolylineGeometry } from \"./types\";\nimport { decode } from \"@placemarkio/polyline\";\nimport type { Position, Feature, Point } from \"geojson\";\n\n/**\n * Decodes the geometry of a route to the form of a coordinates array.\n */\nexport function geometryDecoder(\n requestOptions: MapLibreGlDirectionsConfiguration[\"requestOptions\"],\n geometry: Geometry,\n): Position[] {\n if (requestOptions.geometries === \"geojson\") {\n return (geometry as GeoJSONGeometry).coordinates;\n } else if (requestOptions.geometries === \"polyline6\") {\n return decode(geometry as PolylineGeometry, 6);\n } else {\n return decode(geometry as PolylineGeometry, 5);\n }\n}\n\n/**\n * Decodes the congestion level of a specific segment of a route leg.\n */\nexport function congestionLevelDecoder(\n requestOptions: MapLibreGlDirectionsConfiguration[\"requestOptions\"],\n annotation: Leg[\"annotation\"] | undefined,\n segmentIndex: number,\n): number {\n if (requestOptions.annotations?.includes(\"congestion_numeric\")) {\n return annotation?.congestion_numeric?.[segmentIndex] ?? 0;\n } else if (requestOptions.annotations?.includes(\"congestion\")) {\n switch (annotation?.congestion?.[segmentIndex] ?? \"\") {\n case \"unknown\":\n return 0;\n case \"low\":\n return 1;\n case \"moderate\":\n return 34;\n case \"heavy\":\n return 77;\n case \"severe\":\n return 100;\n default:\n return 0;\n }\n } else {\n return 0;\n }\n}\n\n/**\n * Compares two coordinates and returns `true` if they are equal taking into account that there's an allowable error in\n * 0.00001 degree when using \"polyline\" geometries (5 fractional-digits precision).\n */\nexport function coordinatesComparator(\n requestOptions: MapLibreGlDirectionsConfiguration[\"requestOptions\"],\n a: Position,\n b: Position,\n): boolean {\n if (!requestOptions.geometries || requestOptions.geometries === \"polyline\") {\n return Math.abs(a[0] - b[0]) <= 0.00001 && Math.abs(a[1] - b[1]) <= 0.00001;\n } else {\n return a[0] === b[0] && a[1] === b[1];\n }\n}\n\n/**\n * Gets coordinates of a point feature\n */\nexport function getWaypointsCoordinates(waypoints: Feature<Point>[]): [number, number][] {\n return waypoints.map((waypoint) => {\n return [waypoint.geometry.coordinates[0], waypoint.geometry.coordinates[1]];\n });\n}\n\n/**\n * Gets bearings out of properties of point features\n */\nexport function getWaypointsBearings(waypoints: Feature<Point>[]): ([number, number] | undefined)[] {\n return waypoints.map((waypoint) => {\n return Array.isArray(waypoint.properties?.bearing)\n ? [waypoint.properties?.bearing[0], waypoint.properties?.bearing[1]]\n : undefined;\n });\n}\n","import type { MapLibreGlDirectionsConfiguration, PointType, Route } from \"./types\";\nimport type { Feature, LineString, Point } from \"geojson\";\nimport { MapLibreGlDirectionsDefaultConfiguration } from \"./types\";\nimport layersFactory from \"./layers\";\nimport { nanoid } from \"nanoid\";\nimport { congestionLevelDecoder, coordinatesComparator, geometryDecoder } from \"./helpers\";\n\n/**\n * @protected\n *\n * Takes a missing or an incomplete {@link MapLibreGlDirectionsConfiguration|configuration object}, augments it with the\n * default values and returns the complete configuration object.\n */\nexport function buildConfiguration(\n customConfiguration?: Partial<MapLibreGlDirectionsConfiguration>,\n): MapLibreGlDirectionsConfiguration {\n const layers = layersFactory(\n customConfiguration?.pointsScalingFactor,\n customConfiguration?.linesScalingFactor,\n customConfiguration?.sourceName,\n );\n return Object.assign({}, MapLibreGlDirectionsDefaultConfiguration, { layers }, customConfiguration);\n}\n\nexport type RequestData = {\n method: \"get\" | \"post\";\n url: string;\n payload: URLSearchParams;\n};\n\n/**\n * @protected\n *\n * Builds the routing-request method, URL and payload based on the provided\n * {@link MapLibreGlDirectionsConfiguration|configuration} and the waypoints' coordinates.\n */\nexport function buildRequest(\n configuration: MapLibreGlDirectionsConfiguration,\n waypointsCoordinates: [number, number][],\n waypointsBearings?: ([number, number] | undefined)[],\n): RequestData {\n const method = configuration.makePostRequest ? \"post\" : \"get\";\n\n let url: string;\n let payload: URLSearchParams;\n\n if (method === \"get\") {\n url = `${configuration.api}/${configuration.profile}/${waypointsCoordinates.join(\";\")}`;\n payload = new URLSearchParams(configuration.requestOptions as Record<string, string>);\n } else {\n url = `${configuration.api}/${configuration.profile}${\n configuration.requestOptions.access_token ? `?access_token=${configuration.requestOptions.access_token}` : \"\"\n }`;\n\n const formData = new FormData();\n\n Object.entries(configuration.requestOptions as Record<string, string>).forEach(([key, value]) => {\n if (key !== \"access_token\") {\n formData.set(key, value);\n }\n });\n\n formData.set(\"coordinates\", waypointsCoordinates.join(\";\"));\n\n // the URLSearchParams constructor works perfectly fine with FormData, so ignore the TypeScript's complaint\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n payload = new URLSearchParams(formData);\n }\n\n if (configuration.bearings && waypointsBearings) {\n payload.set(\n \"bearings\",\n waypointsBearings\n .map((waypointBearing) => {\n if (waypointBearing) {\n return `${waypointBearing[0]},${waypointBearing[1]}`;\n } else {\n return \"\";\n }\n })\n .join(\";\"),\n );\n }\n\n return {\n method,\n url,\n payload,\n };\n}\n\n/**\n * @protected\n *\n * Creates a {@link Feature<Point>|GeoJSON Point Feature} of one of the ${@link PointType|known types} with a given\n * coordinate.\n */\nexport function buildPoint(\n coordinate: [number, number],\n type: PointType,\n properties?: Record<string, unknown>,\n): Feature<Point> {\n return {\n type: \"Feature\",\n geometry: {\n type: \"Point\",\n coordinates: coordinate,\n },\n properties: {\n type,\n id: nanoid(),\n ...(properties ?? {}),\n },\n };\n}\n\n/**\n * @protected\n *\n * Creates a ${@link Feature<LineString>|GeoJSON LineString Features} array where each feature represents a\n * line connecting a waypoint with its respective snappoint and the hoverpoint with its respective snappoints.\n */\nexport function buildSnaplines(\n waypointsCoordinates: [number, number][],\n snappointsCoordinates: [number, number][],\n hoverpointCoordinates: [number, number] | undefined,\n departSnappointIndex: number, // might be -1\n showHoverpointSnaplines = false,\n): Feature<LineString>[] {\n if (waypointsCoordinates.length !== snappointsCoordinates.length) return [];\n\n const snaplines = waypointsCoordinates.map((waypointCoordinates, index) => {\n return {\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [\n [waypointCoordinates[0], waypointCoordinates[1]],\n [snappointsCoordinates[index][0], snappointsCoordinates[index][1]],\n ],\n },\n properties: {\n type: \"SNAPLINE\",\n },\n } as Feature<LineString>;\n });\n\n if (~departSnappointIndex && hoverpointCoordinates !== undefined && showHoverpointSnaplines) {\n snaplines.push({\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [\n [hoverpointCoordinates[0], hoverpointCoordinates[1]],\n [snappointsCoordinates[departSnappointIndex][0], snappointsCoordinates[departSnappointIndex][1]],\n ],\n },\n properties: {\n type: \"SNAPLINE\",\n },\n });\n\n snaplines.push({\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [\n [hoverpointCoordinates[0], hoverpointCoordinates[1]],\n [snappointsCoordinates[departSnappointIndex + 1][0], snappointsCoordinates[departSnappointIndex + 1][1]],\n ],\n },\n properties: {\n type: \"SNAPLINE\",\n },\n });\n }\n\n return snaplines;\n}\n\n/**\n * @protected\n *\n * Creates route lines from the server response.\n *\n * Each route line is an array of legs, where each leg is an array of segments. A segment is a\n * {@link Feature<LineString>|GeoJSON LineString Feature}. Route legs are divided into segments by their congestion\n * levels. If there's no congestions, each route leg consists of a single segment.\n */\nexport function buildRoutelines(\n requestOptions: MapLibreGlDirectionsConfiguration[\"requestOptions\"],\n routes: Route[],\n selectedRouteIndex: number,\n snappoints: Feature<Point>[],\n): Feature<LineString>[][] {\n // do the following stuff for each route (there are multiple when `alternatives=true` request option is set)\n return routes.map((route, routeIndex) => {\n // a list of coordinates pairs (longitude-latitude) the route goes by\n const coordinates = geometryDecoder(requestOptions, route.geometry);\n\n // get coordinates from the snappoint-features\n const snappointsCoordinates = snappoints.map((snappoint) => snappoint.geometry.coordinates);\n\n // add a variable to watch the current index to start the search from\n let currentIndex = 0;\n\n // indices of coordinate pairs that match existing snappoints (except for the first one)\n const snappointsCoordinatesIndices = snappointsCoordinates\n .map((snappointLngLat, index) => {\n // use the currentIndex to start the search from the place where the last snappoint's coordinate was found\n const waypointCoordinatesIndex = coordinates.slice(currentIndex).findIndex((lngLat) => {\n // there might be an error in 0.00001 degree between snappoint and decoded coordinate when using the\n // \"polyline\" geometries. The comparator neglects that\n return coordinatesComparator(requestOptions, lngLat, snappointLngLat as [number, number]);\n });\n\n const isLast = index === snappointsCoordinates.length - 1;\n\n // update the current index if something's found\n if (waypointCoordinatesIndex !== -1) {\n currentIndex += waypointCoordinatesIndex;\n } else if (isLast) {\n return coordinates.length - 1;\n }\n\n return currentIndex;\n })\n .slice(1); // because the first one is always 0 (first leg always starts with the first snappoint)\n\n // split the coordinates array by legs. Each leg consists of coordinates between snappoints\n let initialIndex = 0;\n const legsCoordinates = snappointsCoordinatesIndices.map((waypointCoordinatesIndex) => {\n return coordinates.slice(initialIndex, (initialIndex = waypointCoordinatesIndex + 1));\n });\n\n // an array to store the resulting route's features in\n const features: Feature<LineString>[] = [];\n\n legsCoordinates.forEach((legCoordinates, legIndex) => {\n const legId = nanoid();\n\n // for each pair of leg's coordinates\n legCoordinates.forEach((lngLat, i) => {\n // find the previous segment\n const previousSegment = features[features.length - 1];\n // determine the current segment's congestion level\n const segmentCongestion = congestionLevelDecoder(requestOptions, route.legs[legIndex]?.annotation, i);\n\n // only allow to continue the previous segment if it exists and if it's the same leg and if it's the same\n // congestion level\n if (\n legIndex === previousSegment?.properties?.legIndex &&\n previousSegment.properties?.congestion === segmentCongestion\n ) {\n previousSegment.geometry.coordinates.push(lngLat);\n } else {\n const departSnappointProperties = snappoints[legIndex].properties ?? {};\n const arriveSnappointProperties = snappoints[legIndex + 1].properties ?? {};\n\n const segment = {\n type: \"Feature\",\n geometry: {\n type: \"LineString\",\n coordinates: [],\n },\n properties: {\n id: legId, // used to highlight the whole leg when hovered, not a single segment\n routeIndex, // used to switch between alternative and selected routes\n route: routeIndex === selectedRouteIndex ? \"SELECTED\" : \"ALT\",\n legIndex, // used across forEach iterations to check whether it's safe to continue a segment\n congestion: segmentCongestion, // the current segment's congestion level\n departSnappointProperties, // include depart and arrive snappoints' properties to allow customization...\n arriveSnappointProperties, // ...of behavior via a subclass\n },\n } as Feature<LineString>;\n\n // a new segment starts with previous segment's last coordinate\n if (previousSegment) {\n segment.geometry.coordinates.push(\n previousSegment.geometry.coordinates[previousSegment.geometry.coordinates.length - 1],\n );\n }\n\n segment.geometry.coordinates.push(lngLat);\n\n features.push(segment);\n }\n });\n });\n\n return features;\n });\n}\n","import type { Map, MapGeoJSONFeature, MapMouseEvent, MapTouchEvent } from \"maplibre-gl\";\nimport type { Directions, MapLibreGlDirectionsConfiguration } from \"./types\";\nimport type { Feature, FeatureCollection, LineString, Point } from \"geojson\";\nimport {\n MapLibreGlDirectionsEvented,\n MapLibreGlDirectionsRoutingEvent,\n MapLibreGlDirectionsWaypointEvent,\n} from \"./events\";\nimport {\n buildConfiguration,\n buildRequest,\n buildPoint,\n buildSnaplines,\n buildRoutelines,\n type RequestData,\n} from \"./utils\";\nimport { getWaypointsBearings, getWaypointsCoordinates } from \"./helpers\";\n\n/**\n * The main class responsible for all the user interaction and for the routing itself.\n */\nexport default class MapLibreGlDirections extends MapLibreGlDirectionsEvented {\n constructor(map: Map, configuration?: Partial<MapLibreGlDirectionsConfiguration>) {\n super(map);\n\n this.map = map;\n this.configuration = buildConfiguration(configuration);\n\n /*\n * Bind the event listeners to `this` since e.g. `map.off(\"type\", onMove)` won't work if it was registered\n * as `map.on(\"type\", onMove.bind(this))`. `onMove !== onMove.bind(this)`, but\n * `onMoveHandler === onMoveHandler`!\n */\n this.onMoveHandler = this.onMove.bind(this);\n this.onDragDownHandler = this.onDragDown.bind(this);\n this.onDragMoveHandler = this.onDragMove.bind(this);\n this.onDragUpHandler = this.onDragUp.bind(this);\n this.onClickHandler = this.onClick.bind(this);\n this.liveRefreshHandler = this.liveRefresh.bind(this);\n\n this.init();\n }\n\n /*\n * Everything is `protected` to allow access from a subclass.\n */\n protected declare readonly map: Map;\n protected readonly configuration: MapLibreGlDirectionsConfiguration;\n\n protected _interactive = false;\n protected _hoverable = false;\n protected buildRequest = buildRequest;\n protected buildPoint = buildPoint;\n protected buildSnaplines = buildSnaplines;\n protected buildRoutelines = buildRoutelines;\n\n protected onMoveHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n protected onDragDownHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n protected onDragMoveHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n protected onDragUpHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n protected onClickHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n protected liveRefreshHandler: (e: MapMouseEvent | MapTouchEvent) => void;\n\n protected profiles: string[] = [];\n protected _waypoints: Feature<Point>[] = [];\n protected snap