@node-in-layers/geo
v1.0.1
Published
A Geographical data library for node in layers. Features geo models, libs and services for querying.
Maintainers
Readme
Geography - Node In Layers
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
