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

@node-in-layers/geo

v1.0.1

Published

A Geographical data library for node in layers. Features geo models, libs and services for querying.

Readme

Geography - Node In Layers

Unit Tests Coverage Status

How To Use

1. Add Geo domains to your config

Load @node-in-layers/data first, then Geo core (required), then measurements if needed. Core must come before any other Geo domain.

// ./config.base.mts
import { CoreNamespace } from '@node-in-layers/core'
import { GeoNamespace } from '@node-in-layers/geo'

export default async (): Promise<YourConfig> => {
  return {
    environment: 'base',
    systemName: 'your-system-name',
    [CoreNamespace.root]: {
      apps: await Promise.all([
        import(`@node-in-layers/data/index.js`),
        import(`@node-in-layers/geo/core/index.js`),
        import(`@node-in-layers/geo/measurements/index.js`), // optional
        import(`./src/myDomain/index.js`),
      ]),
      [GeoNamespace.Core]: {
        // Optional: import { PropertyType } from 'functional-models'. Use PropertyType.Integer | PropertyType.Text | PropertyType.UniqueId. SQL DB → Integer; Mongo/others → UniqueId (default).
        // modelIdPropertyType: PropertyType.UniqueId
      },
    },
  }
}

2. Use Geo models in your services or features

Type your context with GeoCoreServicesLayer so you get access to Geo CRUDs. Use context.services[GeoNamespace.Core].cruds (keys: Countries, StateProvinces, Cities, PostalCodes, Locations). For spatial queries use context.services[GeoNamespace.Core].queryBoundingBoxOfGeoModels({ model, boundingBox, options }).

In a service:

// src/yourDomain/services.ts
import { queryBuilder } from 'functional-models'
import { ServicesContext } from '@node-in-layers/core'
import { GeoCoreServicesLayer, GeoNamespace } from '@node-in-layers/geo'
import { YourConfig } from '../types.js'
import { YourDomainServicesLayer } from './types.js'

export const create = (
  context: ServicesContext<
    YourConfig,
    YourDomainServicesLayer & GeoCoreServicesLayer
  >
) => {
  const findLocationsByName = async (name: string) => {
    const query = queryBuilder().property('name', name).compile()
    const result =
      await context.services[GeoNamespace.Core].cruds.Locations.search(query)
    return result.instances
  }
  return { findLocationsByName }
}

In a feature: same idea—type the context so the services layer includes GeoCoreServicesLayer, then use context.services[GeoNamespace.Core].cruds the same way:

// src/yourDomain/features.ts
import { queryBuilder } from 'functional-models'
import { FeaturesContext } from '@node-in-layers/core'
import { GeoCoreServicesLayer, GeoNamespace } from '@node-in-layers/geo'
import { YourConfig } from '../types.js'
import { YourServicesLayer, YourFeaturesLayer } from './types.js'

export const create = (
  context: FeaturesContext<
    YourConfig,
    YourServicesLayer & GeoCoreServicesLayer,
    YourFeaturesLayer
  >
) => {
  const findLocationsByName = async (name: string) => {
    const query = queryBuilder().property('name', name).compile()
    const result =
      await context.services[GeoNamespace.Core].cruds.Locations.search(query)
    return result.instances
  }
  return { findLocationsByName }
}

3. Coordinates (Lat/Lon) and formats

All geo models and spatial APIs use typed coordinates, not raw numbers. The library exposes three representations for a single lat or lon value:

| Format | Type | Description | | ------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | String | CoordinateString | Normalized string, 6 decimal places (e.g. "35.227086"). | | Protocol | CoordinateProtocol | Sentinel-prefixed fixed-width int (lat 1_000_000_000..1_180_000_000, lon 1_000_000_000..1_360_000_000). See Protocol integers below. | | Float | CoordinateFloat | Degrees as a number (e.g. 35.227086). |

Creating Lat/Lon: Use parseCoordComponent(value, kind) from @node-in-layers/geo (core libs). It accepts a string, a number (degrees), or a protocol integer; validates range (lat -90..90, lon -180..180); and returns a typed Lat or Lon.

import {
  parseCoordComponent,
  createBoundingBox,
  CoordKind,
} from '@node-in-layers/geo'

const lat = parseCoordComponent(35.227086, CoordKind.Lat)
const lon = parseCoordComponent(-80.843127, CoordKind.Lon)

// Bounding box from four corners (min/max lat/lon in degrees)
const box = createBoundingBox({
  topLeft: {
    lat: parseCoordComponent(36, CoordKind.Lat),
    lon: parseCoordComponent(-81, CoordKind.Lon),
  },
  topRight: {
    lat: parseCoordComponent(36, CoordKind.Lat),
    lon: parseCoordComponent(-80, CoordKind.Lon),
  },
  bottomLeft: {
    lat: parseCoordComponent(35, CoordKind.Lat),
    lon: parseCoordComponent(-81, CoordKind.Lon),
  },
  bottomRight: {
    lat: parseCoordComponent(35, CoordKind.Lat),
    lon: parseCoordComponent(-80, CoordKind.Lon),
  },
})

Converting between formats: Use convertCoordComponent(component, kind, toFormat) and convertCoordinate(coord, toFormat) with CoordFormat.String, CoordFormat.Protocol, or CoordFormat.Float. Geo models store lat/lon as strings and latInt/lonInt as protocol; use these libs so types stay consistent.

Flow: (A) Parse user or API input with parseCoordComponent(value, kind)value can be a string, a float (degrees), or a protocol integer. (B) Convert when you need another representation: convertCoordComponent(component, kind, toFormat) or convertCoordinate(coord, toFormat).

Protocol integers

Protocol values are for efficient database range queries: the datastore can use integer comparison (<, >, BETWEEN) on latInt/lonInt. Each value is a sentinel-prefixed fixed-width integer: a leading 1 plus 9 digits (e.g. lat 1_000_000_000 .. 1_180_000_000, lon 1_000_000_000 .. 1_360_000_000). The sentinel ensures a fixed number of digits and consistent lexicographic ordering so range queries behave correctly.


Domain detail

Geo core (@node-in-layers/geo)

  • Models: Countries, StateProvinces, Cities, PostalCodes, Locations.
  • Libs: parseCoordComponent(value, kind), convertCoordComponent(component, kind, toFormat), convertCoordinate(coord, toFormat), createBoundingBox({ topLeft, topRight, bottomLeft, bottomRight }), isBoundingBoxCrossingAntiMeridian(box).
  • Types: Lat, Lon, CoordComponent, CoordinateString, CoordinateProtocol, CoordinateFloat, CoordKind, CoordFormat, BoundingBox, Coordinate, GeoPolygon, GeoModel, Country, StateProvince, City, PostalCode, Location.
  • Services: queryBoundingBoxOfGeoModels({ model, boundingBox, options }).

Measurements (@node-in-layers/geo/measurements)

  • Libs: distance(value, unit), convertDistance(d, toUnit), createBoundingBoxSquare(center, d), createCirclePolygon(center, distance), getDistanceBetweenHaversine(p1, p2, unit), isPointInBoundingBox(point, box), isPointWithinDistance(center, point, d), boundingBoxFromPolygon(polygon), isPointInPolygon(point, polygon), filterModelsWithinPolygon(instances, polygon).
  • Types: Distance, DistanceUnit, DistanceInUnit<U>, MeasurementsServices, MeasurementsServicesLayer.
  • Services: queryModelsWithinPolygon({ model, polygon, options }), queryModelsFromDistance({ model, center, distance, options }).

MCP Development Server

Node In Layers Geo ships with an MCP server for AI based development. It provides knowledge entries for the AI to have specific examples of how to work with and manipulate the data/features in this system. Not only does this enable your AI to accurately deal with this library, but you can ask questions, and it'll be extremely knowledable about the entire library.

Quick Start

You can setup the server by setting up Cursor (or similar) with this configuration.

{
  "mcpServers": {
    "node-in-layers-geo": {
      "command": "npx",
      "args": ["-y", "@node-in-layers/geo-knowledge-mcp"]
    }
  }
}

You can learn more about this here Knowledge MCP