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

@nika-js/onlymap-remotion

v0.1.0-rc.1

Published

Render OnlyMap maps in Remotion videos. Bring a GeoJSON route, places, or a map story — camera, markers and duration are derived from your data, and every frame waits for the map to finish drawing.

Readme

@nika-js/onlymap-remotion

Render OnlyMap maps inside Remotion compositions. Bring a route, a list of places, or a map story — the camera, the markers, and the video's length are derived from that data, and every frame is held until the map has genuinely finished drawing.

npm install @nika-js/onlymap-remotion @nika-js/onlymap remotion

The problem this solves

Remotion makes videos by screenshotting a React page once per frame. A map is the worst possible thing to screenshot: tiles stream in asynchronously, labels settle over hundreds of milliseconds, and the camera animates on its own clock. Point Remotion at a map naively and you get blurry frames, missing labels, and a video that renders differently every time.

This package makes a map safe to screenshot — then gets out of Remotion's way, so the map is just another React element your titles, charts and audio can be composed against.

You may not need it. If the video is just the map moving, npx onlymapjs record map.html --out flyby.mp4 is one command with none of the caveats below. Reach for this package when you need content reacting to the map per frame, or one composition producing many videos.

Required setup

One step, and it is not optional:

// remotion.config.ts
import { Config } from "@remotion/cli/config";

Config.setChromiumOpenGlRenderer("angle");          // REQUIRED
Config.setDelayRenderTimeoutInMilliseconds(120_000);

Remotion's default software renderer cannot create a WebGL context for MapLibre — every render fails with BindToCurrentSequence failed. Programmatic @remotion/renderer calls do not read the config file; pass chromiumOptions: { gl: "angle" } there instead.

Quickstart

// Root.tsx
import { Composition, staticFile } from "remotion";
import { OmMapVideo, omMapVideoMetadata } from "@nika-js/onlymap-remotion";

const FPS = 30;

export const RemotionRoot = () => (
  <Composition
    id="flyover"
    component={OmMapVideo}
    fps={FPS}
    width={1920}
    height={1080}
    durationInFrames={1}                      // replaced by calculateMetadata
    defaultProps={{ src: staticFile("map.html"), route: myRoute, zoom: 14 }}
    calculateMetadata={omMapVideoMetadata({ fps: FPS, metersPerSecond: 600 })}
  />
);
npx remotion studio                                   # scrub it
npx remotion render src/index.ts flyover out/video.mp4 --concurrency 2

That is a finished flyover: the camera follows the route facing down it, with a duration computed from the route's real ground distance. No coordinates typed, no timing hand-tuned.

Choosing a camera source

The prop you pass decides how the camera moves. TypeScript permits only one at a time, so a contradictory composition will not compile.

| You have | Prop | Derived for you | |---|---|---| | GeoJSON LineString | route | position along the path, follow-bearing, duration from ground distance | | A list of places | places | dwell-and-travel tour, duration from the list | | An OnlyMap story | (none) | camera and duration from the story's own timeline | | Composed shots | keyframes | interpolation between your poses |

<OmMapVideo src={src} route={route} zoom={14} pitch={55} />
<OmMapVideo src={src} places={[{ lngLat: [2.29, 48.86], zoom: 15, label: "Eiffel" }]} />
<OmMapVideo src={src} keyframes={shots} />
<OmMapVideo src={src} />                     {/* the manifest's own <om-story> */}

omMapVideoMetadata reads the same prop to derive the duration, so a longer route is a longer video and an extra place is a longer tour. Hardcoding durationInFrames is the one mistake that undoes the whole model.

Hand-typed coordinates are the escape hatch, not the starting point. If you are typing longitudes, you probably want a route file from geojson.io instead.

<OmMapVideo> props

| Prop | Type | Notes | |---|---|---| | src | string | Manifest URL — wrap local files in staticFile(). Mutually exclusive with html | | html | string | Manifest markup inline, for a generated manifest | | route | RouteInput | LineString, Feature, or bare [lng, lat][]. Pairs with zoom, pitch, bearing ("follow" by default), easing | | places | FlyThroughPlace[] | Pairs with dwellFrames (30), travelFrames (60), easing | | keyframes | CameraKeyframe[] | { frame, longitude, latitude, zoom, pitch?, bearing? }, with easing | | storyId | string | Which <om-story> to drive, when the manifest has several | | showTimeline | boolean | Register each story step as a named track in Studio's timeline | | settleTimeoutMs | number | Per-frame tile budget, also the frame's delayRender timeout. Default 60_000 | | previewGate | "fast" \| "exact" | Preview only. "fast" (default) makes scrubbing instant; rendering always gates exactly | | failOnValidationError | boolean | Turn the map's severity-error validation entries into a failed render. Default false | | onMapReady | (mapEl, storyEl) => void | Fires once — the hook for projection overlays | | onFrameSettled | (info) => void | Per frame. settled: false means that frame may show unrefined tiles | | children | ReactNode | Composited over the map |

Anything the wrapper cannot express — a spring, a data-driven zoom, a camera chasing live telemetry — drops to the useOmManifest and useOmCamera hooks it is built from, which stay public.

Overlays that track geography

The capability a recorded MP4 cannot offer: because the map is live DOM in your React tree, Remotion content stays pinned to real places as the camera flies.

import { OmMapVideo, useProjectedPoints } from "@nika-js/onlymap-remotion";

const [mapEl, setMapEl] = useState(null);
const { points, project } = useProjectedPoints(mapEl);

<OmMapVideo
  src={src}
  route={route}
  onMapReady={setMapEl}
  onFrameSettled={() => project({ hq: [-122.4014, 37.7935] })}
>
  {points.hq && (
    <div style={{ position: "absolute", left: points.hq[0], top: points.hq[1] }}>HQ</div>
  )}
</OmMapVideo>

useProjectedPoints exists because the commit must be flushSync inside onFrameSettled — a plain setState lands after the frame is captured, so every overlay would render one frame stale and visibly lag the map. Use isOnScreen(point, width, height, margin) to cull anchors that leave the frame.

One composition, many videos

Because the manifest and the camera are both functions of props, thirty regional videos are thirty renders of one composition rather than thirty files to maintain:

npx remotion render src/index.ts flyover out/tokyo.mp4 --props='{"route": …}'

This is the case onlymapjs record structurally cannot serve — it takes a manifest file and produces one video.

Authoring aids

  • Typed props panel@nika-js/onlymap-remotion/schemas exports zod schemas (cameraKeyframeSchema, flyThroughPlaceSchema, omAnchorSchema, keyframeCompositionSchema). Pass one to <Composition schema> and Studio renders form fields instead of raw JSON. zod is an optional peer and must match the version your Remotion bundles.
  • Render-cost readoutuseSettleStats() + <OmSettleHud> show frames, misses, hold times and a projected render time: is this scene render-affordable? Studio only; never appears in output. Holds read as zero under the default fast preview — measure with previewGate="exact".

Things that will surprise you

  • Preview and render gate differently, on purpose. Studio releases each frame as soon as the camera is written, so scrubbing is instant and tiles pop in behind the playhead. Rendered output always waits for the map to settle.
  • Studio cannot host interactive map UI. Its preview overlays consume pointer events before a composition sees them, so buttons and map gestures inside a composition receive nothing. This is Remotion's design — runtime interactivity is a @remotion/player feature.
  • Your manifest is rewritten before mounting. Every <om-map> gains the recording switch, <script> tags are removed (they never execute under innerHTML, and a second OnlyMap runtime would fail deck.gl's duplicate-version check), and loop is stripped. Each warns once.
  • Concurrency turns over. Measured on a basemap scene: 2 → 1.66×, 4 → 1.90×, 8slower than 4. Use --concurrency 2. Every worker is a tab with a cold tile cache, so start at 1 on metered tile providers.
  • Distributed rendering is out of scope. Remotion Lambda and Cloud Run have no GPU, and ANGLE on a real GPU is the only verified way to get a WebGL context. For batch work, run N independent render --props invocations.

Determinism, and why it matters

The same frame renders byte-identically whether produced alone, in sequence, or out of order. That is what makes parallel rendering and frame caching safe — without it, "re-render just the part I changed" quietly yields a video that does not match itself.

It is enforced, not asserted: a render suite boots a real browser and byte-compares frames across processes and orderings, and a parity harness runs the same manifest through onlymapjs record and through this package — currently 0.9999 structural similarity at aligned story time, across two different Chromium and GL stacks.

Not a replacement for record

If you want an MP4 of a flyby with nothing layered on top, use onlymapjs record — simpler, no extra licence to consider, and often faster. The two compose: record once, then place the file like any other footage.

// npx onlymapjs record map.html --out public/flyby.mp4 --gpu
<OffthreadVideo src={staticFile("flyby.mp4")} />

That skips this package's per-render cost entirely, at the price of a closed clip — nothing inside it can react to frames, props, or projection.

Requirements

| Peer | Version | Notes | |---|---|---| | @nika-js/onlymap | exact pin | It bundles deck.gl/luma.gl, which fail on duplicate-version detection | | remotion | ≥ 4.0.342 | Uses the scoped useDelayRender hook | | react, react-dom | ≥ 18 | | | zod | optional | Only for /schemas; must match your Remotion's version |

API stability

Primary surface, treated as stable: OmMapVideo, omMapVideoMetadata, OmStory, OmMapStill, useOmCamera, cameraAlongRoute, flyThrough, cameraFromKeyframes, routeLengthMeters, pointAlongRoute, flyThroughDuration, projectLngLat, isOnScreen, useProjectedPoints, omStoryMetadata.

Secondary surface — supported, but shaped by internals and likelier to move across minor versions: useOmManifest, useOmStoryFrame, prepareManifest, measureStoryDuration, parseStorySteps, stepToFrames, setCameraPose, readCameraPose, useSettleStats, OmSettleHud, ManifestError, storyFrameCount, resolveGatePolicy, cameraSourceOf, DEFAULT_SETTLE_TIMEOUT_MS.

Licensing

Free for non-commercial use — personal, educational, academic research, or evaluation — provided the OnlyMap attribution rendered into each frame stays visible in your output. Anything else (a business, a revenue-generating channel, paid client work) needs a commercial licence. Full terms in LICENSE.md.

Whether use is non-commercial is judged by what the video is for, not where it was rendered. OnlyMap lifts its free-plan caps outside hosted http(s) contexts and rendering always runs in such a context — so the caps being inactive during a render is not a grant.

Two further licences apply and neither is ours to give: @nika-js/onlymap (its own commercial licence) and remotion (source-available; may require a paid licence by organisation size or render volume).

Independence. This is an independent integration. It is not affiliated with, endorsed by, or sponsored by Remotion. "Remotion" is a trademark of its respective owner, used here only to describe compatibility.

Documentation