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

@la-trace/map-sdk

v0.1.1

Published

LaTrace cartography SDK — drop-in MapLibre-based map with LaTrace basemaps, smart pins and tracks.

Readme

@la-trace/map-sdk

Drop-in interactive map for LaTrace integrations: ship the same basemaps, typed POI markers, tracks and shapes the LaTrace product uses, in any web app, in a few lines.

Built on top of MapLibre GL JS. Framework-agnostic — works in vanilla, React, Vue, Svelte, Angular.

npm version


Features

  • 3 LaTrace basemaps out of the box: Plan, Satellite, Relief — MapTiler-backed, MapLibre-rendered.
  • Smart pin layer: collision-aware marker placement + density-sampled circles below the marker zoom threshold. Same algorithm as the LaTrace product.
  • 179 LaTrace POI categories baked-in (Castle, Restaurant, Bakery, Beach, …) with the LaTrace droplet design and the matching color palette.
  • Tracks for GPX-like content (LineString / MultiLineString / GeoJSON Features), with hover & click events.
  • Shapes for zones / overlays (polygon, rectangle, circle by radius), interactive.
  • 3D terrain toggle (MapTiler RGB DEM), one method call.
  • Active POIs: mark a pin as selected — the SDK promotes it to a marker, runs an emphasized animation, and bypasses collision so it never disappears.
  • Built-in MapNav control matching the LaTrace UI (zoom group, basemap picker with previews, 3D, GPS, fullscreen). Each section is individually toggleable, or you can drop the whole nav.
  • Mobile bottom-sheet for the basemap picker, portaled into document.body so it always sits above your app's overlays.
  • Typed events for everything (clicks, hovers, camera, basemap, terrain, active pins).

Install

npm install @la-trace/map-sdk maplibre-gl
# or pnpm add @la-trace/map-sdk maplibre-gl
# or yarn add @la-trace/map-sdk maplibre-gl

maplibre-gl is a peer dependency.


Quick start

<div id="map" style="width: 100%; height: 480px;"></div>
import '@la-trace/map-sdk/style.css';
import { createLaTraceMap, laTracePoiPin } from '@la-trace/map-sdk';

const map = createLaTraceMap({
  container: '#map',
  apiKey: 'your-latrace-api-key',
  basemap: 'plan',
  center: [2.3522, 48.8566],
  zoom: 5,
});

map.on('ready', () => {
  map.addPin(
    laTracePoiPin({
      id: 'eiffel',
      lat: 48.8584,
      lng: 2.2945,
      label: 'Tour Eiffel',
      type: 'Monument',
      score: 100,
    }),
  );
});

map.on('pin:click', ({ pin }) => {
  console.log('Clicked', pin.id);
  map.setActivePins([pin.id]);
});

The bundled stylesheet already embeds maplibre-gl.css, so the import above is the only one you need.


API reference

createLaTraceMap(options)

| Option | Type | Default | Notes | |---|---|---|---| | container | string \| HTMLElement | required | CSS selector, element id, or DOM node. | | apiKey | string | required | Your LaTrace API key (pass-through for now, future-gated). | | basemap | 'plan' \| 'satellite' \| 'topo' | 'plan' | Initial style. | | center | [lng, lat] | [2.3522, 48.8566] | Initial center. | | zoom | number | 5 | Initial zoom. | | minZoom / maxZoom | number | — | Optional clamps. | | pins | PinClusteringOptions | see below | Marker clustering tuning. | | mapNav | false \| MapNavOptions | {} (all on) | Built-in nav control config. |

PinClusteringOptions

| Field | Default | Notes | |---|---|---| | markerExclusionRadiusPx | 180 | Half-size of the no-collision square around each marker. | | minMarkerZoom | 4.5 | Below this zoom, every pin is a circle (active pins still render as markers). | | maxCirclePoints | 1500 | Cap on circles drawn after density sampling. | | circleColor | '#0D1D27' | Default circle fill. Per-pin override via pin.circleColor. |

MapNavOptions

| Field | Default | Notes | |---|---|---| | fullscreen | true | Show the fullscreen button. | | stylePicker | true | Show the basemap picker. | | terrain3d | true | Show the 3D toggle. | | gps | true | Show the GPS button. | | zoom | true | Show the zoom group. | | position | 'bottom-right' | MapLibre ControlPosition. |

LaTraceMap instance

// Pins
map.addPin(pin);
map.addPins(pins);
map.removePin(id);
map.clearPins();
map.setPinOptions({ circleColor: '#15803d' });
map.addLaTracePin({ id, lat, lng, type: 'Castle' });

// Active selection (force-promote to marker + emphasized variant)
map.setActivePin(id);
map.setActivePins([id1, id2]);
map.unsetActivePin(id);
map.clearActivePins();
map.getActivePins();

// Tracks (GeoJSON LineString / MultiLineString / Feature / FeatureCollection)
map.addTrack({ id, geometry, color, width, opacity });
map.updateTrack({ id, geometry, ... });
map.removeTrack(id);
map.clearTracks();

// Shapes
map.addShape({ kind: 'rectangle', id, sw, ne, fillColor, ... });
map.addShape({ kind: 'circle', id, center, radiusMeters, ... });
map.addShape({ kind: 'polygon', id, rings, ... });
map.updateShape(shape);
map.removeShape(id);
map.clearShapes();

// Camera
map.flyTo({ center, zoom, duration });
map.fitBounds([[w, s], [e, n]], padding);
map.setBasemap('satellite');
map.getBasemap();
map.setTerrain3d(true);
map.getTerrain3d();
map.toggleTerrain3d();

// Geolocation (drives MapLibre's GeolocateControl, blue pulsing dot)
map.triggerGeolocate();

// Lifecycle
map.destroy();

// Escape hatch — the underlying maplibregl.Map
map.raw;

Events

map.on('ready', () => {});
map.on('pin:click',        ({ pin, lngLat }) => {});
map.on('pin:mouseenter',   ({ pin, lngLat }) => {});
map.on('pin:mouseleave',   ({ pin, lngLat }) => {});
map.on('track:click',      ({ track, lngLat }) => {});
map.on('track:mouseenter', ({ track, lngLat }) => {});
map.on('track:mouseleave', ({ track, lngLat }) => {});
map.on('shape:click',      ({ shape, lngLat }) => {});
map.on('shape:mouseenter', ({ shape, lngLat }) => {});
map.on('shape:mouseleave', ({ shape, lngLat }) => {});
map.on('map:click',        ({ lngLat, point }) => {});
map.on('map:move',         (snapshot) => {});  // continuous
map.on('map:moveend',      (snapshot) => {});  // settled
map.on('map:zoom',         ({ zoom }) => {});
map.on('map:zoomend',      ({ zoom }) => {});
map.on('basemap:change',   ({ basemap }) => {});
map.on('terrain3d:change', ({ enabled }) => {});
map.on('activepins:change', ({ ids }) => {});

MapCameraSnapshot (passed to map:move / map:moveend):

{ center: [lng, lat], zoom, bearing, pitch, bounds: { sw: [lng, lat], ne: [lng, lat] } }

map.on(event, handler) returns an unsubscribe function. map.off(event, handler) works too.

Pin shape

interface Pin {
  id: string;          // stable, used for events / updates / removal
  lat: number;
  lng: number;
  score?: number;      // higher = wins collision priority
  label?: string;      // echoed back in events; SDK does not render it
  circleColor?: string;
  markerHtml?: string;          // see security note below
  markerHtmlEmphasized?: string;
  anchor?: 'center' | 'top' | 'bottom' | 'left' | 'right';
  data?: unknown;      // arbitrary, echoed back
}

A pin without markerHtml stays a circle forever — even if you call setActivePin on it. To get the LaTrace droplet automatically, build the pin via laTracePoiPin({ type }):

import { laTracePoiPin } from '@la-trace/map-sdk';

const pin = laTracePoiPin({
  id: 'p1', lat: 48.85, lng: 2.34, score: 90,
  type: 'Restaurant',  // one of 179 LaTrace categories
});

type is a LaTracePoiType string union — your editor will autocomplete the full list.


Security

HTML injection in custom markers

The SDK inserts pin.markerHtml, pin.markerHtmlEmphasized, and any custom iconSvg you pass to renderLaTracePoiMarkerHtml via innerHTML. If you build those strings from arbitrary user data, you are responsible for escaping it.

The SDK exports an escapeHtml helper you can use for the common case:

import { escapeHtml } from '@la-trace/map-sdk';

const pin = {
  id: place.id, lat: place.lat, lng: place.lng,
  markerHtml: `<div class="poi">${escapeHtml(place.userSubmittedName)}</div>`,
};

For richer untrusted content (HTML coming from a CMS, a third-party API, etc.), run it through your usual sanitizer (DOMPurify, sanitize-html, …) before handing it to the SDK.

MapTiler API key

The MapTiler API key used to fetch the LaTrace basemaps is embedded in the SDK bundle at build time. It is not a secret in the defense-in-depth sense — anyone inspecting the JavaScript can extract it — and that's expected for client-side maps. The key will be locked down to a list of LaTrace-approved origin domains via the MapTiler dashboard once integration partners are onboarded; until then, the same key is shared across all SDK consumers.

LaTrace API key (apiKey option)

The apiKey you pass to createLaTraceMap({ apiKey }) is stored but not validated in this version. It will be checked server-side in a future release, alongside the MapTiler-key origin whitelisting, so plan your integration around providing a real key per environment now — swapping it later won't require a code change on your side.

What is sent over the network

The SDK only talks to MapTiler (basemap tiles, optional terrain DEM tiles, optional geocoding) — no LaTrace endpoint is contacted by default. Anything you display via pins/tracks/shapes stays in the browser unless your own code uploads it elsewhere.


TypeScript

Everything is typed:

import type {
  CreateLaTraceMapOptions,
  Basemap,
  Pin,
  Track,
  Shape,
  PinClusteringOptions,
  MapNavOptions,
  LaTracePoiType,
  MapCameraSnapshot,
  // …
} from '@la-trace/map-sdk';

The escape hatch map.raw is typed as maplibregl.Map.


Frameworks

The SDK is vanilla — no framework lock-in. Wrap it in whatever you use:

// React
import { useEffect, useRef } from 'react';
import { createLaTraceMap, type LaTraceMap } from '@la-trace/map-sdk';

export function Map({ apiKey }: { apiKey: string }) {
  const ref = useRef<HTMLDivElement>(null);
  const mapRef = useRef<LaTraceMap | null>(null);

  useEffect(() => {
    if (!ref.current) return;
    const map = createLaTraceMap({ container: ref.current, apiKey });
    mapRef.current = map;
    return () => map.destroy();
  }, [apiKey]);

  return <div ref={ref} style={{ width: '100%', height: 480 }} />;
}

Examples

Live demos covering the main integration patterns are maintained in a dedicated public repo: latrace-code/la-trace-sdk-examples.

| Example | Stack | What it shows | |---|---|---| | basic-vanilla | Vite + TS | Full-feature tour: typed POIs, custom markers, tracks, shapes, every event logged. | | react-app | Vite + React + TS | Reusable <LaTraceMapView> component, sidebar-driven selection. | | tracking-trip | Vite + TS | Animated GPX-style replay with follow-camera and scrubber. |

Clone the repo (or open one of the examples on StackBlitz / CodeSandbox using the GitHub import URL) — each folder runs with pnpm install && pnpm dev, no monorepo setup required.


License

Proprietary — see LICENSE. Use is permitted as part of a LaTrace integration; redistribution requires a written agreement.