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

@darioscianna/zonekit

v0.1.0

Published

Zero-dependency TypeScript geometry for polygonal zones: validation, overlap detection and simplification.

Readme

zonekit

Small TypeScript library for checking polygons ("zones"): validation, overlap and containment, simplification. No dependencies.

I needed to answer a few questions about hand-drawn polygons: does it cross itself, is it inside a given boundary, does it overlap the one next to it, can I drop some points without breaking any of that. This is the code that does it, pulled out into its own package.

There's a playground where you can draw two zones and see what the functions return.

npm install @darioscianna/zonekit
import { relateRings, ringContainsRing, simplifyRing, validateRing } from "@darioscianna/zonekit";

const bounds = [
  { x: 0, y: 0 },
  { x: 100, y: 0 },
  { x: 100, y: 80 },
  { x: 0, y: 80 },
];

const zone = [
  { x: 10, y: 10 },
  { x: 60, y: 12 },
  { x: 62, y: 45 },
  { x: 12, y: 44 },
];

validateRing(zone, { minArea: 25 });   // { valid: true, issues: [] }
ringContainsRing(bounds, zone);          // true
relateRings(zone, otherZone);          // "touching"
simplifyRing(zone, { maxVertices: 3 }); // drops the cheapest vertex

Why not turf.js?

If you need a full GIS toolbox, use turf, polygon-clipping or simplify-js. This does less, but a few things work differently:

  • relateRings tells apart polygons that only share a border (touching) from ones that actually overlap. For my use case the first is fine and the second is an error, and a boolean "intersects" doesn't say which.
  • simplifyRing won't fold the outline through itself (plain Visvalingam-Whyatt can, on concave shapes). You can turn the check off.
  • Validation returns a list of issues with vertex/edge indices instead of throwing, so a UI can highlight what's wrong.
  • epsilon is always a distance in your coordinate units, not a raw cross-product threshold.
  • ~700 lines, ESM, strict TS, no deps.

Concepts

Rings are closed implicitly. A ring is a list of vertices where the edge from the last back to the first is implied. Do not repeat the first vertex at the end; validateRing will tell you if you did. This differs from GeoJSON, which requires the repeat.

The plane is cartesian and y-up. signedArea is positive counterclockwise. Coordinates carry whatever unit you give them, as long as x and y share it.

Geography is projected, not assumed. A degree of longitude is not a degree of latitude anywhere but the equator, so comparing them directly distorts every area and angle. Project first:

import { projectionFor, toPlaneRing, validateRing } from "@darioscianna/zonekit";

const projection = projectionFor(positions);      // local plane in metres
const ring = toPlaneRing(positions, projection);
validateRing(ring, { epsilon: 0.05, minArea: 10 }); // 5 cm, 10 m²

The projection is equirectangular around a local origin: accurate to well under a metre over a few kilometres, not meant for country-scale data, and it does not handle the antimeridian.

API

Predicates

| Function | Purpose | | --- | --- | | signedArea, area, perimeter | Ring measurements | | orientation, withOrientation | Winding direction, and normalising it | | sideOfLine, cross | Which side of a line a point falls on | | distance, distanceToSegment, isPointOnSegment | Distances | | locatePoint, containsPoint | interior, boundary or exterior | | intersectSegments, segmentsIntersect | none, point (with proper) or overlap |

Validation

| Function | Purpose | | --- | --- | | validateRing(ring, options) | Full check, returns { valid, issues } | | isValidRing, isSelfIntersecting | Boolean shortcuts | | describeIssue(issue) | One-line message for an issue |

Options: epsilon, minArea, maxVertices. Issues cover too few or too many vertices, a repeated closing vertex, coincident vertices, self-intersections (with the crossing point), overlapping edges, a degenerate outline and an area below the minimum.

Relations

| Function | Purpose | | --- | --- | | relateRings(a, b) | disjoint, touching, overlapping, a-contains-b, b-contains-a, identical | | ringContainsRing(outer, inner) | Inside an outer boundary, shared border allowed | | ringsOverlap(a, b) | Interiors share area | | conflictingRings(candidate, existing, options) | Indices of the zones a new one collides with |

Both rings are assumed to be simple. Validate first if that is not guaranteed.

Simplification

| Function | Purpose | | --- | --- | | simplifyRing(ring, options) | Visvalingam-Whyatt on a closed ring | | simplifyRingTo(ring, maxVertices) | The common case | | ringEffectiveAreas(ring) | What each vertex costs, useful for visualising |

Options: minArea, maxVertices, minVertices (floor, default 3), epsilon and preventSelfIntersection (default true). With the guard on, a removal that would fold the outline is skipped and the next cheapest vertex is tried; if every candidate would fold, simplification stops early rather than returning a broken ring.

Geography

localProjection, projectionFor, meanPosition, toPlaneRing, toLatLonRing, haversineDistance.

Development

npm install
npm test          # vitest
npm run typecheck
npm run build     # dist/, ESM + .d.ts
npm run serve     # builds the playground and serves docs/ on localhost:3000

The demo in docs/ imports the compiled library from docs/lib, which is built by the Pages workflow and ignored in git.

Notes on complexity

Validation and relations are O(n·m) over edges, and simplification is O(n²), or O(n³) with the fold guard. Zones are drawn by hand and rarely pass a few dozen vertices, so the cost buys correctness where it matters. If you need this on geometry with thousands of vertices, an index over the edges is the first thing to add.

License

MIT