var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var location_conflation_exports = {}; __export(location_conflation_exports, { LocationConflation: () => LocationConflation, default: () => location_conflation_default }); module.exports = __toCommonJS(location_conflation_exports); var CountryCoder = __toESM(require("@rapideditor/country-coder"), 1); var Polyclip = __toESM(require("polyclip-ts"), 1); var import_geojson_area = __toESM(require("@mapbox/geojson-area"), 1); var import_circle_to_polygon = __toESM(require("circle-to-polygon"), 1); var import_geojson_precision = __toESM(require("geojson-precision"), 1); var import_json_stringify_pretty_compact = __toESM(require("@aitodotai/json-stringify-pretty-compact"), 1); class LocationConflation { // constructor // // `fc` Optional FeatureCollection of known features // // Optionally pass a GeoJSON FeatureCollection of known features which we can refer to later. // Each feature must have a filename-like `id`, for example: `something.geojson` // // { // "type": "FeatureCollection" // "features": [ // { // "type": "Feature", // "id": "philly_metro.geojson", // "properties": { … }, // "geometry": { … } // } // ] // } constructor(fc) { this._cache = {}; this.strict = true; if (fc && fc.type === "FeatureCollection" && Array.isArray(fc.features)) { fc.features.forEach((feature) => { feature.properties = feature.properties || {}; let props = feature.properties; let id = feature.id || props.id; if (!id || !/^\S+\.geojson$/i.test(id)) return; id = id.toLowerCase(); feature.id = id; props.id = id; if (!props.area) { const area = import_geojson_area.default.geometry(feature.geometry) / 1e6; props.area = Number(area.toFixed(2)); } this._cache[id] = feature; }); } let world = _cloneDeep(CountryCoder.feature("Q2")); world.geometry = { type: "Polygon", coordinates: [[[-180, -90], [180, -90], [180, 90], [-180, 90], [-180, -90]]] }; world.id = "Q2"; world.properties.id = "Q2"; world.properties.area = import_geojson_area.default.geometry(world.geometry) / 1e6; this._cache.Q2 = world; } // validateLocation // `location` The location to validate // // Pass a `location` value to validate // // Returns a result like: // { // type: 'point', 'geojson', or 'countrycoder' // location: the queried location // id: the stable identifier for the feature // } // or `null` if the location is invalid // validateLocation(location) { if (Array.isArray(location) && (location.length === 2 || location.length === 3)) { const lon = location[0]; const lat = location[1]; const radius = location[2]; if (Number.isFinite(lon) && lon >= -180 && lon <= 180 && Number.isFinite(lat) && lat >= -90 && lat <= 90 && (location.length === 2 || Number.isFinite(radius) && radius > 0)) { const id = "[" + location.toString() + "]"; return { type: "point", location, id }; } } else if (typeof location === "string" && /^\S+\.geojson$/i.test(location)) { const id = location.toLowerCase(); if (this._cache[id]) { return { type: "geojson", location, id }; } } else if (typeof location === "string" || typeof location === "number") { const feature = CountryCoder.feature(location); if (feature) { const id = feature.properties.wikidata; return { type: "countrycoder", location, id }; } } if (this.strict) { throw new Error(`validateLocation: Invalid location: "${location}".`); } else { return null; } } // resolveLocation // `location` The location to resolve // // Pass a `location` value to resolve // // Returns a result like: // { // type: 'point', 'geojson', or 'countrycoder' // location: the queried location // id: a stable identifier for the feature // feature: the resolved GeoJSON feature // } // or `null` if the location is invalid // resolveLocation(location) { const valid = this.validateLocation(location); if (!valid) return null; const id = valid.id; if (this._cache[id]) { return Object.assign(valid, { feature: this._cache[id] }); } if (valid.type === "point") { const lon = location[0]; const lat = location[1]; const radius = location[2] || 25; const EDGES = 10; const PRECISION = 3; const area = Math.PI * radius * radius; const feature = this._cache[id] = (0, import_geojson_precision.default)({ type: "Feature", id, properties: { id, area: Number(area.toFixed(2)) }, geometry: (0, import_circle_to_polygon.default)([lon, lat], radius * 1e3, EDGES) // km to m }, PRECISION); return Object.assign(valid, { feature }); } else if (valid.type === "geojson") { } else if (valid.type === "countrycoder") { let feature = _cloneDeep(CountryCoder.feature(id)); let props = feature.properties; if (Array.isArray(props.members)) { let aggregate = CountryCoder.aggregateFeature(id); aggregate.geometry.coordinates = _clip([aggregate], "UNION").geometry.coordinates; feature.geometry = aggregate.geometry; } if (!props.area) { const area = import_geojson_area.default.geometry(feature.geometry) / 1e6; props.area = Number(area.toFixed(2)); } feature.id = id; props.id = id; this._cache[id] = feature; return Object.assign(valid, { feature }); } if (this.strict) { throw new Error(`resolveLocation: Couldn't resolve location "${location}".`); } else { return null; } } // validateLocationSet // `locationSet` the locationSet to validate // // Pass a locationSet Object to validate like: // { // include: [ Array of locations ], // exclude: [ Array of locations ] // } // // Returns a result like: // { // type: 'locationset' // locationSet: the queried locationSet // id: the stable identifier for the feature // } // or `null` if the locationSet is invalid // validateLocationSet(locationSet) { locationSet = locationSet || {}; const validator = this.validateLocation.bind(this); let include = (locationSet.include || []).map(validator).filter(Boolean); let exclude = (locationSet.exclude || []).map(validator).filter(Boolean); if (!include.length) { if (this.strict) { throw new Error(`validateLocationSet: LocationSet includes nothing.`); } else { locationSet.include = ["Q2"]; include = [{ type: "countrycoder", location: "Q2", id: "Q2" }]; } } include.sort(_sortLocations); let id = "+[" + include.map((d) => d.id).join(",") + "]"; if (exclude.length) { exclude.sort(_sortLocations); id += "-[" + exclude.map((d) => d.id).join(",") + "]"; } return { type: "locationset", locationSet, id }; } // resolveLocationSet // `locationSet` the locationSet to resolve // // Pass a locationSet Object to validate like: // { // include: [ Array of locations ], // exclude: [ Array of locations ] // } // // Returns a result like: // { // type: 'locationset' // locationSet: the queried locationSet // id: the stable identifier for the feature // feature: the resolved GeoJSON feature // } // or `null` if the locationSet is invalid // resolveLocationSet(locationSet) { locationSet = locationSet || {}; const valid = this.validateLocationSet(locationSet); if (!valid) return null; const id = valid.id; if (this._cache[id]) { return Object.assign(valid, { feature: this._cache[id] }); } const resolver = this.resolveLocation.bind(this); const includes = (locationSet.include || []).map(resolver).filter(Boolean); const excludes = (locationSet.exclude || []).map(resolver).filter(Boolean); if (includes.length === 1 && excludes.length === 0) { return Object.assign(valid, { feature: includes[0].feature }); } const includeGeoJSON = _clip(includes.map((d) => d.feature), "UNION"); const excludeGeoJSON = _clip(excludes.map((d) => d.feature), "UNION"); let resultGeoJSON = excludeGeoJSON ? _clip([includeGeoJSON, excludeGeoJSON], "DIFFERENCE") : includeGeoJSON; const area = import_geojson_area.default.geometry(resultGeoJSON.geometry) / 1e6; resultGeoJSON.id = id; resultGeoJSON.properties = { id, area: Number(area.toFixed(2)) }; this._cache[id] = resultGeoJSON; return Object.assign(valid, { feature: resultGeoJSON }); } // stringify // convenience method to prettyStringify the given object stringify(obj, options) { return (0, import_json_stringify_pretty_compact.default)(obj, options); } } function _clip(features, which) { if (!Array.isArray(features) || !features.length) return null; const fn = { UNION: Polyclip.union, DIFFERENCE: Polyclip.difference }[which]; const args = features.map((feature) => feature.geometry.coordinates); const coords = fn.apply(null, args); return { type: "Feature", properties: {}, geometry: { type: whichType(coords), coordinates: coords } }; function whichType(coords2) { const a = Array.isArray(coords2); const b = a && Array.isArray(coords2[0]); const c = b && Array.isArray(coords2[0][0]); const d = c && Array.isArray(coords2[0][0][0]); return d ? "MultiPolygon" : "Polygon"; } } function _cloneDeep(obj) { return JSON.parse(JSON.stringify(obj)); } function _sortLocations(a, b) { const rank = { countrycoder: 1, geojson: 2, point: 3 }; const aRank = rank[a.type]; const bRank = rank[b.type]; return aRank > bRank ? 1 : aRank < bRank ? -1 : a.id.localeCompare(b.id); } var location_conflation_default = LocationConflation; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { LocationConflation }); //# sourceMappingURL=location-conflation.cjs.map