npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geoleaf-plugins/routing

v1.0.5

Published

GeoLeaf routing plugin

Readme

@geoleaf-plugins/routing

Route computation plugin for GeoLeaf JS. It asks an external routing provider for an itinerary between two or more points, and normalises the answer into a single model whatever the provider.

Routes are computed by the plugin's itinerary panel, which the profile's "Route to here" action opens (see Load it eagerly below). GeoLeaf.Routing exposes the rest: the providers (registerProvider, getProvider, listProviders), the composition of waypoints (addWaypoint, removeWaypoint, moveWaypoint, clearWaypoints, isRoutable, maxWaypoints), the reading of a computed route (legSummaries, routeFeatures) and its display (publishRoute, clearRoute).

Note — There is no GeoLeaf.Routing.route() yet: a route is computed by the panel, from the provider the profile configures. A route you compute elsewhere is displayed with publishRoute().


Installation

npm install @geoleaf-plugins/routing

Note — Prerequisite: @geoleaf/core must be loaded before this plugin.


Quick start

import "@geoleaf-plugins/routing";

// After GeoLeaf.boot() — GeoLeaf.Routing is available on globalThis.GeoLeaf
const provider = GeoLeaf.Routing.getProvider(); // { id, attribution } of the configured engine
// The profile's feature-info action widget must declare this id to open the panel:
const actionId = GeoLeaf.Routing.actionId();

Load it eagerly, not on demand

Import this plugin before GeoLeaf.boot(). It is deliberately the light half of the two navigation packages, and lazy-loading it does not work the way it looks like it should: the "Route to here" entry point is a profile-declared action widget whose visibility is gated on GeoLeaf.plugins.isLoaded(). A lazily registered plugin only enters the registry once it has loaded — so it would hide its own entry point, and nothing would ever trigger the load that reveals it. No error is emitted; the button simply never appears.


Attribution — read this before you ship

Both built-in engines route on OpenStreetMap. Its ODbL permits commercial use and requires nothing to be shared back for a route drawn on a map — but it does require attribution wherever the derived work is shown. A map displaying a computed itinerary without crediting the data is out of compliance, and the party out of compliance is the one shipping the map.

So the plugin does it for you, and you should know how, in case you build your own interface:

  • Every provider declares an attribution. It is required, and createProvider() refuses a provider whose attribution is missing or blank — it will log which one and return null rather than compute a route it cannot credit.
  • RouteResult.attribution carries the credit for that route. It travels with the data rather than being read from configuration, because a route computed by one engine can still be on screen after you have pointed the profile at another.
  • publishRoute() mounts the credit in the map container; clearRoute() removes it. If you publish the geometry yourself instead of using these, the credit is yours to display.
  • getProvider() answers { id, attribution } for the configured engine, or null.

Registering your own engine? Declare its credit:

GeoLeaf.Routing.registerProvider("my-engine", (endpoint) => ({
    id: "my-engine",
    attribution: "© My data source",
    async route(request) {
        /* … */
    },
}));

⚠️ There is no default credit and there will not be one. Substituting "© OpenStreetMap contributors" for an unknown engine would attribute data to a source that may not have produced it — a false statement rather than a missing one.

Configuration — modules.routing

| Key | Type | Default | Meaning | | -------------- | -------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | enabled | boolean | true | Turns the plugin on. | | showButton | boolean | false | Shows the toolbar entry point. | | provider | string | "valhalla" | Which engine to query. An id with no registered adapter yields null — never a silent substitution. | | endpoint | string | "" | Base URL of the engine. Empty selects the provider's own default. A value that does not start with https:// is refused, never downgraded. | | timeoutMs | number | 10000 | How long before the request is aborted. ≤ 0 or non-finite falls back to the default. | | maxWaypoints | number | 10 | Cap on stops. Beyond it, adding is refused with the limit named. | | profile | string | "car" | Travel mode: car, foot or bike. An unknown token falls back to car rather than reaching the engine. | | layerId | string | "routing-route" | The profile layer the computed route is published into. | | labelField | string \| string[] | "name" | The feature property (or ordered list of candidates) carrying a stop's display name. |

showButton defaults to false on purpose while there is no panel to open: a visible control that does nothing is worse than an absent one, because it does not announce itself — it gets clicked.

🛑 A LIST is accepted, and it is what a multi-layer profile needs. The entry-point button is declared PER LAYER in the profile, while this setting is global to the plugin — and layers rarely agree on the name of their label. The first candidate that is present and non-empty wins; a plain string still works and means a list of one. Guessing is still refused: taking "the first string property" would name a destination after a status code.

⚠️ labelField carries a constraint no schema expresses: the property it names must ALSO be listed in the payloadFields of the profile's action widget. With only one of the two, the event arrives without the property and the destination has no name — a profile that is valid on both sides and renders a silent panel.

{
    "modules": {
        "routing": {
            "enabled": true,
            "showButton": false,
            "provider": "valhalla",
            "endpoint": "https://routing.example.org",
            "timeoutMs": 10000,
            "maxWaypoints": 10
        }
    }
}

🛑 An API key placed in a profile is publicly readable

A profile is static JSON served to the browser. Everything in it — including anything you put in endpoint — reaches every visitor, and reaches them in a file they can open, save and share. https://routing.example.org/route?api_key=abc123 in a profile is not a configured secret: it is a published one.

This is not a theoretical concern for this repository. Its own deploy/ folder once shipped a development token in a file that was .gitignored and guarded against execution on non-local hosts — and a plain curl of the deployed URL still returned it in clear. Two safeguards, and an angle between them: a secret is read, it is not executed.

⚠️ A build-time environment variable is not an answer either. Injected at build, it ends up in a served file — the same exposure under another name.

What to do instead, in order of preference:

  1. Use an engine that needs no key. Several public and self-hostable routing engines do not. endpoint exists precisely so you can point at your own.
  2. Put the key on a server you control, and give endpoint the URL of a thin proxy that adds it. The browser then holds nothing.
  3. If the key must reach the browser, restrict it at the provider — by HTTP referrer, by origin, and by quota. A restricted key that leaks costs you a quota; an unrestricted one costs you a bill.

⚠️ Restriction by referrer is a mitigation, not a secret: it makes the key useless from another origin, it does not make it unreadable from yours. Treat a key in a profile as public, and choose it so that being public is acceptable.


Relationship to @geoleaf-plugins/navigation

Two packages, and the split is not arbitrary. Route computation is useful on its own — knowing the distance and the path to a point does not require being guided along it. Turn-by-turn guidance, on the other hand, is worthless without a route. The dependency is asymmetric, so that is where the boundary goes, and a single package would make every consumer pay for a guidance runtime most of them do not want.

@geoleaf-plugins/navigation depends on this package; the reverse is never true.


License

MIT © 2026 Mattieu Pottier