@magnaboy/maps
v0.0.1
Published
Pure TypeScript coordinate maths, geographic bounds, DMS parsing and GeoJSON utilities.
Readme
@magnaboy/maps
Pure TypeScript coordinate maths, geographic rectangles, DMS parsing, point layout and GeoJSON builders. No runtime dependencies, React, MapLibre, DOM or Node APIs. Runs in browsers and Node.js. ESM with TypeScript declarations; follows this repository's Node 25+ tooling requirement.
Install
pnpm add @magnaboy/mapsUsage
import {
XLocation, XRect, haversineDistance, destinationPoint,
toRadians, fullDmsToDecimal, createPathGeojson, spreadMapItems
} from '@magnaboy/maps';
const start = { lat: 51.5, lng: -0.12 };
const end = destinationPoint(start, 1000, toRadians(90));
const distance = haversineDistance(start, end); // approximately 1000 metres
const tuple = new XLocation(start).toLngLat(); // [longitude, latitude]
const bounds = new XRect({ southWest: start, northEast: { lat: 51.6, lng: 0 } });
const inside = bounds.contains(end);
const coordinate = fullDmsToDecimal(`51°30'0"N 0°7'12"W`);
const path = createPathGeojson(
[{ ...start, time: 1 }, { ...end, time: 2 }],
point => ({ time: point.time })
);
const markers = spreadMapItems([start, start], {
maxDistanceMeters: 10,
radiusMeters: 20
});API and conventions
Location (also exported as ILocation) contains lat and lng in decimal degrees. GeoJSON uses [lng, lat]. Distances are metres. The earth model is a sphere with radius EARTH_RADIUS_M = 6_371_000; it is not an ellipsoidal survey model.
| Export | Behaviour |
| --- | --- |
| distanceMeters(aLat, aLng, bLat, bLng) / haversineDistance(a, b) | Great-circle distance |
| toRadians, toDegrees, normalizeHeading | Angle conversion; normalized heading in [0, 360) degrees |
| offsetMeters(lat, lng, meters, bearingRad) | Local flat-earth offset |
| bearingTo(fromLat, fromLng, toLat, toLng) | Local flat-earth bearing in radians, clockwise from north |
| destinationPoint(from, meters, bearingRad) | Great-circle destination; longitude normalized to [-180, 180) |
| XLocation | Clone, distance, cardinal movement, random bearing displacement, JSON/string/tuple conversion |
| XRect | Width/height, approximate area, centre, containment, expansion and random sampling |
| dmsToDecimal / fullDmsToDecimal | Parse one DMS coordinate / latitude-longitude pair |
| spreadMapItems(items, options) | Spread nearby groups on circles, preserving extra fields and input order |
| roundLatLngs(location, gridMeters) | Approximate grid rounding with explicit metre spacing |
| pointsToGeoJson(points, properties?) | Point feature collection |
| createPathGeojson(points, properties?) | Line followed by point features; omit the line for fewer than two points |
| isValidLocation / assertLocation | Check finite coordinates and latitude/longitude ranges |
The root exports all APIs. @magnaboy/maps/geo and @magnaboy/maps/geojson offer focused imports. GeoJSON structural types are exported without an ambient GeoJSON namespace or external type dependency.
Limits and input behaviour
- Existing low-level maths and class constructors expect valid numeric coordinates; they do not validate input. Use
assertLocationat input boundaries. Local offsets, cardinal movement andbearingToare for short distances away from poles and the antimeridian. They do not wrap longitude or constrain latitude. UsedestinationPointfor spherical movement across those boundaries. destinationPointvalidates coordinates and finite distance/bearing. Negative distance moves in the opposite direction.moveRandomchooses a bearing at exactly the requested distance; it does not sample within a disk.- Rectangles use numeric latitude/longitude intervals, not wrapped intervals. They do not model rectangles across the antimeridian.
containsand random sampling accept inverted corners; signedwidth/heightpreserve corner order.expandexpects ordered corners. Metric area is width times height, an approximation for small regions. Sampling is uniform in degrees, not surface area.randomPointsEvenuses jittered grid cells and accepts nonnegative integer counts, including zero. - DMS accepts ASCII or Unicode degree/minute/second marks and a required hemisphere. Minutes/seconds must be below 60; latitude/longitude limits apply. Pairs require latitude first, separated by whitespace or a comma. Malformed input throws
RangeError. - Point layout and GeoJSON builders reject invalid coordinates with
RangeError. They do not mutate the input. Spreading is deterministic, order-dependent and O(n²): each group uses its first point as the anchor, not transitive clustering. All output items are shallow copies; nested metadata retains its references. - Rounding uses longitude scale at the input latitude, so it is an approximation, not a global projected grid or privacy guarantee. Spacing must be finite and positive. Pole longitude is set to zero.
- GeoJSON builders include only properties returned by the callback; default properties are empty. They preserve path order and do not split lines crossing the antimeridian. Callers must supply JSON-serializable properties.
Development
From this repository root:
pnpm --filter @magnaboy/maps build
pnpm --filter @magnaboy/maps test:types
pnpm --filter @magnaboy/maps test:unit
pnpm --filter @magnaboy/maps check:publishTests retain the extracted maths/class regression cases and add generic utility and boundary cases. Coverage uses the shared repository thresholds. prepack builds and checks dist/index.js; README packing follows the shared hide/restore convention.
Extraction from CX
This package extracts the pure utilities from cx/packages/maps. CX remains operational with its existing code until this package is published and installed there.
- Replace CX's core imports with
@magnaboy/mapsor@magnaboy/maps/geoafter publication. - Location types are now local; no
@cx/schemasdependency exists. XLocation.distanceToandXRect.containsalso accept plain coordinate objects.spreadMapItemsnow requires grouping distance and radius, returns copies, and rejects invalid coordinates instead of dropping them.roundLatLngsnow requires grid spacing and does not apply the old fixed decimal truncation.- Replace duplicate
pathPointsToGeoJson/createPathGeojsoncalls withcreatePathGeojson; pass date/thumbnail properties explicitly when the app needs them. - Replace
createClipFeatureswithpointsToGeoJson(items, item => item).features; normalize string coordinates in the app first. - Keep React hooks/components, clip/entity types, Austin constants, map styles, controls and rendering in CX.
