UNPKG

@rapideditor/location-conflation

Version:

Define complex geographic regions by including and excluding country codes and geojson shapes

328 lines 14.4 kB
import type { HasLocationSet, HasLocationSetID, LocoFeature, Location, LocationSet, LocationSetID, ResolvedLocation, ResolvedLocationSet, StringifyOptions, ValidatedLocation, ValidatedLocationSet, Vec2 } from './types.ts'; export type * from './types.ts'; /** * LocationConflation lets you define complex geographic regions by including and * excluding country codes, points, and custom `.geojson` shapes. * * It resolves these definitions into GeoJSON `Feature`s with polygon geometries, * caching expensive polygon-clipping operations for performance. * * @example * ```ts * import { LocationConflation } from '@rapideditor/location-conflation'; * * const loco = new LocationConflation(myFeatureCollection); * * const result = loco.resolveLocationSet({ include: ['de'], exclude: ['de-berlin.geojson'] }); * console.log(result.feature); // GeoJSON Feature of Germany minus Berlin * ``` */ export declare class LocationConflation { /** * Internal cache of all resolved features, keyed by stable identifiers. * Identifiers look like: * - `'[8.67039,49.41882]'` — point locations * - `'de-hamburg.geojson'` — geojson file locations * - `'Q2'` — country-coder locations (Wikidata QIDs) * - `'+[Q2]-[Q18,Q27611]'` — aggregated location sets */ private _resolved; private _registered; private _setsIncluding; private _setsExcluding; private _spatialIndex; /** * @constructor * Creates a new LocationConflation instance. * @param fc - Optional GeoJSON FeatureCollection of known features with filename-like IDs * (e.g. `"something.geojson"`). Accepts a standard `GeoJSON.FeatureCollection` from * `@types/geojson` — no casts needed. Features without a `.geojson` id are silently skipped. */ constructor(fc?: GeoJSON.FeatureCollection); /** * Setup the world objects. * This ensures that we both have our world location and world locationSet available. * * Calling `registerLocationSets` is important here because allows `locationSetsAt` * to return the world locationSet, even if no locationSets have been otherwise registered. */ private _setupWorld; /** * Returns whether a coordinate pair is within valid WGS84 bounds. */ private _isValidPoint; /** * Adds `.geojson` features to the cache of known locations. * * Each feature must have a filename-like `id` ending in `.geojson` (either on * the feature itself or in its `properties`). Features without a qualifying * `id` are silently skipped. * * The method normalizes each feature into the {@link LocoFeature} shape * (lowercased id, guaranteed `area` property, polygon geometry). If a * feature with the same id already exists in the cache, it is replaced. * * @param fc - A GeoJSON FeatureCollection containing custom locations. * Accepts a standard `GeoJSON.FeatureCollection` — no casts needed. * * @example * ```ts * // Load additional features after construction * const loco = new LocationConflation(); * loco.addFeatures(additionalFeatureCollection); * * // Now these features are available for resolving * const result = loco.resolveLocation('philly_metro.geojson'); * ``` */ addFeatures(fc: GeoJSON.FeatureCollection): void; /** * Removes `.geojson` features from the cache by id. * Each id should be a filename-like string ending in `.geojson`. * Ids are matched case-insensitively (lowercased before lookup). * Non-`.geojson` ids and ids not present in the cache are silently ignored. * * @param ids - One or more feature ids to remove. * * @example * ```ts * loco.removeFeatures('philly_metro.geojson', 'dc_metro.geojson'); * ``` */ removeFeatures(...ids: string[]): void; /** * Removes all cached resolved features. * This clears everything from the resolved cache — `.geojson` features, resolved * country-coder features, point circles, and aggregated location sets. * The world feature ('Q2') is re-added automatically. * * @example * ```ts * loco.clearFeatures(); * // Cache now contains only the world feature (Q2) * ``` */ clearFeatures(): void; /** * Backward-compatibility alias to direct access the internal resolved-feature cache. * If you use this to change the features, make sure to call `rebuildIndex()` after. * @deprecated Use the LocationConflation cache management APIs instead (`addFeatures`, `removeFeatures`, `clearFeatures`). */ get _cache(): Map<string, LocoFeature>; /** * Validates a location and returns its type and stable identifier. * @param location - Location to validate (point, geojson filename, or country code) * @returns Validated location result object * @throws Throws Error if the given location is invalid. * * @example * ```typescript * // Point location with default 25km radius * loco.validateLocation([8.67039, 49.41882]); * // => { type: 'point', location: [8.67039, 49.41882], id: '[8.67039,49.41882]' } * * // Point location with custom radius * loco.validateLocation([-77.0369, 38.9072, 10]); * // => { type: 'point', location: [-77.0369, 38.9072, 10], id: '[-77.0369,38.9072,10]' } * * // Country code * loco.validateLocation('de'); * // => { type: 'countrycoder', location: 'de', id: 'Q183' } * loco.validateLocation('001'); * // => { type: 'countrycoder', location: '001', id: 'Q2' } * * // GeoJSON file * loco.validateLocation('philly_metro.geojson'); * // => { type: 'geojson', location: 'philly_metro.geojson', id: 'philly_metro.geojson' } * ``` */ validateLocation(location: Location): ValidatedLocation; /** * Resolves a location to a GeoJSON Feature. * @param location - Location to resolve * @returns Resolved location result object, including the GeoJSON Feature * @throws Throws Error if the given location is invalid or cannot be resolved. * * @example * ```typescript * // Resolve a point location to a circular polygon * const result = loco.resolveLocation([8.67039, 49.41882]); * // result.feature is a GeoJSON Feature with a circular Polygon geometry * * // Resolve a country code * const germany = loco.resolveLocation('de'); * // germany.feature is a GeoJSON Feature with Germany's boundary * * // Resolve a custom GeoJSON file * const metro = loco.resolveLocation('philly_metro.geojson'); * // metro.feature is the pre-loaded GeoJSON Feature * ``` */ resolveLocation(location: Location): ResolvedLocation; /** * Validates a locationSet and returns its stable identifier. * @param locationSet - LocationSet with include/exclude arrays * @returns Validated locationSet result object * @throws Throws Error if any referenced location is invalid, or locationSet has no include. * * @example * ```typescript * // Include multiple countries * loco.validateLocationSet({ include: ['de', 'fr', 'it'] }); * // => { type: 'locationset', locationSet: {...}, id: '+[Q183,Q142,Q38]' } * * // Include with exclusions * loco.validateLocationSet({ * include: ['de'], * exclude: ['de-berlin.geojson'] * }); * // => { type: 'locationset', locationSet: {...}, id: '+[Q183]-[de-berlin.geojson]' } * * // Mix different location types * loco.validateLocationSet({ * include: ['us', [8.67039, 49.41882], 'philly_metro.geojson'] * }); * ``` */ validateLocationSet(locationSet?: LocationSet): ValidatedLocationSet; /** * Resolves a locationSet to a GeoJSON Feature by combining included/excluded regions. * @param locationSet - LocationSet with include/exclude arrays * @returns Resolved locationSet result object, including the GeoJSON Feature * @throws Throws Error if any referenced location is invalid, or locationSet has no include. * * @example * ```typescript * // Combine multiple countries into one feature * const benelux = loco.resolveLocationSet({ * include: ['be', 'nl', 'lu'] * }); * // benelux.feature is a GeoJSON Feature with combined boundaries * * // Germany excluding Berlin * const germanyNoCapital = loco.resolveLocationSet({ * include: ['de'], * exclude: ['de-berlin.geojson'] * }); * // Result is Germany with Berlin cut out * * // Complex region definition * const customRegion = loco.resolveLocationSet({ * include: ['us-ca', 'us-or', 'us-wa'], * exclude: [[8.67039, 49.41882, 50]] * }); * // West coast states minus a 50km circle * ``` */ resolveLocationSet(locationSet?: LocationSet): ResolvedLocationSet; /** * Registers `locationSet`-bearing objects so that {@link locationSetsAt} can tell you * which of them cover a given point. * * For each object, this method: * 1. Validates the `locationSet` and assigns its stable `locationSetID` (e.g. `'+[Q183]'`). * 2. Walks the `include` / `exclude` components and records them in an inverted index * (component `locationID` → set of `locationSetID`s that reference it). * 3. Sums the approximate areas of the `include` components so results can be ranked * smallest-first. * * Unlike the single-item `validate*` / `resolve*` methods, this method is **tolerant** of * bad input so that a batch of thousands of presets won't be rejected over a single typo: * - Objects with a missing, empty, or invalid `locationSet` fall back to world (`+[Q2]`). * - Individual invalid `include` / `exclude` components are silently ignored. * * This method **accumulates** — calling it multiple times (e.g. for different data sources) * inserts all locationSets into the same index. The spatial index is rebuilt automatically. * To reset the index entirely, create a new `LocationConflation` instance. * * Note: this does **not** resolve each locationSet to a combined polygon — that expensive * clipping step is deferred to {@link resolveLocationSet}, called only when the caller * actually needs the GeoJSON geometry. * * @param objects - Objects with a `locationSet` property to index. * @returns The same array of objects, narrowed to guarantee `locationSetID` is set. * * @example * ```ts * const presets = [ * { id: 'amenity/cafe', locationSet: { include: ['de'] } }, * { id: 'amenity/atm', locationSet: { include: ['001'] } }, * ]; * loco.registerLocationSets(presets); * // presets[0].locationSetID === '+[Q183]' * // presets[1].locationSetID === '+[Q2]' * ``` */ registerLocationSets<T extends HasLocationSet>(objects: T[]): (T & HasLocationSetID)[]; /** * Rebuilds the internal spatial index from the current cache and inverted index. * * Normally you don't need to call this yourself — {@link registerLocationSets}, * {@link addFeatures}, {@link removeFeatures}, and {@link clearFeatures} all rebuild * the index automatically when appropriate. Call it manually only if you've mutated * the cache through the (deprecated) `_cache` getter. */ rebuildIndex(): void; /** * Returns the registered locationSets that cover the given point, * mapped to their approximate area in km². * * Uses the inverted spatial index built by {@link registerLocationSets} — no polygon clipping is * performed. For country-coder regions, CountryCoder's built-in spatial index is used. * For custom `.geojson` and point-radius features, our local which-polygon index is used. * * The returned `Map` gives callers O(1) `has(locationSetID)` membership tests (the common * "is this locationSet valid here?" check). * Results are not sorted — if you need them ordered, sort `[...result.entries()]` by value. * * Call {@link resolveLocationSet} on any returned ID if you need the actual GeoJSON feature. * * @param loc - `[longitude, latitude]` coordinate to query. * @returns Map of locationSetID → approximate area (km²), for all sets valid at the point. * * @example * ```ts * loco.registerLocationSets(presets); * const hits = loco.locationSetsAt([-75.16, 39.95]); * if (hits.has('+[Q30]')) { ... } * // Iterate smallest-first: * const sorted = [...hits.entries()].sort((a, b) => a[1] - b[1]); * ``` */ locationSetsAt(loc: Vec2): Map<LocationSetID, number>; /** * Returns the approximate area (in km²) of a registered locationSet. * The area is the sum of `include` location areas computed during * {@link registerLocationSets} — it does not subtract `exclude` areas. * Returns `undefined` if the locationSet has not been registered. * * @param locationSetID - Stable locationSet identifier, e.g. `'+[Q183]'`. * @returns Approximate area in km², or `undefined` if not registered. * * @example * ```ts * loco.registerLocationSets([{ locationSet: { include: ['de'] } }]); * loco.getLocationSetArea('+[Q183]'); // ~357000 * ``` */ getLocationSetArea(locationSetID: LocationSetID): number | undefined; /** * Convenience method to pretty-stringify an object. * @param obj - Object to stringify * @param options - Stringify options * @returns Pretty-formatted JSON string * * @example * ```typescript * const result = loco.resolveLocation('de'); * console.log(LocationConflation.stringify(result.feature)); * // Outputs a compact, readable JSON representation of the feature * * // Custom formatting options * console.log(LocationConflation.stringify(result.feature, { indent: 2, maxLength: 80 })); * ``` */ static stringify(obj: unknown, options?: StringifyOptions): string; } export default LocationConflation; //# sourceMappingURL=location-conflation.d.ts.map