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

ria-3d-shape-editor

v0.1.4

Published

Generic Point/LineString/Polygon create+edit controller for LuciadRIA with independent per-vertex height (Z) editing

Readme

ria-3d-shape-editor

A single LuciadRIA controller that creates and edits Point/Polyline/Polygon shapes with independent control over each vertex's height (Z) - the affordance missing from LuciadRIA's stock BasicCreateController/EditController, which were built when RIA was 2D-only.

Fully independent of any backend: it only mutates the shape it is given (or creates) and emits events describing what happened. Persistence (WFS-T or anything else) is entirely up to the caller. Works identically with mouse and touch input, with no separate code path for either.

Installation

npm install ria-3d-shape-editor

Compatible with @luciad/ria 2025.0 or higher.

Usage

import {Shape3DEditController} from "ria-3d-shape-editor";
import {ShapeType} from "@luciad/ria/shape/ShapeType.js";

// Create a new Point. `layer` is required: the controller reads `layer.model.reference` once, at
// construction, and every shape it creates/emits is always in that reference - never in
// map.reference - so it can be added straight to `layer.model` (or a WFS-T store backing it)
// without any reprojection on your part.
const controller = new Shape3DEditController(ShapeType.POINT, layer);
map.controller = controller;

controller.on("ShapeCreated", ({shape}) => {
  // shape is a Point, already in layer.model.reference; save it however you like
});

controller.on("ShapeChanged", ({shape}) => {
  // fired on every vertex move/insert/removal while editing
});

controller.on("ShapeEditingFinished", ({shape, confirmed}) => {
  // fired once, when the controller is deactivated while editing - persist only if confirmed
});
// Edit an existing shape directly - skips creation, starts in edit mode immediately
const controller = new Shape3DEditController(ShapeType.POLYGON, layer, {existingShape: existingPolygon});
map.controller = controller;

Handles

Each vertex offers several simultaneous, individually-grabbable handles. Only the active vertex (or, if selected instead, an active midpoint) shows the full set below - every other vertex draws just a plain, clickable marker, so a shape with many vertices doesn't turn into a field of overlapping icons.

| Handle | Gesture | Effect | | --- | --- | --- | | Vertex itself ("free") | drag | Moves in X/Y/Z, continuously adopting whatever terrain/mesh surface is under the cursor | | Move | drag | Moves in X/Y only - height frozen | | Height | drag | Moves in Z only - X/Y frozen | | Rotate | drag | Swings the whole shape horizontally around this vertex, which stays fixed as the pivot - only shown while whole-shape mode is armed | | Remove | click/tap | Removes this vertex outright (also reachable via double-click/double-tap anywhere near a vertex) | | Whole-shape toggle | click/tap | Arms/disarms whole-shape mode (see below) | | Finish | click/tap | Confirms the shape and ends editing | | Cancel | click/tap | Discards changes made this session and ends editing | | Midpoint marker | click to select, then drag one of its own handles | Promotes that segment's midpoint into a real vertex |

Move/Height/Rotate/Remove/the toggle/Finish/Cancel only appear on a 3D (EPSG:4978, geocentric) map view - on a 2D map there's no well-defined "up" to make height or rotation meaningful. Use controller.setVertexPosition(index, point) to set a height programmatically in that case.

Whole-shape mode

Clicking/tapping the toggle handle arms whole-shape mode: every subsequent drag on Move, Height, Rotate, or the vertex itself now acts on the entire shape - every other vertex is either rigidly carried along by the same move/height delta, or swung around the active vertex by Rotate - instead of only the one active vertex. It's sticky (stays armed across drags and across switching which vertex is active) and global (one on/off state for the whole session), and works identically for mouse and touch - there is no keyboard-modifier equivalent.

Live feedback while dragging

A guide line plus a live numeric label (distance, height delta, or swept angle, auto-formatted in the configured unit family) tracks every drag. Two more visual aids are opt-in via constructor options (below): a translucent ground-reference grid while dragging Move, and a line straight down to Earth's center - the exact rotation axis when dragging Rotate, a depth/occlusion cue otherwise. Everything drawn by this controller is occlusion-aware: geometry that's genuinely part of the shape recolors when hidden behind terrain/a mesh, while handle icons (which aren't real points on the shape) simply stay visible on top regardless.

Creating a shape

Click to place each vertex of a LineString/Polygon, moving the mouse in between to live-preview the next one; double-click (or, for Point, a single click) to finish. The cursor is a crosshair for the whole creation phase - a Point's rubber-band vertex icon gives its own visual feedback immediately, but a LineString/Polygon draws nothing at all until the first point is placed, so the crosshair is what signals the controller is active in the meantime. It reverts to the default pointer the moment editing begins.

Touch support

Every gesture above works the same way with a finger as with a mouse:

  • Touch gets a larger effective hit tolerance (~44-48px, matching Apple/Material touch-target guidance) than the ~24px a mouse pointer gets, and hit-testing is always recomputed fresh at the actual touch/drag position rather than relying on a stale hover state (touch has no hover-before-contact the way a mouse does).
  • The actively-dragged icon is drawn offset above the fingertip, so it's never hidden underneath the very finger dragging it.
  • A short haptic pulse fires on grabbing and releasing a handle (silently skipped on platforms without the Vibration API, e.g. iOS Safari).
  • Double-tap removes a vertex, same as double-click.

htmlToolbar option

Small, 3D-anchored Finish/Cancel icons and a virtual keyboard for a height value can be genuinely hard to hit/use reliably with a finger. Setting htmlToolbar: true replaces the canvas-drawn Finish/Cancel icons with fixed HTML buttons (X/checkmark, not text, so nothing needs translation) overlaid on the map, plus a live, editable height input for the active vertex/midpoint - never both at once for Finish/Cancel, whichever is active is the only one drawn and hit-testable. Every element gets a stable CSS class name (.ria-3d-shape-editor-toolbar, .ria-3d-shape-editor-btn-finish, .ria-3d-shape-editor-btn-cancel, .ria-3d-shape-editor-height-input, .ria-3d-shape-editor-height-unit) for restyling, and an optional labels object supplies aria-label/title text for screen readers without touching the icon-only visible UI:

new Shape3DEditController(ShapeType.POLYGON, layer, {
  htmlToolbar: {labels: {cancel: "Annuler", finish: "Terminer", height: "Hauteur"}},
});

labels can be updated live via updateController() (e.g. a language switcher); whether the toolbar exists at all is constructor-only.

Options

All constructor options are optional and can be read back/changed live via controller.updateController({...}), except existingShape (constructor-only, by definition) and htmlToolbar (constructor-only - see above; its labels sub-option is the one part of it that's still live-updatable).

| Option | Type | Default | Effect | | --- | --- | --- | --- | | existingShape | EditableShape | - | Start directly in edit mode on this shape instead of creating a new one | | vertexHitPixelTolerance | number | 12 | Pixel radius for a mouse click/hover to count as targeting a handle (touch always gets a larger effective floor, see above) | | uom | "metric" \| "imperial" | "metric" | Unit family for the live drag label | | showPlane | boolean | true | Draw a translucent ground-reference grid while dragging Move | | showDropLine | boolean | true | Draw a line from the drag anchor straight down to Earth's center while dragging Height/Move/Rotate | | htmlToolbar | boolean \| {labels?} | false | Touch-friendly HTML Finish/Cancel/height-input alternative (see above) |

Public API

  • controller.shape - the shape currently being created/edited, or null before the first vertex.
  • controller.phase - "creating" or "editing".
  • controller.setVertexPosition(index, point) - move a vertex programmatically (e.g. from a numeric input, or as the 2D/non-3D fallback for height editing).
  • controller.finish() - forces creation to finish now, if enough vertices have been placed. No-op while editing.
  • controller.confirm() - the programmatic equivalent of clicking the Finish handle while editing.
  • controller.cancel() - reverts the shape to its value when the controller was constructed (only meaningful when constructed with existingShape).
  • controller.updateController(options) - apply any subset of the options above to an already-active controller, without losing in-progress session state.

Ending a session (Finish, Cancel, or Escape) hands map.controller back to null itself, mirroring BasicCreateController's own self-deactivating behavior - unlike stock EditController, which requires the app to do this itself.

Events

  • ShapeCreated - emitted once, when creation finishes and the controller transitions into edit mode.
  • ShapeChanged - emitted on every vertex move/insert/removal while editing.
  • ShapeEditingFinished - emitted when the controller is deactivated while editing, with confirmed: true only when that happened via the Finish handle/confirm() - persist only in that case, never on ShapeCreated or any other kind of session end.

Notes on drape targets

If the layer that ultimately renders the edited feature styles it with DrapeTarget.MESH/ALL, a height edit made through this controller is real in the underlying data, but may not be visible once control returns to that layer's own painter (the shape gets flattened onto the terrain/mesh surface for rendering). This package's own handle visuals never drape, but it has no opinion on how a consuming app styles the final persisted feature.

Attribution

A handful of small math/interaction utilities in this package (src/math/, parts of src/handle/HandleInteractions.ts) are adapted from Luciad's own @luciad/ria-toolbox-* packages, under the permissive license embedded in those source files (see the header preserved at the top of each adapted file). This package does not depend on any @luciad/ria-toolbox-* package at runtime.