@rapideditor/location-conflation
Version:
Define complex geographic regions by including and excluding country codes and geojson shapes
328 lines • 14.4 kB
TypeScript
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