@proximap/core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +11 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/geo.ts","../src/categories.ts","../src/taxonomy.ts","../src/quality.ts","../src/hours.ts","../src/http.ts","../src/providers/nominatim.ts","../src/providers/overpass.ts","../src/providers/valhalla.ts","../src/providers/osrm.ts","../src/ranking.ts","../src/routing.ts","../src/filters.ts","../src/origin.ts","../src/disambiguate.ts","../src/proximity.ts","../src/nearby.ts","../src/reachable.ts","../src/gaps.ts","../src/walkability.ts","../src/compare.ts","../src/errands.ts","../src/export.ts","../src/snapshot.ts","../src/index.ts"],"sourcesContent":["/**\n * Domain model for proximap. These types are provider-agnostic: OpenStreetMap\n * is the default backend, but anything implementing {@link GeocodingProvider}\n * and {@link PlacesProvider} can drive the same pipeline.\n */\n\n/** A WGS84 latitude/longitude coordinate, in decimal degrees. */\nexport interface LatLng {\n lat: number;\n lng: number;\n}\n\n/**\n * Normalized, top-level categories every POI is bucketed into. This is the\n * single source of truth — {@link Category} is derived from it.\n */\nexport const CATEGORIES = [\n 'food',\n 'grocery',\n 'shopping',\n 'healthcare',\n 'education',\n 'finance',\n 'transport',\n 'fuel',\n 'parking',\n 'accommodation',\n 'leisure',\n 'tourism',\n 'worship',\n 'public_service',\n 'utility',\n 'other',\n] as const;\n\n/** A normalized amenity/utility category. */\nexport type Category = (typeof CATEGORIES)[number];\n\n/**\n * A single OSM tag selector, e.g. `{ key: 'amenity', value: 'cafe' }` or\n * `{ key: 'cuisine', value: 'coffee_shop', regex: true }`. An omitted `value`\n * matches the mere presence of the key.\n */\nexport interface CategorySelector {\n key: string;\n value?: string;\n /** Treat `value` as an Overpass regular expression (the `~` operator). */\n regex?: boolean;\n}\n\n/** A geocoded location — the resolved origin of a search. */\nexport interface Place {\n /** Best-effort short name, e.g. \"Eiffel Tower\". */\n name: string;\n /** Full human-readable label from the geocoder. */\n displayName: string;\n location: LatLng;\n /** Geocoder-provided class/type, e.g. \"tourism\" or \"city\". */\n kind?: string;\n /** Bounding box as [south, north, west, east], if provided. */\n boundingBox?: [number, number, number, number];\n /** Identifier of the provider that produced this result, e.g. \"nominatim\". */\n source: string;\n /** Raw provider payload, for advanced consumers. */\n raw?: unknown;\n}\n\n/** A point of interest: an amenity, utility, shop, or similar feature. */\nexport interface Poi {\n /** Stable identifier, e.g. \"node/123\" or \"way/456\". */\n id: string;\n name?: string;\n category: Category;\n /** The specific source value behind the category, e.g. \"restaurant\". */\n kind?: string;\n location: LatLng;\n /** Original source tags/attributes (e.g. OSM tags). */\n tags: Record<string, string>;\n source: string;\n /** Share of expected tags present for this category, in [0, 1]. */\n completeness?: number;\n /** Best-effort last-verified date (YYYY-MM-DD) from check_date/survey/meta. */\n lastVerified?: string;\n}\n\n/** Whether a place is open at a given time: known states or \"not enough data\". */\nexport type OpenState = 'open' | 'closed' | 'unknown';\n\n/** A {@link Poi} enriched with distance from the search origin and a rank. */\nexport interface RankedPoi extends Poi {\n /** Great-circle distance from the origin, in metres. */\n distanceMeters: number;\n /** Composite score in [0, 1]; higher is better. */\n score: number;\n /** 1-based position after ranking. */\n rank: number;\n /** Open/closed/unknown at the queried time — set only when `open` was requested. */\n openState?: OpenState;\n /** ISO 8601 timestamp of the next open/closed transition, when computable. */\n nextChange?: string;\n /** Travel duration from the origin in seconds — set only when ranking by travel time. */\n travelSeconds?: number;\n /** Travel distance from the origin in metres — set only when ranking by travel time. */\n travelMeters?: number;\n /** A short human-readable reason for this rank — set only when `explain` is on. */\n rankingReason?: string;\n}\n\n/** Options for a geocoding lookup. */\nexport interface GeocodeOptions {\n /** Maximum number of candidates to return. */\n limit?: number;\n /** Preferred result language, e.g. \"en\". */\n language?: string;\n signal?: AbortSignal;\n}\n\n/** Resolves place names/addresses to coordinates (and optionally back). */\nexport interface GeocodingProvider {\n readonly name: string;\n geocode(query: string, options?: GeocodeOptions): Promise<Place[]>;\n reverse?(location: LatLng, options?: GeocodeOptions): Promise<Place | null>;\n}\n\n/** Options for a nearby-places search. */\nexport interface NearbyOptions {\n /** Search radius from the center, in metres. */\n radiusMeters: number;\n /** Restrict results to these normalized categories (post-classification). */\n categories?: Category[];\n /**\n * Tag selectors that drive a targeted query (and define what to keep). When\n * set, the provider should fetch only matching features instead of the broad\n * default set. Takes precedence over `categories` for query construction.\n */\n selectors?: CategorySelector[];\n /** Upper bound on POIs fetched from the provider. */\n limit?: number;\n signal?: AbortSignal;\n}\n\n/** Finds points of interest around a coordinate. */\nexport interface PlacesProvider {\n readonly name: string;\n findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]>;\n}\n","import type { LatLng } from './types';\n\n/** Mean Earth radius (IUGG), in metres. */\nconst EARTH_RADIUS_M = 6_371_008.8;\n\nconst toRadians = (degrees: number): number => (degrees * Math.PI) / 180;\n\n/**\n * Great-circle distance between two coordinates, in metres, via the haversine\n * formula. Accurate to within ~0.5% — more than enough to rank nearby places.\n */\nexport function haversineMeters(a: LatLng, b: LatLng): number {\n const dLat = toRadians(b.lat - a.lat);\n const dLng = toRadians(b.lng - a.lng);\n const lat1 = toRadians(a.lat);\n const lat2 = toRadians(b.lat);\n\n const sinLat = Math.sin(dLat / 2);\n const sinLng = Math.sin(dLng / 2);\n const h = sinLat * sinLat + Math.cos(lat1) * Math.cos(lat2) * sinLng * sinLng;\n return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));\n}\n\n/** Format a metre distance as a compact human string (\"125 m\", \"1.4 km\"). */\nexport function formatDistance(meters: number): string {\n if (!Number.isFinite(meters) || meters < 0) return '—';\n if (meters < 1000) return `${Math.round(meters)} m`;\n const km = meters / 1000;\n return `${km < 10 ? km.toFixed(1) : Math.round(km)} km`;\n}\n\n/** Format a second duration as a compact human string (\"8 min\", \"1 h 5 min\"). */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '—';\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes} min`;\n const hours = Math.floor(minutes / 60);\n const remainder = minutes % 60;\n return remainder === 0 ? `${hours} h` : `${hours} h ${remainder} min`;\n}\n\n/**\n * Parse a \"lat,lng\" string (decimal degrees) into a {@link LatLng}, or return\n * null when the input is not a valid, in-range coordinate pair.\n */\nexport function parseCoordinates(input: string): LatLng | null {\n const match = input.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)$/);\n if (!match) return null;\n const lat = Number(match[1]);\n const lng = Number(match[2]);\n if (Number.isNaN(lat) || Number.isNaN(lng)) return null;\n if (lat < -90 || lat > 90 || lng < -180 || lng > 180) return null;\n return { lat, lng };\n}\n","import { CATEGORIES, type Category } from './types';\n\n/**\n * OSM `amenity=*` values grouped by normalized category. Kept as the readable\n * source for the flattened lookup built below.\n */\nconst AMENITY_GROUPS = {\n food: ['restaurant', 'cafe', 'fast_food', 'bar', 'pub', 'food_court', 'biergarten', 'ice_cream'],\n healthcare: [\n 'hospital',\n 'clinic',\n 'doctors',\n 'dentist',\n 'pharmacy',\n 'veterinary',\n 'nursing_home',\n ],\n education: ['school', 'college', 'university', 'kindergarten', 'library', 'language_school'],\n finance: ['bank', 'atm', 'bureau_de_change'],\n fuel: ['fuel', 'charging_station'],\n parking: ['parking', 'bicycle_parking', 'motorcycle_parking', 'parking_entrance'],\n transport: [\n 'bus_station',\n 'taxi',\n 'ferry_terminal',\n 'car_rental',\n 'bicycle_rental',\n 'car_sharing',\n ],\n worship: ['place_of_worship'],\n public_service: [\n 'police',\n 'fire_station',\n 'post_office',\n 'townhall',\n 'courthouse',\n 'community_centre',\n ],\n leisure: ['cinema', 'theatre', 'nightclub', 'arts_centre'],\n shopping: ['marketplace'],\n utility: [\n 'toilets',\n 'drinking_water',\n 'shower',\n 'recycling',\n 'waste_disposal',\n 'post_box',\n 'telephone',\n 'fountain',\n 'shelter',\n ],\n} satisfies Partial<Record<Category, string[]>>;\n\nconst AMENITY_TO_CATEGORY = new Map<string, Category>();\nfor (const [category, values] of Object.entries(AMENITY_GROUPS)) {\n for (const value of values) AMENITY_TO_CATEGORY.set(value, category as Category);\n}\n\n/** `shop=*` values that are really about groceries rather than retail. */\nconst GROCERY_SHOPS = new Set([\n 'supermarket',\n 'convenience',\n 'greengrocer',\n 'bakery',\n 'butcher',\n 'general',\n 'deli',\n 'farm',\n 'dairy',\n 'health_food',\n]);\n\n/** `tourism=*` values that represent places to stay. */\nconst LODGING_TOURISM = new Set([\n 'hotel',\n 'hostel',\n 'guest_house',\n 'motel',\n 'apartment',\n 'chalet',\n 'camp_site',\n 'caravan_site',\n]);\n\n/** `railway=*` values that are passenger access points. */\nconst TRANSPORT_RAILWAY = new Set(['station', 'halt', 'tram_stop', 'subway_entrance', 'stop']);\n\nexport interface Categorization {\n category: Category;\n /** The specific source value that drove the classification, if any. */\n kind?: string;\n}\n\n/**\n * Classify a set of OSM tags into a normalized {@link Category}. Keys are\n * checked in priority order; a present-but-unknown key falls back to 'other'\n * while preserving its value as `kind`.\n */\nexport function categorize(tags: Record<string, string>): Categorization {\n const {\n amenity,\n shop,\n tourism,\n leisure,\n healthcare,\n railway,\n public_transport,\n highway,\n aeroway,\n office,\n } = tags;\n\n if (amenity) {\n const category = AMENITY_TO_CATEGORY.get(amenity);\n return category ? { category, kind: amenity } : { category: 'other', kind: amenity };\n }\n if (shop) return { category: GROCERY_SHOPS.has(shop) ? 'grocery' : 'shopping', kind: shop };\n if (tourism)\n return { category: LODGING_TOURISM.has(tourism) ? 'accommodation' : 'tourism', kind: tourism };\n if (healthcare) return { category: 'healthcare', kind: healthcare };\n if (leisure) return { category: 'leisure', kind: leisure };\n if (railway && TRANSPORT_RAILWAY.has(railway)) return { category: 'transport', kind: railway };\n if (public_transport) return { category: 'transport', kind: public_transport };\n if (highway === 'bus_stop') return { category: 'transport', kind: 'bus_stop' };\n if (aeroway) return { category: 'transport', kind: aeroway };\n if (office)\n return {\n category: office === 'government' ? 'public_service' : 'other',\n kind: `office:${office}`,\n };\n return { category: 'other' };\n}\n\n/** Human-friendly display labels for each category. */\nexport const CATEGORY_LABELS: Record<Category, string> = {\n food: 'Food & drink',\n grocery: 'Groceries',\n shopping: 'Shopping',\n healthcare: 'Healthcare',\n education: 'Education',\n finance: 'Money',\n transport: 'Transport',\n fuel: 'Fuel & charging',\n parking: 'Parking',\n accommodation: 'Accommodation',\n leisure: 'Leisure',\n tourism: 'Tourism',\n worship: 'Places of worship',\n public_service: 'Public services',\n utility: 'Utilities',\n other: 'Other',\n};\n\n/** Type guard: is `value` one of the known {@link Category} names? */\nexport function isCategory(value: string): value is Category {\n return (CATEGORIES as readonly string[]).includes(value);\n}\n","import type { Category, CategorySelector } from './types';\n\n/**\n * Maps the words people (and agents) actually use — \"coffee\", \"chemist\",\n * \"petrol\" — to the OSM tag selectors that find them, and rolls each up to one\n * of the 16 normalized {@link Category} buckets. This is the query vocabulary;\n * `categorize()` is the inverse (tags -> category) used when reading results.\n */\ninterface TermDefinition {\n /** Canonical term. */\n term: string;\n category: Category;\n selectors: CategorySelector[];\n /** Alternative spellings/phrasings that resolve to this term. */\n synonyms?: string[];\n}\n\nconst a = (value: string): CategorySelector => ({ key: 'amenity', value });\nconst shop = (value: string): CategorySelector => ({ key: 'shop', value });\nconst leisure = (value: string): CategorySelector => ({ key: 'leisure', value });\nconst tourism = (value: string): CategorySelector => ({ key: 'tourism', value });\nconst rx = (key: string, value: string): CategorySelector => ({ key, value, regex: true });\nconst present = (key: string): CategorySelector => ({ key });\n\nconst TERMS: TermDefinition[] = [\n // food\n {\n term: 'food',\n category: 'food',\n selectors: [a('restaurant'), a('cafe'), a('fast_food'), a('bar'), a('pub'), a('food_court')],\n synonyms: ['food and drink', 'places to eat', 'eat', 'dining', 'somewhere to eat'],\n },\n { term: 'restaurant', category: 'food', selectors: [a('restaurant')], synonyms: ['restaurants'] },\n { term: 'cafe', category: 'food', selectors: [a('cafe')], synonyms: ['café', 'cafes'] },\n {\n term: 'coffee',\n category: 'food',\n selectors: [a('cafe'), rx('cuisine', 'coffee_shop'), shop('coffee')],\n synonyms: ['coffee shop', 'coffeehouse'],\n },\n {\n term: 'fast food',\n category: 'food',\n selectors: [a('fast_food')],\n synonyms: ['fastfood', 'takeaway food'],\n },\n { term: 'bar', category: 'food', selectors: [a('bar')], synonyms: ['bars'] },\n { term: 'pub', category: 'food', selectors: [a('pub')], synonyms: ['pubs'] },\n { term: 'pizza', category: 'food', selectors: [rx('cuisine', 'pizza')], synonyms: ['pizzeria'] },\n { term: 'ice cream', category: 'food', selectors: [a('ice_cream')], synonyms: ['gelato'] },\n\n // grocery\n {\n term: 'grocery',\n category: 'grocery',\n selectors: [shop('supermarket'), shop('convenience'), shop('greengrocer'), shop('grocery')],\n synonyms: ['groceries', 'grocery store', 'food shopping'],\n },\n { term: 'supermarket', category: 'grocery', selectors: [shop('supermarket')] },\n {\n term: 'convenience',\n category: 'grocery',\n selectors: [shop('convenience')],\n synonyms: ['convenience store', 'corner shop'],\n },\n { term: 'bakery', category: 'grocery', selectors: [shop('bakery')], synonyms: ['baker'] },\n\n // shopping\n {\n term: 'shopping',\n category: 'shopping',\n selectors: [present('shop')],\n synonyms: ['shops', 'stores', 'retail'],\n },\n {\n term: 'mall',\n category: 'shopping',\n selectors: [shop('mall'), shop('department_store')],\n synonyms: ['shopping mall', 'shopping centre', 'shopping center'],\n },\n {\n term: 'clothes',\n category: 'shopping',\n selectors: [shop('clothes')],\n synonyms: ['clothing', 'fashion'],\n },\n\n // healthcare\n {\n term: 'healthcare',\n category: 'healthcare',\n selectors: [\n a('hospital'),\n a('clinic'),\n a('doctors'),\n a('dentist'),\n a('pharmacy'),\n present('healthcare'),\n ],\n synonyms: ['health', 'medical', 'health care'],\n },\n {\n term: 'pharmacy',\n category: 'healthcare',\n selectors: [a('pharmacy'), rx('healthcare', 'pharmacy')],\n synonyms: ['chemist', 'drugstore', 'drug store'],\n },\n {\n term: 'hospital',\n category: 'healthcare',\n selectors: [a('hospital')],\n synonyms: ['hospitals', 'emergency room', 'a&e'],\n },\n { term: 'clinic', category: 'healthcare', selectors: [a('clinic'), rx('healthcare', 'clinic')] },\n {\n term: 'doctor',\n category: 'healthcare',\n selectors: [a('doctors'), rx('healthcare', 'doctor')],\n synonyms: ['doctors', 'gp', 'physician'],\n },\n {\n term: 'dentist',\n category: 'healthcare',\n selectors: [a('dentist'), rx('healthcare', 'dentist')],\n synonyms: ['dental'],\n },\n\n // education\n {\n term: 'education',\n category: 'education',\n selectors: [a('school'), a('college'), a('university'), a('kindergarten'), a('library')],\n synonyms: ['schools'],\n },\n { term: 'school', category: 'education', selectors: [a('school')] },\n {\n term: 'university',\n category: 'education',\n selectors: [a('university'), a('college')],\n synonyms: ['college', 'uni'],\n },\n { term: 'library', category: 'education', selectors: [a('library')], synonyms: ['libraries'] },\n\n // finance\n {\n term: 'finance',\n category: 'finance',\n selectors: [a('bank'), a('atm'), a('bureau_de_change')],\n synonyms: ['money', 'banking'],\n },\n {\n term: 'atm',\n category: 'finance',\n selectors: [a('atm')],\n synonyms: ['cash machine', 'cashpoint'],\n },\n { term: 'bank', category: 'finance', selectors: [a('bank')], synonyms: ['banks'] },\n\n // transport\n {\n term: 'transport',\n category: 'transport',\n selectors: [\n a('bus_station'),\n { key: 'public_transport', value: 'station' },\n rx('railway', 'station|halt|tram_stop|subway_entrance|stop'),\n { key: 'highway', value: 'bus_stop' },\n { key: 'aeroway', value: 'aerodrome' },\n ],\n synonyms: ['public transport', 'transit'],\n },\n {\n term: 'bus stop',\n category: 'transport',\n selectors: [{ key: 'highway', value: 'bus_stop' }, a('bus_station')],\n synonyms: ['bus', 'bus station'],\n },\n {\n term: 'train station',\n category: 'transport',\n selectors: [rx('railway', 'station|halt'), { key: 'public_transport', value: 'station' }],\n synonyms: ['railway station', 'train', 'metro', 'metro station', 'subway', 'subway station'],\n },\n { term: 'taxi', category: 'transport', selectors: [a('taxi')] },\n {\n term: 'airport',\n category: 'transport',\n selectors: [{ key: 'aeroway', value: 'aerodrome' }],\n synonyms: ['airports'],\n },\n\n // fuel\n {\n term: 'fuel',\n category: 'fuel',\n selectors: [a('fuel'), a('charging_station')],\n synonyms: ['petrol', 'gas', 'gas station', 'petrol station', 'filling station'],\n },\n {\n term: 'ev charging',\n category: 'fuel',\n selectors: [a('charging_station')],\n synonyms: ['charging station', 'ev charger', 'charger'],\n },\n\n // parking\n {\n term: 'parking',\n category: 'parking',\n selectors: [a('parking')],\n synonyms: ['car park', 'parking lot'],\n },\n {\n term: 'bicycle parking',\n category: 'parking',\n selectors: [a('bicycle_parking')],\n synonyms: ['bike parking'],\n },\n\n // accommodation\n {\n term: 'accommodation',\n category: 'accommodation',\n selectors: [tourism('hotel'), tourism('hostel'), tourism('guest_house'), tourism('motel')],\n synonyms: ['lodging', 'places to stay', 'stay'],\n },\n { term: 'hotel', category: 'accommodation', selectors: [tourism('hotel')], synonyms: ['hotels'] },\n { term: 'hostel', category: 'accommodation', selectors: [tourism('hostel')] },\n\n // leisure\n {\n term: 'leisure',\n category: 'leisure',\n selectors: [present('leisure')],\n synonyms: ['recreation'],\n },\n { term: 'park', category: 'leisure', selectors: [leisure('park')], synonyms: ['parks'] },\n {\n term: 'gym',\n category: 'leisure',\n selectors: [leisure('fitness_centre'), leisure('sports_centre')],\n synonyms: ['fitness', 'fitness centre', 'gymnasium'],\n },\n {\n term: 'cinema',\n category: 'leisure',\n selectors: [a('cinema')],\n synonyms: ['movie theater', 'movie theatre', 'movies'],\n },\n { term: 'playground', category: 'leisure', selectors: [leisure('playground')] },\n\n // tourism\n {\n term: 'tourism',\n category: 'tourism',\n selectors: [present('tourism')],\n synonyms: ['attractions', 'sights', 'things to do'],\n },\n { term: 'museum', category: 'tourism', selectors: [tourism('museum')], synonyms: ['museums'] },\n { term: 'viewpoint', category: 'tourism', selectors: [tourism('viewpoint')] },\n\n // worship\n {\n term: 'worship',\n category: 'worship',\n selectors: [a('place_of_worship')],\n synonyms: ['place of worship', 'church', 'mosque', 'temple', 'synagogue'],\n },\n\n // public_service\n {\n term: 'public_service',\n category: 'public_service',\n selectors: [a('police'), a('fire_station'), a('post_office'), a('townhall')],\n synonyms: ['public services', 'government', 'civic'],\n },\n {\n term: 'police',\n category: 'public_service',\n selectors: [a('police')],\n synonyms: ['police station'],\n },\n {\n term: 'post office',\n category: 'public_service',\n selectors: [a('post_office')],\n synonyms: ['postal'],\n },\n { term: 'fire station', category: 'public_service', selectors: [a('fire_station')] },\n\n // utility\n {\n term: 'utility',\n category: 'utility',\n selectors: [a('toilets'), a('drinking_water'), a('recycling'), a('post_box')],\n synonyms: ['utilities'],\n },\n {\n term: 'toilets',\n category: 'utility',\n selectors: [a('toilets')],\n synonyms: ['toilet', 'restroom', 'restrooms', 'wc', 'public toilet', 'bathroom'],\n },\n {\n term: 'drinking water',\n category: 'utility',\n selectors: [a('drinking_water')],\n synonyms: ['water fountain', 'water point'],\n },\n\n // other (catch-all; not directly queryable)\n { term: 'other', category: 'other', selectors: [] },\n];\n\nconst normalize = (term: string): string => term.trim().toLowerCase().replace(/\\s+/g, ' ');\n\nconst TERM_INDEX = new Map<string, TermDefinition>();\nfor (const def of TERMS) {\n TERM_INDEX.set(normalize(def.term), def);\n for (const synonym of def.synonyms ?? []) TERM_INDEX.set(normalize(synonym), def);\n}\n\nconst selectorKey = (s: CategorySelector): string => `${s.key}|${s.value ?? ''}|${s.regex ? 1 : 0}`;\n\nexport interface ResolvedCategories {\n /** Deduplicated selectors for all recognized terms. */\n selectors: CategorySelector[];\n /** Recognized terms with their canonical name and category. */\n matched: { input: string; term: string; category: Category }[];\n /** Terms that could not be resolved. */\n unknown: string[];\n}\n\n/** Resolve a list of natural-language terms (or category names) to selectors. */\nexport function resolveCategories(terms: readonly string[]): ResolvedCategories {\n const selectors: CategorySelector[] = [];\n const seen = new Set<string>();\n const matched: ResolvedCategories['matched'] = [];\n const unknown: string[] = [];\n\n for (const input of terms) {\n const def = TERM_INDEX.get(normalize(input));\n if (!def) {\n unknown.push(input);\n continue;\n }\n matched.push({ input, term: def.term, category: def.category });\n for (const selector of def.selectors) {\n const key = selectorKey(selector);\n if (!seen.has(key)) {\n seen.add(key);\n selectors.push(selector);\n }\n }\n }\n return { selectors, matched, unknown };\n}\n\n/** Is `term` a known category or synonym? */\nexport function isKnownTerm(term: string): boolean {\n return TERM_INDEX.has(normalize(term));\n}\n\n/** All canonical query terms with their top-level category. */\nexport function categoryVocabulary(): { term: string; category: Category }[] {\n return TERMS.map(({ term, category }) => ({ term, category }));\n}\n\nfunction editDistance(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const row = Array.from({ length: n + 1 }, (_, i) => i);\n for (let i = 1; i <= m; i++) {\n let prev = row[0]!;\n row[0] = i;\n for (let j = 1; j <= n; j++) {\n const temp = row[j]!;\n row[j] = Math.min(row[j]! + 1, row[j - 1]! + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));\n prev = temp;\n }\n }\n return row[n]!;\n}\n\n/** Suggest known terms for an unrecognized input (typos, partial matches). */\nexport function suggestCategories(term: string, limit = 5): string[] {\n const query = normalize(term);\n const scored: { term: string; score: number }[] = [];\n for (const def of TERMS) {\n const candidates = [def.term, ...(def.synonyms ?? [])].map(normalize);\n let best = Infinity;\n for (const candidate of candidates) {\n if (candidate.includes(query) || query.includes(candidate)) best = Math.min(best, 0);\n else best = Math.min(best, editDistance(query, candidate));\n }\n if (best <= 2) scored.push({ term: def.term, score: best });\n }\n scored.sort((x, y) => x.score - y.score || x.term.localeCompare(y.term));\n return scored.slice(0, limit).map((s) => s.term);\n}\n\n/** Render a selector as an Overpass tag filter, e.g. `[\"amenity\"=\"cafe\"]`. */\nexport function selectorToOverpassFilter(selector: CategorySelector): string {\n if (selector.value === undefined) return `[\"${selector.key}\"]`;\n const op = selector.regex ? '~' : '=';\n return `[\"${selector.key}\"${op}\"${selector.value}\"]`;\n}\n\n/** Does a tag set satisfy a single selector? */\nexport function tagsMatchSelector(\n tags: Record<string, string>,\n selector: CategorySelector,\n): boolean {\n const value = tags[selector.key];\n if (value === undefined) return false;\n if (selector.value === undefined) return true;\n return selector.regex ? new RegExp(selector.value).test(value) : value === selector.value;\n}\n\n/** Does a tag set satisfy any of the selectors? */\nexport function tagsMatchAnySelector(\n tags: Record<string, string>,\n selectors: readonly CategorySelector[],\n): boolean {\n return selectors.some((selector) => tagsMatchSelector(tags, selector));\n}\n","import { haversineMeters } from './geo';\nimport type { Category, Poi } from './types';\n\n/**\n * Tags a well-described POI of each category tends to carry. Used to compute a\n * coarse completeness score — a trust signal that lets gap/score features treat\n * sparse OSM data honestly (low completeness, not a confident \"no\").\n */\nconst BASE_EXPECTED = ['name'];\nconst EXPECTED_BY_CATEGORY: Partial<Record<Category, string[]>> = {\n food: ['name', 'opening_hours', 'cuisine', 'website', 'phone', 'wheelchair'],\n grocery: ['name', 'opening_hours', 'website', 'phone'],\n shopping: ['name', 'opening_hours', 'website', 'phone'],\n healthcare: ['name', 'opening_hours', 'phone', 'website', 'wheelchair'],\n education: ['name', 'website', 'phone'],\n finance: ['name', 'opening_hours', 'operator'],\n transport: ['name', 'network', 'operator'],\n fuel: ['name', 'opening_hours', 'operator'],\n parking: ['capacity', 'fee', 'access'],\n accommodation: ['name', 'website', 'phone', 'stars'],\n leisure: ['name', 'opening_hours'],\n tourism: ['name', 'website', 'opening_hours'],\n worship: ['name', 'religion'],\n public_service: ['name', 'opening_hours', 'phone'],\n utility: ['fee', 'access'],\n other: ['name'],\n};\n\n/** Fraction of a category's expected tags that are present, in [0, 1]. */\nexport function completenessOf(category: Category, tags: Record<string, string>): number {\n const expected = EXPECTED_BY_CATEGORY[category] ?? BASE_EXPECTED;\n if (expected.length === 0) return 1;\n const present = expected.filter((key) => {\n const value = tags[key];\n return value !== undefined && value.trim() !== '';\n }).length;\n return Math.round((present / expected.length) * 100) / 100;\n}\n\nconst DATE_TAGS = ['check_date', 'check_date:opening_hours', 'survey:date'];\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}/;\n\n/**\n * Best-effort \"last verified\" date (YYYY-MM-DD): an explicit survey/check_date\n * tag if present, else the element's last-edit timestamp.\n */\nexport function lastVerifiedOf(\n tags: Record<string, string>,\n timestamp?: string,\n): string | undefined {\n for (const key of DATE_TAGS) {\n const value = tags[key];\n if (value && ISO_DATE.test(value)) return value.slice(0, 10);\n }\n if (timestamp && ISO_DATE.test(timestamp)) return timestamp.slice(0, 10);\n return undefined;\n}\n\nconst normalizeName = (poi: Poi): string => (poi.name ?? '').trim().toLowerCase();\n\n/** Heuristic: do two POIs describe the same real-world feature? Conservative. */\nfunction isDuplicate(a: Poi, b: Poi): boolean {\n if (a.category !== b.category) return false;\n const distance = haversineMeters(a.location, b.location);\n if (distance > 40) return false;\n\n const an = normalizeName(a);\n const bn = normalizeName(b);\n if (an && bn) return an === bn; // both named: names must agree\n // at least one unnamed: only merge near-coincident features of the same kind\n return distance <= 15 && a.kind === b.kind;\n}\n\n/** Higher = a better representative to keep when collapsing duplicates. */\nfunction richness(poi: Poi): number {\n let score = 0;\n if (poi.name) score += 100;\n score += (poi.completeness ?? 0) * 10;\n if (poi.id.startsWith('way/') || poi.id.startsWith('relation/')) score += 1;\n return score;\n}\n\n/**\n * Collapse duplicate representations of the same POI (e.g. a node and a building\n * way) into one, keeping the richer entry. Order of first appearance is kept.\n */\nexport function dedupePois(pois: Poi[]): Poi[] {\n const kept: Poi[] = [];\n for (const poi of pois) {\n const index = kept.findIndex((other) => isDuplicate(other, poi));\n if (index === -1) kept.push(poi);\n else if (richness(poi) > richness(kept[index]!)) kept[index] = poi;\n }\n return kept;\n}\n","import type { OpenState } from './types';\n\nexport interface OpeningEvaluation {\n state: OpenState;\n /** ISO 8601 timestamp of the next open/closed transition, when computable. */\n nextChange?: string;\n}\n\n/**\n * A small, dependency-free evaluator for the common subset of the OSM\n * `opening_hours` grammar: weekday ranges/lists, multiple time ranges,\n * overnight (midnight-wrapping) ranges, `24/7`, and `off`/`closed`. Public- and\n * school-holiday rules (`PH`/`SH`) are skipped (no holiday calendar), so a\n * normal day still evaluates from the regular rules.\n *\n * Anything outside this subset — `sunrise`/`sunset`, month/date/week selectors,\n * open-ended `08:00+`, etc. — yields `unknown` rather than a guess. A missing or\n * empty value is `unknown` too. We never assert a state we can't justify.\n *\n * Times are read from the local-time fields of `when`; to evaluate a POI in\n * another timezone, pass a `when` already shifted into that zone.\n */\nexport function isOpenAt(openingHours: string | undefined, when: Date): OpeningEvaluation {\n const raw = (openingHours ?? '').trim();\n if (!raw) return { state: 'unknown' };\n\n const lower = raw.toLowerCase();\n if (/^24\\s*\\/\\s*7$/.test(lower) || lower === '24/7') return { state: 'open' };\n if (lower === 'off' || lower === 'closed') return { state: 'closed' };\n if (hasUnsupported(lower)) return { state: 'unknown' };\n\n const segments = buildSegments(lower);\n if (!segments) return { state: 'unknown' };\n\n const day = when.getDay();\n const minute = when.getHours() * 60 + when.getMinutes();\n const open = isOpenInSegments(segments, day, minute);\n\n const evaluation: OpeningEvaluation = { state: open ? 'open' : 'closed' };\n const next = nextChange(segments, when, open);\n if (next) evaluation.nextChange = next;\n return evaluation;\n}\n\n// getDay(): 0 = Sunday … 6 = Saturday. OSM weeks run Mo…Su.\nconst DAY_INDEX: Record<string, number> = { su: 0, mo: 1, tu: 2, we: 3, th: 4, fr: 5, sa: 6 };\nconst WEEK_ORDER = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'];\nconst MONTHS = /\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b/;\n\n/** Grammar we deliberately don't evaluate; presence ⇒ `unknown` (never a guess). */\nfunction hasUnsupported(s: string): boolean {\n return (\n /sunrise|sunset|dawn|dusk|easter|\\bweek\\b/.test(s) ||\n /\\+/.test(s) || // open-ended times like 08:00+\n /\\b\\d{4}\\b/.test(s) || // explicit years\n MONTHS.test(s)\n );\n}\n\ntype Interval = [number, number]; // [start, end) in minutes within a day\ntype Segments = Interval[][]; // [dayIndex 0..6] -> that day's open intervals\n\nfunction buildSegments(input: string): Segments | null {\n // Each day's own intervals, last matching rule wins (OSM `;` override).\n const own: (Interval[] | undefined)[] = new Array(7).fill(undefined);\n\n for (const rulePart of input.split(';')) {\n const rule = rulePart.trim();\n if (!rule) continue;\n const parsed = parseRule(rule.replace(/\\s*([,:\\-])\\s*/g, '$1'));\n if (parsed === 'unknown') return null;\n if (parsed === null) continue; // holiday-only rule: skip\n for (const day of parsed.days) own[day] = parsed.intervals;\n }\n\n // Expand into per-day segments, spilling overnight ranges into the next day.\n const segments: Segments = Array.from({ length: 7 }, () => [] as Interval[]);\n for (let day = 0; day < 7; day++) {\n const intervals = own[day];\n if (!intervals) continue;\n for (const [start, end] of intervals) {\n if (start === end)\n segments[day]!.push([0, 1440]); // full day (e.g. 00:00-00:00)\n else if (end > start) segments[day]!.push([start, end]);\n else {\n segments[day]!.push([start, 1440]); // head: until midnight\n segments[(day + 1) % 7]!.push([0, end]); // tail: early next day\n }\n }\n }\n return segments;\n}\n\ntype ParsedRule = { days: number[]; intervals: Interval[] };\n\nfunction parseRule(rule: string): ParsedRule | 'unknown' | null {\n let dayPart: string;\n let timePart: string;\n const space = rule.indexOf(' ');\n if (space === -1) {\n if (/\\d/.test(rule)) {\n dayPart = '';\n timePart = rule;\n } else if (rule === 'off' || rule === 'closed') {\n dayPart = '';\n timePart = rule;\n } else {\n dayPart = rule;\n timePart = '';\n }\n } else {\n dayPart = rule.slice(0, space);\n timePart = rule.slice(space + 1).trim();\n }\n\n const days = parseDays(dayPart);\n if (days === 'unknown') return 'unknown';\n if (days === null) return null;\n\n if (timePart === '') return { days, intervals: [[0, 1440]] as Interval[] };\n if (timePart === 'off' || timePart === 'closed') return { days, intervals: [] };\n\n const intervals = parseTimes(timePart);\n if (!intervals) return 'unknown';\n return { days, intervals };\n}\n\nfunction parseDays(part: string): number[] | 'unknown' | null {\n if (part === '') return [0, 1, 2, 3, 4, 5, 6];\n\n const days = new Set<number>();\n let sawReal = false;\n let sawHoliday = false;\n for (const token of part.split(',')) {\n if (token === 'ph' || token === 'sh') {\n sawHoliday = true;\n continue;\n }\n const match = token.match(/^([a-z]{2})(?:-([a-z]{2}))?$/);\n if (!match) return 'unknown';\n const from = match[1]!;\n if (!(from in DAY_INDEX)) return 'unknown';\n if (match[2] === undefined) {\n days.add(DAY_INDEX[from]!);\n sawReal = true;\n } else {\n const to = match[2];\n if (!(to in DAY_INDEX)) return 'unknown';\n addWeekdayRange(days, from, to);\n sawReal = true;\n }\n }\n if (!sawReal) return sawHoliday ? null : 'unknown';\n return [...days];\n}\n\n/** Add an OSM weekday range (Mo…Su order, wrapping, e.g. Fr-Mo). */\nfunction addWeekdayRange(set: Set<number>, from: string, to: string): void {\n let i = WEEK_ORDER.indexOf(from);\n const end = WEEK_ORDER.indexOf(to);\n for (;;) {\n set.add(DAY_INDEX[WEEK_ORDER[i]!]!);\n if (i === end) break;\n i = (i + 1) % 7;\n }\n}\n\nfunction parseTimes(part: string): Interval[] | null {\n const intervals: Interval[] = [];\n for (const piece of part.split(',')) {\n const match = piece.match(/^(\\d{1,2}):(\\d{2})-(\\d{1,2}):(\\d{2})$/);\n if (!match) return null;\n const h1 = Number(match[1]);\n const m1 = Number(match[2]);\n const h2 = Number(match[3]);\n const m2 = Number(match[4]);\n if (h1 > 24 || h2 > 24 || m1 > 59 || m2 > 59) return null;\n intervals.push([h1 * 60 + m1, h2 * 60 + m2]);\n }\n return intervals;\n}\n\nfunction isOpenInSegments(segments: Segments, day: number, minute: number): boolean {\n return segments[day]!.some(([start, end]) => minute >= start && minute < end);\n}\n\n/**\n * The next instant the open/closed state flips, searching up to 8 days ahead.\n * Returns undefined when nothing changes in that window (e.g. always closed).\n */\nfunction nextChange(segments: Segments, when: Date, currentlyOpen: boolean): string | undefined {\n const nowMinute = when.getHours() * 60 + when.getMinutes();\n const baseDay = when.getDay();\n\n const candidates: number[] = [];\n for (let offset = 0; offset <= 8; offset++) {\n const day = (baseDay + offset) % 7;\n for (const [start, end] of segments[day]!) {\n candidates.push(offset * 1440 + start, offset * 1440 + end);\n }\n }\n\n candidates.sort((a, b) => a - b);\n for (const candidate of candidates) {\n if (candidate <= nowMinute) continue;\n const day = (baseDay + Math.floor(candidate / 1440)) % 7;\n const open = isOpenInSegments(segments, day, candidate % 1440);\n if (open !== currentlyOpen) {\n const midnight = new Date(when.getFullYear(), when.getMonth(), when.getDate(), 0, 0, 0, 0);\n return new Date(midnight.getTime() + candidate * 60_000).toISOString();\n }\n }\n return undefined;\n}\n","/**\n * Minimal JSON-over-HTTP helper built on the platform `fetch`. Keeps the core\n * dependency-free while handling timeouts, cancellation, retries with backoff,\n * optional caching, and consistent error surfacing across providers.\n */\n\n/** Default contact identifier sent to OSM services; override per provider. */\nexport const DEFAULT_USER_AGENT = 'proximap/0.1 (+https://github.com/AmeyaBorkar/proximap)';\n\n/** A pluggable response cache. Values are parsed JSON. */\nexport interface RequestCache {\n get(key: string): Promise<unknown> | unknown;\n set(key: string, value: unknown): Promise<void> | void;\n}\n\n/** A process-local, unbounded cache. Opt-in — pass it to a provider to enable. */\nexport class InMemoryCache implements RequestCache {\n private readonly store = new Map<string, unknown>();\n get(key: string): unknown {\n return this.store.get(key);\n }\n set(key: string, value: unknown): void {\n this.store.set(key, value);\n }\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Serializes calls so that consecutive `acquire()`s are spaced at least\n * `minIntervalMs` apart — for honouring usage policies (e.g. Nominatim's 1 req/s).\n */\nexport class RateLimiter {\n private last = 0;\n private chain: Promise<void> = Promise.resolve();\n\n constructor(private readonly minIntervalMs: number) {}\n\n acquire(): Promise<void> {\n this.chain = this.chain.then(async () => {\n if (this.minIntervalMs <= 0) return;\n const wait = this.last + this.minIntervalMs - Date.now();\n if (wait > 0) await sleep(wait);\n this.last = Date.now();\n });\n return this.chain;\n }\n}\n\nexport interface RequestOptions {\n method?: 'GET' | 'POST';\n headers?: Record<string, string>;\n body?: string;\n /** External cancellation signal; merged with the internal timeout. */\n signal?: AbortSignal;\n /** Abort the request after this many milliseconds (default 20000). */\n timeoutMs?: number;\n /** Retry attempts on transient failures (429/5xx/timeout/network). Default 0. */\n retries?: number;\n /** Base backoff delay in ms, doubled each attempt (default 500). */\n retryDelayMs?: number;\n /** Optional response cache, keyed by method + url + body. */\n cache?: RequestCache;\n}\n\n/** Thrown when a request times out or returns a non-2xx response. */\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n message: string,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof HttpError) {\n return error.status === 0 || error.status === 429 || error.status >= 500;\n }\n if (error instanceof Error && error.name === 'AbortError') return false;\n return true; // treat unexpected network errors as transient\n}\n\nasync function fetchJsonOnce<T>(url: string, options: RequestOptions): Promise<T> {\n const { method = 'GET', headers = {}, body, signal, timeoutMs = 20_000 } = options;\n const timeout = AbortSignal.timeout(timeoutMs);\n const composite = signal ? AbortSignal.any([signal, timeout]) : timeout;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: { 'User-Agent': DEFAULT_USER_AGENT, Accept: 'application/json', ...headers },\n ...(body === undefined ? {} : { body }),\n signal: composite,\n });\n } catch (error) {\n if (timeout.aborted) throw new HttpError(0, url, `Request timed out after ${timeoutMs} ms`);\n throw error;\n }\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new HttpError(\n response.status,\n url,\n `${response.status} ${response.statusText}: ${detail.slice(0, 200)}`.trim(),\n );\n }\n return (await response.json()) as T;\n}\n\n/** Perform an HTTP request and parse the JSON body as `T`, with retry + cache. */\nexport async function requestJson<T>(url: string, options: RequestOptions = {}): Promise<T> {\n const { method = 'GET', body, retries = 0, retryDelayMs = 500, cache } = options;\n const cacheKey = cache ? `${method} ${url} ${body ?? ''}` : undefined;\n\n if (cache && cacheKey !== undefined) {\n const cached = await cache.get(cacheKey);\n if (cached !== undefined) return cached as T;\n }\n\n let lastError: unknown;\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1));\n try {\n const value = await fetchJsonOnce<T>(url, options);\n if (cache && cacheKey !== undefined) await cache.set(cacheKey, value);\n return value;\n } catch (error) {\n lastError = error;\n if (attempt === retries || !isRetryable(error)) throw error;\n }\n }\n throw lastError;\n}\n","import { DEFAULT_USER_AGENT, RateLimiter, requestJson, type RequestCache } from '../http';\nimport type { GeocodeOptions, GeocodingProvider, LatLng, Place } from '../types';\n\nexport interface NominatimOptions {\n /** Base URL of the Nominatim instance (no trailing slash required). */\n endpoint?: string;\n /** Contact User-Agent, required by the public OSM instance's usage policy. */\n userAgent?: string;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000, per OSM policy). */\n minIntervalMs?: number;\n /** Retry attempts on transient failures (default 2). */\n retries?: number;\n /** Optional response cache (opt-in). */\n cache?: RequestCache;\n}\n\ninterface NominatimResult {\n place_id: number;\n lat: string;\n lon: string;\n name?: string;\n display_name: string;\n class?: string;\n type?: string;\n /** Nominatim's relevance heuristic in [0, 1]; not correctness — used for ambiguity. */\n importance?: number;\n /** [south, north, west, east] as strings. */\n boundingbox?: [string, string, string, string];\n}\n\n/**\n * Geocoding via Nominatim (OpenStreetMap). Free and key-less; please honour the\n * usage policy (max ~1 req/s, valid User-Agent) or point `endpoint` at your own\n * instance for production traffic.\n */\nexport class NominatimGeocoder implements GeocodingProvider {\n readonly name = 'nominatim';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: NominatimOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://nominatim.openstreetmap.org').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 2;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async geocode(query: string, options: GeocodeOptions = {}): Promise<Place[]> {\n const params = new URLSearchParams({\n q: query,\n format: 'jsonv2',\n limit: String(options.limit ?? 5),\n });\n if (options.language) params.set('accept-language', options.language);\n const results = await this.request<NominatimResult[]>(`/search?${params}`, options.signal);\n return results.map((result) => this.toPlace(result));\n }\n\n async reverse(location: LatLng, options: GeocodeOptions = {}): Promise<Place | null> {\n const params = new URLSearchParams({\n lat: String(location.lat),\n lon: String(location.lng),\n format: 'jsonv2',\n });\n if (options.language) params.set('accept-language', options.language);\n const result = await this.request<NominatimResult | { error: unknown }>(\n `/reverse?${params}`,\n options.signal,\n );\n if (!result || 'error' in result) return null;\n return this.toPlace(result);\n }\n\n private async request<T>(path: string, signal: AbortSignal | undefined): Promise<T> {\n await this.limiter.acquire();\n return requestJson<T>(`${this.endpoint}${path}`, {\n headers: { 'User-Agent': this.userAgent },\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(signal ? { signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n }\n\n private toPlace(result: NominatimResult): Place {\n const fallbackName = result.display_name.split(',')[0]?.trim() ?? result.display_name;\n const place: Place = {\n name: result.name && result.name.length > 0 ? result.name : fallbackName,\n displayName: result.display_name,\n location: { lat: Number(result.lat), lng: Number(result.lon) },\n source: this.name,\n raw: result,\n };\n const kind = result.type ?? result.class;\n if (kind) place.kind = kind;\n if (result.boundingbox) {\n const [s, n, w, e] = result.boundingbox;\n place.boundingBox = [Number(s), Number(n), Number(w), Number(e)];\n }\n return place;\n }\n}\n","import { categorize } from '../categories';\nimport {\n DEFAULT_USER_AGENT,\n HttpError,\n RateLimiter,\n requestJson,\n type RequestCache,\n} from '../http';\nimport { completenessOf, dedupePois, lastVerifiedOf } from '../quality';\nimport { selectorToOverpassFilter } from '../taxonomy';\nimport type { CategorySelector, LatLng, NearbyOptions, PlacesProvider, Poi } from '../types';\n\nexport interface OverpassOptions {\n /** Overpass interpreter endpoint URL. */\n endpoint?: string;\n /** Contact User-Agent sent with each request. */\n userAgent?: string;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000). */\n minIntervalMs?: number;\n /** Retry attempts on transient failures (default 2). */\n retries?: number;\n /** Optional response cache (opt-in). */\n cache?: RequestCache;\n}\n\ninterface OverpassElement {\n type: 'node' | 'way' | 'relation';\n id: number;\n lat?: number;\n lon?: number;\n center?: { lat: number; lon: number };\n tags?: Record<string, string>;\n /** Last-edit time, present when the query requests `meta`. */\n timestamp?: string;\n}\n\ninterface OverpassResponse {\n elements?: OverpassElement[];\n /** Present when Overpass reports a runtime error/timeout (often with HTTP 200). */\n remark?: string;\n}\n\n/** Tag selectors fetched around the search center. Mirrors `categorize`. */\nconst SELECTORS = [\n 'nwr[\"amenity\"]',\n 'nwr[\"shop\"]',\n 'nwr[\"tourism\"]',\n 'nwr[\"leisure\"]',\n 'nwr[\"healthcare\"]',\n 'nwr[\"office\"=\"government\"]',\n 'nwr[\"railway\"~\"^(station|halt|tram_stop|subway_entrance|stop)$\"]',\n 'nwr[\"public_transport\"=\"station\"]',\n 'node[\"highway\"=\"bus_stop\"]',\n 'nwr[\"aeroway\"=\"aerodrome\"]',\n];\n\n/** Build the Overpass QL query for all relevant POIs within `radiusMeters`. */\nexport function buildOverpassQuery(center: LatLng, radiusMeters: number): string {\n const around = `around:${Math.max(1, Math.round(radiusMeters))},${center.lat},${center.lng}`;\n const body = SELECTORS.map((selector) => ` ${selector}(${around});`).join('\\n');\n return `[out:json][timeout:25];\\n(\\n${body}\\n);\\nout center tags meta;`;\n}\n\n/** Build an Overpass query that fetches only features matching `selectors`. */\nexport function buildTargetedOverpassQuery(\n center: LatLng,\n radiusMeters: number,\n selectors: CategorySelector[],\n): string {\n const around = `around:${Math.max(1, Math.round(radiusMeters))},${center.lat},${center.lng}`;\n const body = selectors\n .map((selector) => ` nwr${selectorToOverpassFilter(selector)}(${around});`)\n .join('\\n');\n return `[out:json][timeout:25];\\n(\\n${body}\\n);\\nout center tags meta;`;\n}\n\nfunction coordOf(lat: number | undefined, lon: number | undefined): LatLng | null {\n if (\n typeof lat === 'number' &&\n typeof lon === 'number' &&\n Number.isFinite(lat) &&\n Number.isFinite(lon)\n ) {\n return { lat, lng: lon };\n }\n return null;\n}\n\n/**\n * Nearby-places search via the Overpass API over OpenStreetMap data. Fetches\n * every relevant feature within the radius, classifies it, and (optionally)\n * filters by category — distance ranking happens upstream.\n */\nexport class OverpassPlacesProvider implements PlacesProvider {\n readonly name = 'overpass';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: OverpassOptions = {}) {\n this.endpoint = options.endpoint ?? 'https://overpass-api.de/api/interpreter';\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.retries = options.retries ?? 2;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]> {\n const query =\n options.selectors && options.selectors.length > 0\n ? buildTargetedOverpassQuery(center, options.radiusMeters, options.selectors)\n : buildOverpassQuery(center, options.radiusMeters);\n await this.limiter.acquire();\n const data = await requestJson<OverpassResponse>(this.endpoint, {\n method: 'POST',\n headers: {\n 'User-Agent': this.userAgent,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: `data=${encodeURIComponent(query)}`,\n timeoutMs: this.timeoutMs,\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n if (data.remark && /error|timed out|rate_limited|too many|memory/i.test(data.remark)) {\n throw new HttpError(0, this.endpoint, `Overpass: ${data.remark}`);\n }\n\n const wanted = options.categories ? new Set(options.categories) : null;\n const pois: Poi[] = [];\n for (const element of data.elements ?? []) {\n const poi = this.toPoi(element);\n if (!poi) continue;\n if (wanted && !wanted.has(poi.category)) continue;\n pois.push(poi);\n }\n return dedupePois(pois);\n }\n\n private toPoi(element: OverpassElement): Poi | null {\n const tags = element.tags;\n if (!tags) return null;\n const coords =\n element.type === 'node'\n ? coordOf(element.lat, element.lon)\n : element.center\n ? coordOf(element.center.lat, element.center.lon)\n : null;\n if (!coords) return null;\n\n const { category, kind } = categorize(tags);\n const poi: Poi = {\n id: `${element.type}/${element.id}`,\n category,\n location: coords,\n tags,\n source: this.name,\n };\n if (tags.name) poi.name = tags.name;\n if (kind) poi.kind = kind;\n poi.completeness = completenessOf(category, tags);\n const lastVerified = lastVerifiedOf(tags, element.timestamp);\n if (lastVerified) poi.lastVerified = lastVerified;\n return poi;\n }\n}\n","import {\n DEFAULT_USER_AGENT,\n HttpError,\n RateLimiter,\n requestJson,\n type RequestCache,\n} from '../http';\nimport type {\n PolygonRing,\n RouteMetric,\n RoutingProvider,\n RoutingRequestOptions,\n TravelMode,\n} from '../routing';\nimport type { LatLng } from '../types';\n\n/** proximap travel modes → Valhalla costing models. */\nconst COSTING: Record<TravelMode, string> = {\n walk: 'pedestrian',\n bike: 'bicycle',\n drive: 'auto',\n};\n\nexport interface ValhallaOptions {\n /** Base URL of the Valhalla instance (default: the key-free FOSSGIS instance). */\n endpoint?: string;\n userAgent?: string;\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000). */\n minIntervalMs?: number;\n retries?: number;\n cache?: RequestCache;\n}\n\ninterface MatrixCell {\n distance: number | null; // kilometres\n time: number | null; // seconds\n to_index: number;\n from_index: number;\n}\ninterface MatrixResponse {\n sources_to_targets?: MatrixCell[][];\n error?: string;\n}\ninterface IsochroneResponse {\n features?: { geometry?: { type: string; coordinates: unknown } }[];\n error?: string;\n}\n\n/**\n * Routing via Valhalla. Defaults to the **key-free** public FOSSGIS instance,\n * which supports pedestrian/bicycle/auto matrices and real isochrone polygons —\n * please honour its ~1 req/s fair-use policy or self-host for volume.\n */\nexport class ValhallaRoutingProvider implements RoutingProvider {\n readonly name = 'valhalla';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: ValhallaOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://valhalla1.openstreetmap.de').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 1;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<(RouteMetric | null)[]> {\n if (targets.length === 0) return [];\n const body = JSON.stringify({\n sources: [{ lat: origin.lat, lon: origin.lng }],\n targets: targets.map((target) => ({ lat: target.lat, lon: target.lng })),\n costing: COSTING[mode],\n });\n const data = await this.post<MatrixResponse>('/sources_to_targets', body, options.signal);\n if (data.error) throw new HttpError(0, this.endpoint, `Valhalla: ${data.error}`);\n\n const row = data.sources_to_targets?.[0] ?? [];\n const metrics: (RouteMetric | null)[] = targets.map(() => null);\n for (const cell of row) {\n if (cell.time === null || cell.distance === null) continue;\n if (cell.to_index < 0 || cell.to_index >= metrics.length) continue;\n metrics[cell.to_index] = {\n seconds: Math.round(cell.time),\n meters: Math.round(cell.distance * 1000),\n };\n }\n return metrics;\n }\n\n async isochrone(\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<PolygonRing> {\n const body = JSON.stringify({\n locations: [{ lat: origin.lat, lon: origin.lng }],\n costing: COSTING[mode],\n contours: [{ time: minutes }],\n polygons: true,\n });\n const data = await this.post<IsochroneResponse>('/isochrone', body, options.signal);\n if (data.error) throw new HttpError(0, this.endpoint, `Valhalla: ${data.error}`);\n const ring = extractRing(data.features ?? []);\n if (!ring) throw new HttpError(0, this.endpoint, 'Valhalla: no isochrone polygon returned');\n return ring;\n }\n\n private async post<T>(path: string, body: string, signal: AbortSignal | undefined): Promise<T> {\n await this.limiter.acquire();\n return requestJson<T>(`${this.endpoint}${path}`, {\n method: 'POST',\n headers: { 'User-Agent': this.userAgent, 'Content-Type': 'application/json' },\n body,\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(signal ? { signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n }\n}\n\n/** Pull the outer ring out of a Valhalla isochrone FeatureCollection (Polygon/MultiPolygon). */\nfunction extractRing(\n features: { geometry?: { type: string; coordinates: unknown } }[],\n): PolygonRing | null {\n for (const feature of features) {\n const geometry = feature.geometry;\n if (!geometry) continue;\n const coords = geometry.coordinates;\n if (geometry.type === 'LineString' && isRing(coords)) return coords;\n if (geometry.type === 'Polygon' && Array.isArray(coords) && isRing(coords[0])) {\n return coords[0];\n }\n if (\n geometry.type === 'MultiPolygon' &&\n Array.isArray(coords) &&\n Array.isArray(coords[0]) &&\n isRing(coords[0][0])\n ) {\n return coords[0][0];\n }\n }\n return null;\n}\n\nfunction isRing(value: unknown): value is PolygonRing {\n return (\n Array.isArray(value) &&\n value.length >= 3 &&\n value.every(\n (point) =>\n Array.isArray(point) &&\n point.length >= 2 &&\n typeof point[0] === 'number' &&\n typeof point[1] === 'number',\n )\n );\n}\n","import { DEFAULT_USER_AGENT, RateLimiter, requestJson, type RequestCache } from '../http';\nimport type { RouteMetric, RoutingProvider, RoutingRequestOptions, TravelMode } from '../routing';\nimport type { LatLng } from '../types';\n\n/** proximap travel modes → OSRM profiles (self-hosted; the public demo is car-only). */\nconst PROFILE: Record<TravelMode, string> = { walk: 'foot', bike: 'bike', drive: 'driving' };\n\nexport interface OsrmOptions {\n /** Base URL of the OSRM instance (default: the public demo server). */\n endpoint?: string;\n userAgent?: string;\n timeoutMs?: number;\n minIntervalMs?: number;\n retries?: number;\n cache?: RequestCache;\n}\n\ninterface TableResponse {\n code: string;\n /** durations[i][j] seconds; sources=0 ⇒ durations[0] is origin→each coordinate. */\n durations?: (number | null)[][];\n distances?: (number | null)[][];\n}\n\n/**\n * Travel-time matrices via OSRM's Table service. The public demo server\n * (`router.project-osrm.org`) only offers the car profile; for walk/bike,\n * point `endpoint` at a self-hosted OSRM with the matching profile. Matrix only —\n * OSRM has no isochrone service (use {@link ValhallaRoutingProvider} for that).\n */\nexport class OsrmRoutingProvider implements RoutingProvider {\n readonly name = 'osrm';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: OsrmOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://router.project-osrm.org').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 1;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<(RouteMetric | null)[]> {\n if (targets.length === 0) return [];\n const coordinates = [origin, ...targets].map((c) => `${c.lng},${c.lat}`).join(';');\n const params = new URLSearchParams({ sources: '0', annotations: 'duration,distance' });\n const url = `${this.endpoint}/table/v1/${PROFILE[mode]}/${coordinates}?${params}`;\n\n await this.limiter.acquire();\n const data = await requestJson<TableResponse>(url, {\n headers: { 'User-Agent': this.userAgent },\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n\n const durations = data.durations?.[0] ?? [];\n const distances = data.distances?.[0] ?? [];\n return targets.map((_, index) => {\n const seconds = durations[index + 1]; // index 0 is the origin→origin self-pair\n if (seconds === null || seconds === undefined) return null;\n const meters = distances[index + 1];\n return { seconds: Math.round(seconds), meters: meters == null ? 0 : Math.round(meters) };\n });\n }\n}\n","import { haversineMeters } from './geo';\nimport type { Category, LatLng, Poi, RankedPoi } from './types';\n\n/** Inputs handed to a scorer for a single POI. */\nexport interface ScoreInput {\n poi: Poi;\n distanceMeters: number;\n /** Radius used to normalize distance into a [0, 1] proximity. */\n radiusMeters: number;\n}\n\nexport interface RankOptions {\n /** Per-category multipliers applied to the base score (default 1 each). */\n categoryWeights?: Partial<Record<Category, number>>;\n /** Custom scorer (higher = better); overrides the default proximity scorer. */\n scoreFn?: (input: ScoreInput) => number;\n /** Distance normalization radius; defaults to the farthest POI found. */\n radiusMeters?: number;\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\nfunction defaultScorer(weights?: Partial<Record<Category, number>>): (input: ScoreInput) => number {\n return ({ poi, distanceMeters, radiusMeters }) => {\n const proximity = 1 - clamp01(distanceMeters / radiusMeters);\n const completeness = poi.name ? 0.05 : 0;\n const weight = weights?.[poi.category] ?? 1;\n return (proximity + completeness) * weight;\n };\n}\n\n/**\n * Rank POIs by proximity to `origin`. By default results are ordered nearest\n * first; supplying `categoryWeights` or a custom `scoreFn` switches to\n * highest-score first (ties broken by distance). Each result gains its\n * great-circle `distanceMeters`, a `score` in [0, 1], and a 1-based `rank`.\n */\nexport function rankByProximity(\n origin: LatLng,\n pois: Poi[],\n options: RankOptions = {},\n): RankedPoi[] {\n if (pois.length === 0) return [];\n\n const measured = pois.map((poi) => ({\n poi,\n distanceMeters: haversineMeters(origin, poi.location),\n }));\n const maxDistance = measured.reduce((max, m) => Math.max(max, m.distanceMeters), 0);\n const radiusMeters = options.radiusMeters ?? Math.max(maxDistance, 1);\n const score = options.scoreFn ?? defaultScorer(options.categoryWeights);\n\n const scored = measured.map(({ poi, distanceMeters }) => ({\n poi,\n distanceMeters,\n score: clamp01(score({ poi, distanceMeters, radiusMeters })),\n }));\n\n const byScore = Boolean(options.scoreFn || options.categoryWeights);\n // A stable `id` tie-break makes the order byte-identical across runs — agents\n // can rely on it (per the agent-native output goals).\n scored.sort((a, b) => {\n const primary = byScore\n ? b.score - a.score || a.distanceMeters - b.distanceMeters\n : a.distanceMeters - b.distanceMeters;\n return primary || a.poi.id.localeCompare(b.poi.id);\n });\n\n return scored.map((entry, index) => ({\n ...entry.poi,\n distanceMeters: entry.distanceMeters,\n score: entry.score,\n rank: index + 1,\n }));\n}\n","import { haversineMeters } from './geo';\nimport type { LatLng } from './types';\n\n/** How a route is travelled. Maps to engine-specific profiles per adapter. */\nexport type TravelMode = 'walk' | 'bike' | 'drive';\n\nexport interface RouteMetric {\n /** Travel duration in seconds. */\n seconds: number;\n /** Travel distance in metres. */\n meters: number;\n}\n\n/** A polygon ring as [longitude, latitude] pairs (GeoJSON order). */\nexport type PolygonRing = [number, number][];\n\nexport interface RoutingRequestOptions {\n signal?: AbortSignal;\n}\n\n/**\n * Routing is a commodity (OSRM/Valhalla/ORS self-host it); proximap's value is\n * *composing* it with the amenity layer. This is the seam: a one-to-many matrix\n * and an optional isochrone, with adapters for real engines and a haversine\n * fallback so everything degrades gracefully and works key-free out of the box.\n */\nexport interface RoutingProvider {\n readonly name: string;\n /** Durations/distances from one origin to many targets, aligned with `targets` (null = unreachable). */\n matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options?: RoutingRequestOptions,\n ): Promise<(RouteMetric | null)[]>;\n /** Polygon reachable within `minutes`. Optional — absent ⇒ callers fall back to a matrix threshold. */\n isochrone?(\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n options?: RoutingRequestOptions,\n ): Promise<PolygonRing>;\n}\n\n/** Typical speeds (m/s) for the haversine fallback: ~5, ~15, ~40 km/h. */\nexport const MODE_SPEED_MPS: Record<TravelMode, number> = { walk: 1.4, bike: 4.2, drive: 11.1 };\n\n/**\n * Straight-line routing: distance via haversine, duration via a per-mode speed,\n * isochrone as a circle. The key-free, network-free default — a floor that always\n * works; pass a real {@link RoutingProvider} (Valhalla/OSRM) for road accuracy.\n */\nexport class HaversineRoutingProvider implements RoutingProvider {\n readonly name = 'haversine';\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n ): Promise<RouteMetric[]> {\n const speed = MODE_SPEED_MPS[mode];\n return targets.map((target) => {\n const meters = haversineMeters(origin, target);\n return { meters: Math.round(meters), seconds: Math.round(meters / speed) };\n });\n }\n\n async isochrone(origin: LatLng, minutes: number, mode: TravelMode): Promise<PolygonRing> {\n return circlePolygon(origin, MODE_SPEED_MPS[mode] * minutes * 60);\n }\n}\n\n/** Approximate a circle of `radiusMeters` around `center` as a polygon ring ([lng, lat]). */\nexport function circlePolygon(center: LatLng, radiusMeters: number, steps = 48): PolygonRing {\n const latDelta = radiusMeters / 111_320;\n const lngDelta = radiusMeters / (111_320 * Math.cos((center.lat * Math.PI) / 180));\n const ring: PolygonRing = [];\n for (let i = 0; i <= steps; i++) {\n const angle = (2 * Math.PI * i) / steps;\n ring.push([center.lng + lngDelta * Math.cos(angle), center.lat + latDelta * Math.sin(angle)]);\n }\n return ring;\n}\n\n/** Ray-casting point-in-polygon test; `ring` is [lng, lat] pairs. */\nexport function pointInPolygon(point: LatLng, ring: PolygonRing): boolean {\n const x = point.lng;\n const y = point.lat;\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i]!;\n const b = ring[j]!;\n const intersect =\n a[1] > y !== b[1] > y && x < ((b[0] - a[0]) * (y - a[1])) / (b[1] - a[1]) + a[0];\n if (intersect) inside = !inside;\n }\n return inside;\n}\n","import type { ScoreInput } from './ranking';\n\n/**\n * Composable consumer/accessibility facets that OSM tags carry but most apps\n * never expose as combinable filters: dietary options, cuisine, payment\n * methods, connectivity, seating, and step-free access. Each facet compiles to\n * a predicate over a POI's tags; a POI must satisfy *all* active facets.\n *\n * Sparsity note: a missing tag is treated as \"unknown\", which means the POI is\n * not a positive match — never that it \"fails\". We don't claim a place lacks a\n * feature, only that OSM doesn't record it.\n */\nexport interface FacetFilters {\n /** Dietary options, e.g. \"vegan\", \"vegetarian\", \"halal\" (matches diet:<x>=yes|only). */\n diet?: string | string[];\n /** Cuisine tokens, e.g. \"italian\", \"pizza\" (matches the ;-separated cuisine tag). */\n cuisine?: string | string[];\n /** Accepted payments, e.g. \"contactless\", \"cards\", \"visa\" (matches payment:<x>). */\n payment?: string | string[];\n /** Require internet access (internet_access present and not \"no\"). */\n internetAccess?: boolean;\n /** Require outdoor seating. */\n outdoorSeating?: boolean;\n /** Require takeaway (takeaway=yes|only). */\n takeaway?: boolean;\n /** Require delivery. */\n delivery?: boolean;\n /** Required wheelchair value(s), e.g. \"yes\" or [\"yes\", \"limited\"]. */\n wheelchair?: string | string[];\n /** Raw tag constraints: `true` = present, `false` = absent, string = equals. */\n tags?: Record<string, string | boolean>;\n}\n\n/** A compiled facet check over a POI's tag set. */\nexport type FacetPredicate = (tags: Record<string, string>) => boolean;\n\nconst toArray = (v?: string | string[]): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n/** Values that count as \"yes\" for binary OSM facets where only yes/only apply. */\nconst YES_ONLY = new Set(['yes', 'only']);\nconst NEGATIVE = new Set(['no', 'false', '0', '']);\n\n/** True when a tag is present and not an explicit negative (yes/wlan/customers/…). */\nconst isAffirmative = (value: string | undefined): boolean =>\n value !== undefined && !NEGATIVE.has(value.trim().toLowerCase());\n\nconst cuisineTokens = (tags: Record<string, string>): string[] =>\n (tags.cuisine ?? '')\n .toLowerCase()\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean);\n\n/** Compile a {@link FacetFilters} object into a list of tag predicates (AND-ed). */\nexport function compileFacets(filters: FacetFilters): FacetPredicate[] {\n const preds: FacetPredicate[] = [];\n\n for (const diet of toArray(filters.diet)) {\n const key = `diet:${diet.toLowerCase()}`;\n preds.push((t) => YES_ONLY.has((t[key] ?? '').toLowerCase()));\n }\n for (const cuisine of toArray(filters.cuisine)) {\n const want = cuisine.toLowerCase();\n preds.push((t) => cuisineTokens(t).includes(want));\n }\n for (const payment of toArray(filters.payment)) {\n const key = `payment:${payment.toLowerCase()}`;\n preds.push((t) => isAffirmative(t[key]));\n }\n if (filters.internetAccess) preds.push((t) => isAffirmative(t.internet_access));\n if (filters.outdoorSeating) preds.push((t) => isAffirmative(t.outdoor_seating));\n if (filters.takeaway) preds.push((t) => YES_ONLY.has((t.takeaway ?? '').toLowerCase()));\n if (filters.delivery) preds.push((t) => isAffirmative(t.delivery));\n\n const wheelchair = toArray(filters.wheelchair).map((v) => v.toLowerCase());\n if (wheelchair.length > 0) {\n preds.push((t) => wheelchair.includes((t.wheelchair ?? '').toLowerCase()));\n }\n\n for (const [key, value] of Object.entries(filters.tags ?? {})) {\n if (value === true) preds.push((t) => t[key] !== undefined);\n else if (value === false) preds.push((t) => t[key] === undefined);\n else {\n const want = String(value).toLowerCase();\n preds.push((t) => (t[key] ?? '').toLowerCase() === want);\n }\n }\n return preds;\n}\n\n/** Does a tag set satisfy every compiled facet predicate? */\nexport function matchesFacets(\n tags: Record<string, string>,\n predicates: readonly FacetPredicate[],\n): boolean {\n return predicates.every((predicate) => predicate(tags));\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\n/**\n * A ranking scorer for accessibility-first search: step-free (`wheelchair=yes`)\n * POIs rank above `limited`, which rank above unknown/none — with distance\n * breaking ties *within* each tier. Tiers occupy non-overlapping score bands so\n * an accessible-but-slightly-farther place still outranks a closer inaccessible\n * one, as the use case demands.\n */\nexport function accessibleScorer(): (input: ScoreInput) => number {\n return ({ poi, distanceMeters, radiusMeters }) => {\n const proximity = 1 - clamp01(distanceMeters / radiusMeters);\n const wheelchair = (poi.tags.wheelchair ?? '').toLowerCase();\n const tierBase = wheelchair === 'yes' ? 0.66 : wheelchair === 'limited' ? 0.33 : 0;\n return tierBase + proximity * 0.33;\n };\n}\n","import { parseCoordinates } from './geo';\nimport type { GeocodingProvider, LatLng, Place } from './types';\n\nexport interface ResolveOriginOptions {\n language?: string;\n signal?: AbortSignal;\n}\n\n/**\n * Resolve a place name, a \"lat,lng\" string, or a {@link LatLng} into an origin\n * {@link Place}. Coordinate inputs are enriched with a best-effort reverse\n * geocode when the provider supports it, falling back to the raw coordinates.\n */\nexport async function resolveOrigin(\n query: string | LatLng,\n geocoder: GeocodingProvider,\n options: ResolveOriginOptions = {},\n): Promise<Place> {\n if (typeof query !== 'string') return originFromCoords(query, geocoder, options);\n\n const coords = parseCoordinates(query);\n if (coords) return originFromCoords(coords, geocoder, options);\n\n const matches = await geocoder.geocode(query, { limit: 1, ...geoOptions(options) });\n const first = matches[0];\n if (!first) throw new Error(`No location found for query: \"${query}\"`);\n return first;\n}\n\nasync function originFromCoords(\n coords: LatLng,\n geocoder: GeocodingProvider,\n options: ResolveOriginOptions,\n): Promise<Place> {\n const label = `${coords.lat}, ${coords.lng}`;\n const fallback: Place = {\n name: label,\n displayName: label,\n location: coords,\n source: 'coordinates',\n };\n if (geocoder.reverse) {\n try {\n const reversed = await geocoder.reverse(coords, geoOptions(options));\n if (reversed) return reversed;\n } catch {\n // Reverse geocoding is best-effort; fall back to the raw coordinates.\n }\n }\n return fallback;\n}\n\nfunction geoOptions(options: ResolveOriginOptions): { language?: string; signal?: AbortSignal } {\n const out: { language?: string; signal?: AbortSignal } = {};\n if (options.language) out.language = options.language;\n if (options.signal) out.signal = options.signal;\n return out;\n}\n","import { haversineMeters } from './geo';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport type { GeocodingProvider, Place } from './types';\n\nexport interface DisambiguateOptions {\n geocoder?: GeocodingProvider;\n /** How many candidates to fetch/return (default 5). */\n limit?: number;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface Disambiguation {\n query: string;\n /**\n * True when several plausible, geographically distinct candidates exist (e.g.\n * the ~90 US \"Springfield\"s). Callers should present `candidates` rather than\n * silently trusting `best`, since geocoder relevance is not correctness.\n */\n ambiguous: boolean;\n /** The top-ranked candidate (the geocoder's best guess), or null if none. */\n best: Place | null;\n /** Ranked candidates to disambiguate between. */\n candidates: Place[];\n}\n\n/** Distinct places this far apart (km) with similar names/relevance ⇒ ambiguous. */\nconst DISTINCT_KM = 25;\n/** A rival is \"comparably relevant\" if its importance is ≥ this fraction of the best's. */\nconst RIVAL_IMPORTANCE_RATIO = 0.8;\n\n/**\n * Geocode a query and decide whether it is ambiguous — multiple distinct places\n * a human would need to choose between — instead of silently taking result #1.\n * This is the agent-safety guard against confidently-wrong locations.\n */\nexport async function disambiguateLocation(\n query: string,\n options: DisambiguateOptions = {},\n): Promise<Disambiguation> {\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const limit = options.limit ?? 5;\n const candidates = await geocoder.geocode(query, {\n limit,\n ...(options.language ? { language: options.language } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n const best = candidates[0] ?? null;\n return { query, ambiguous: isAmbiguous(candidates), best, candidates };\n}\n\nconst importanceOf = (place: Place): number => {\n const raw = place.raw as { importance?: number } | undefined;\n return typeof raw?.importance === 'number' ? raw.importance : 0;\n};\n\nconst shortName = (place: Place): string =>\n (place.name || place.displayName.split(',')[0] || '').trim().toLowerCase();\n\nfunction isAmbiguous(candidates: Place[]): boolean {\n if (candidates.length < 2) return false;\n const best = candidates[0]!;\n const bestImportance = importanceOf(best);\n const bestName = shortName(best);\n\n return candidates.slice(1).some((rival) => {\n const farApart = haversineMeters(best.location, rival.location) > DISTINCT_KM * 1000;\n if (!farApart) return false;\n // A genuine rival is either comparably relevant, or shares the same name\n // (two real \"Springfield\"s) — both signal a real choice for the user.\n const comparablyRelevant =\n bestImportance > 0 && importanceOf(rival) >= bestImportance * RIVAL_IMPORTANCE_RATIO;\n const sameName = bestName.length > 0 && shortName(rival) === bestName;\n return comparablyRelevant || sameName;\n });\n}\n","import { haversineMeters } from './geo';\nimport { tagsMatchAnySelector } from './taxonomy';\nimport type { CategorySelector, LatLng, Poi } from './types';\n\n/** The closest POI matching a selector set, with its distance from an origin. */\nexport interface NearestMatch {\n /** Distance to the nearest match in metres, or null if none matched. */\n meters: number | null;\n /** The nearest matching POI, or null if none matched. */\n poi: Poi | null;\n}\n\n/**\n * Find the nearest POI to `origin` whose tags satisfy any of `selectors`.\n * Shared by the gap and walkability features; returns nulls (never throws)\n * when nothing matches, so callers can frame absence honestly.\n */\nexport function nearestMatchingPoi(\n origin: LatLng,\n pois: readonly Poi[],\n selectors: readonly CategorySelector[],\n): NearestMatch {\n let bestMeters: number | null = null;\n let bestPoi: Poi | null = null;\n for (const poi of pois) {\n if (!tagsMatchAnySelector(poi.tags, selectors)) continue;\n const meters = haversineMeters(origin, poi.location);\n if (bestMeters === null || meters < bestMeters) {\n bestMeters = meters;\n bestPoi = poi;\n }\n }\n return { meters: bestMeters, poi: bestPoi };\n}\n","import { CATEGORY_LABELS } from './categories';\nimport { accessibleScorer, compileFacets, matchesFacets, type FacetFilters } from './filters';\nimport { formatDistance, formatDuration, haversineMeters } from './geo';\nimport { isOpenAt, type OpeningEvaluation } from './hours';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { rankByProximity, type RankOptions } from './ranking';\nimport {\n HaversineRoutingProvider,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n CategorySelector,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n RankedPoi,\n} from './types';\n\nexport interface FindNearbyOptions {\n /** Search radius in metres (default 1000). */\n radiusMeters?: number;\n /**\n * Restrict to these categories. Accepts the 16 normalized category names and\n * natural-language terms (\"coffee\", \"pharmacy\", \"petrol\"). Unknown terms throw\n * with suggestions.\n */\n categories?: Array<Category | (string & {})>;\n /** Max results to return after ranking (default 30; <= 0 means no limit). */\n limit?: number;\n /** Preferred language for geocoding results. */\n language?: string;\n /** Override the geocoder (default: Nominatim/OSM). */\n geocoder?: GeocodingProvider;\n /** Override the places provider (default: Overpass/OSM). */\n places?: PlacesProvider;\n /**\n * Composable consumer/accessibility facets (diet, payment, wifi, wheelchair…).\n * A POI must satisfy every active facet; a missing tag means \"not a match\",\n * never asserted as the feature being absent.\n */\n filters?: FacetFilters;\n /**\n * Accessibility-first ranking: step-free POIs rank above `limited`, above\n * unknown/none, with distance breaking ties within each tier. Ignored when a\n * custom `rank.scoreFn` is supplied.\n */\n accessible?: boolean;\n /**\n * Keep only places open at this time and annotate each result with\n * `openState`/`nextChange`. `'now'` uses the current time; `{ at }` takes an\n * ISO string or Date. Places whose hours are unknown are kept and labelled\n * `unknown` (never silently dropped); only confirmed-closed places are\n * removed. Times are read as the POI's local wall-clock (see {@link isOpenAt}).\n */\n open?: 'now' | { at: string | Date };\n /**\n * Order results by straight-line `'distance'` (default) or by `'travelTime'`.\n * Travel-time ranking attaches `travelSeconds`/`travelMeters` to each result.\n */\n rankBy?: 'distance' | 'travelTime';\n /** Travel mode for `rankBy: 'travelTime'` (default `'walk'`). */\n mode?: TravelMode;\n /**\n * Routing engine for travel-time ranking (default: {@link HaversineRoutingProvider},\n * key-free straight-line estimates). Pass a real engine for road-network times;\n * if it errors, ranking falls back to haversine and `result.routing.fellBack` is set.\n */\n routing?: RoutingProvider;\n /** Attach a short `rankingReason` to each result (e.g. \"closest open cafe, 240 m\"). */\n explain?: boolean;\n /** Ranking tweaks (category weights or a custom scorer). */\n rank?: RankOptions;\n signal?: AbortSignal;\n}\n\nexport interface NearbyResult {\n origin: Place;\n results: RankedPoi[];\n /** Number of POIs found before `limit` was applied. */\n total: number;\n /** Set when `rankBy: 'travelTime'` was used: which engine answered, and the mode. */\n routing?: { provider: string; mode: TravelMode; fellBack: boolean };\n}\n\nconst DEFAULT_RADIUS_M = 1000;\nconst DEFAULT_LIMIT = 30;\n\n/**\n * Resolve a place name or coordinate to an origin, find surrounding amenities,\n * and rank them by distance. This is the headline entry point of proximap.\n *\n * @param query A place name/address, a \"lat,lng\" string, or a {@link LatLng}.\n */\nexport async function findNearbyAmenities(\n query: string | LatLng,\n options: FindNearbyOptions = {},\n): Promise<NearbyResult> {\n const radiusMeters = options.radiusMeters ?? DEFAULT_RADIUS_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const selectors = resolveSelectors(options.categories);\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = { radiusMeters };\n if (selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n\n const found = await places.findNearby(origin.location, nearbyOptions);\n let pois =\n selectors.length > 0 ? found.filter((poi) => tagsMatchAnySelector(poi.tags, selectors)) : found;\n\n if (options.filters) {\n const predicates = compileFacets(options.filters);\n if (predicates.length > 0) pois = pois.filter((poi) => matchesFacets(poi.tags, predicates));\n }\n\n // Drop confirmed-closed places (keep open + unknown), remembering each\n // evaluation so it can annotate the ranked results below.\n let openEval: Map<string, OpeningEvaluation> | null = null;\n if (options.open) {\n const when = options.open === 'now' ? new Date() : new Date(options.open.at);\n openEval = new Map();\n pois = pois.filter((poi) => {\n const evaluation = isOpenAt(poi.tags.opening_hours, when);\n openEval!.set(poi.id, evaluation);\n return evaluation.state !== 'closed';\n });\n }\n\n let ranked: RankedPoi[];\n let routingInfo: NearbyResult['routing'];\n if (options.rankBy === 'travelTime') {\n const outcome = await rankByTravelTime(origin.location, pois, {\n mode: options.mode ?? 'walk',\n routing: options.routing ?? new HaversineRoutingProvider(),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n ranked = outcome.ranked;\n routingInfo = outcome.info;\n } else {\n const rankOptions: RankOptions = { radiusMeters, ...options.rank };\n if (options.accessible && !rankOptions.scoreFn) rankOptions.scoreFn = accessibleScorer();\n ranked = rankByProximity(origin.location, pois, rankOptions);\n }\n\n if (openEval) {\n ranked = ranked.map((poi) => {\n const evaluation = openEval!.get(poi.id);\n if (!evaluation) return poi;\n const annotated: RankedPoi = { ...poi, openState: evaluation.state };\n if (evaluation.nextChange) annotated.nextChange = evaluation.nextChange;\n return annotated;\n });\n }\n\n if (options.explain) {\n const mode = options.mode ?? 'walk';\n ranked = ranked.map((poi) => ({ ...poi, rankingReason: rankingReasonFor(poi, mode) }));\n }\n\n const limit = options.limit ?? DEFAULT_LIMIT;\n const results = limit > 0 ? ranked.slice(0, limit) : ranked;\n return {\n origin,\n results,\n total: ranked.length,\n ...(routingInfo ? { routing: routingInfo } : {}),\n };\n}\n\nconst MODE_ADVERB: Record<TravelMode, string> = {\n walk: 'on foot',\n bike: 'by bike',\n drive: 'by car',\n};\n\n/** Ordinal closeness word: 1 → \"closest\", 2 → \"2nd-closest\", … */\nfunction ordinalCloseness(rank: number): string {\n if (rank === 1) return 'closest';\n const mod100 = rank % 100;\n const mod10 = rank % 10;\n const suffix =\n mod10 === 1 && mod100 !== 11\n ? 'st'\n : mod10 === 2 && mod100 !== 12\n ? 'nd'\n : mod10 === 3 && mod100 !== 13\n ? 'rd'\n : 'th';\n return `${rank}${suffix}-closest`;\n}\n\n/** A short, deterministic explanation of a result's rank, e.g. \"closest open cafe, 240 m\". */\nfunction rankingReasonFor(poi: RankedPoi, mode: TravelMode): string {\n const what = poi.kind ?? CATEGORY_LABELS[poi.category].toLowerCase();\n const openness = poi.openState === 'open' ? 'open ' : '';\n if (poi.travelSeconds !== undefined) {\n return `${ordinalCloseness(poi.rank)} ${openness}${what} ${MODE_ADVERB[mode]}, ${formatDuration(poi.travelSeconds)}`;\n }\n return `${ordinalCloseness(poi.rank)} ${openness}${what}, ${formatDistance(poi.distanceMeters)}`;\n}\n\n/** Cap on candidates sent to a routing matrix — bounds cost and public-engine limits. */\nconst TRAVEL_MATRIX_CAP = 80;\n\n/**\n * Rank POIs by travel duration. Trims to the nearest {@link TRAVEL_MATRIX_CAP}\n * candidates by straight-line distance first (to bound matrix size), then orders\n * by the routing engine's durations — falling back to haversine if it errors.\n */\nasync function rankByTravelTime(\n origin: LatLng,\n pois: Poi[],\n options: { mode: TravelMode; routing: RoutingProvider; signal?: AbortSignal },\n): Promise<{ ranked: RankedPoi[]; info: NonNullable<NearbyResult['routing']> }> {\n const candidates = [...pois]\n .sort((a, b) => haversineMeters(origin, a.location) - haversineMeters(origin, b.location))\n .slice(0, TRAVEL_MATRIX_CAP);\n const points = candidates.map((poi) => poi.location);\n const requestOptions = options.signal ? { signal: options.signal } : {};\n\n let metrics: (RouteMetric | null)[];\n let provider = options.routing.name;\n let fellBack = false;\n try {\n metrics = await options.routing.matrix(origin, points, options.mode, requestOptions);\n } catch (error) {\n if (options.routing instanceof HaversineRoutingProvider) throw error;\n const fallback = new HaversineRoutingProvider();\n metrics = await fallback.matrix(origin, points, options.mode);\n provider = fallback.name;\n fellBack = true;\n }\n\n const reachable = candidates\n .map((poi, index) => ({ poi, metric: metrics[index] ?? null }))\n .filter((entry): entry is { poi: Poi; metric: RouteMetric } => entry.metric !== null)\n .sort((a, b) => a.metric.seconds - b.metric.seconds);\n\n const slowest = reachable.reduce((max, entry) => Math.max(max, entry.metric.seconds), 0) || 1;\n const ranked: RankedPoi[] = reachable.map((entry, index) => ({\n ...entry.poi,\n distanceMeters: haversineMeters(origin, entry.poi.location),\n score: Math.round((1 - entry.metric.seconds / slowest) * 100) / 100,\n rank: index + 1,\n travelSeconds: entry.metric.seconds,\n travelMeters: entry.metric.meters,\n }));\n\n return { ranked, info: { provider, mode: options.mode, fellBack } };\n}\n\n/** Resolve requested category terms to selectors, throwing on unknown terms. */\nfunction resolveSelectors(categories: FindNearbyOptions['categories']): CategorySelector[] {\n if (!categories || categories.length === 0) return [];\n const { selectors, unknown } = resolveCategories(categories);\n if (unknown.length > 0) {\n const detail = unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n return selectors;\n}\n","import { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport {\n HaversineRoutingProvider,\n MODE_SPEED_MPS,\n pointInPolygon,\n type PolygonRing,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n RankedPoi,\n} from './types';\n\nexport interface ReachableOptions {\n /** Time budget in minutes. */\n within: number;\n /** Travel mode (default `'walk'`). */\n mode?: TravelMode;\n /** Restrict to these categories/terms (default: all amenities). */\n categories?: Array<Category | (string & {})>;\n /**\n * Routing engine (default {@link HaversineRoutingProvider}). A provider with an\n * `isochrone` method gives a true reachability polygon; otherwise membership is\n * decided by a travel-time matrix threshold.\n */\n routing?: RoutingProvider;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface ReachableResult {\n origin: Place;\n withinMinutes: number;\n mode: TravelMode;\n /** The isochrone polygon used ([lng, lat] ring), or null if none was available. */\n isochrone: PolygonRing | null;\n /** Amenities reachable within the budget, soonest first. */\n results: RankedPoi[];\n count: number;\n}\n\n/** Don't fetch beyond this radius even for long drive budgets (keeps Overpass sane). */\nconst MAX_FETCH_RADIUS_M = 15_000;\nconst REACHABLE_MATRIX_CAP = 100;\n\n/**\n * Return the amenities reachable within a time budget — the *answer*, not just a\n * polygon. With an isochrone-capable engine, membership is the true road-network\n * polygon (point-in-polygon); otherwise it falls back to a travel-time matrix\n * threshold. Results are annotated with travel time and sorted soonest-first.\n */\nexport async function reachableAmenities(\n query: string | LatLng,\n options: ReachableOptions,\n): Promise<ReachableResult> {\n const withinMinutes = options.within;\n if (!(withinMinutes > 0)) {\n throw new Error('reachableAmenities needs a positive `within` (minutes).');\n }\n const mode = options.mode ?? 'walk';\n const routing = options.routing ?? new HaversineRoutingProvider();\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n const budgetSeconds = withinMinutes * 60;\n\n const selectors = resolveSelectors(options.categories);\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n // Straight-line reach is an upper bound on what any route could cover.\n const fetchRadius = Math.min(\n Math.round(MODE_SPEED_MPS[mode] * budgetSeconds),\n MAX_FETCH_RADIUS_M,\n );\n const nearbyOptions: NearbyOptions = { radiusMeters: fetchRadius };\n if (selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n const found = await places.findNearby(origin.location, nearbyOptions);\n const pois =\n selectors.length > 0 ? found.filter((poi) => tagsMatchAnySelector(poi.tags, selectors)) : found;\n\n const isochrone = await tryIsochrone(\n routing,\n origin.location,\n withinMinutes,\n mode,\n options.signal,\n );\n\n // With a polygon, membership is point-in-polygon; otherwise a matrix threshold.\n const candidates = isochrone\n ? pois.filter((poi) => pointInPolygon(poi.location, isochrone))\n : pois;\n const nearest = [...candidates]\n .sort(\n (a, b) =>\n haversineMeters(origin.location, a.location) - haversineMeters(origin.location, b.location),\n )\n .slice(0, REACHABLE_MATRIX_CAP);\n\n const metrics = await safeMatrix(routing, origin.location, nearest, mode, options.signal);\n const members = nearest\n .map((poi, index) => ({ poi, metric: metrics[index] ?? null }))\n .filter((entry) => isochrone !== null || withinBudget(entry.metric, budgetSeconds));\n\n members.sort(\n (a, b) =>\n (a.metric?.seconds ?? Infinity) - (b.metric?.seconds ?? Infinity) ||\n haversineMeters(origin.location, a.poi.location) -\n haversineMeters(origin.location, b.poi.location),\n );\n\n const results: RankedPoi[] = members.map((entry, index) => {\n const ranked: RankedPoi = {\n ...entry.poi,\n distanceMeters: haversineMeters(origin.location, entry.poi.location),\n score: entry.metric ? Math.round((1 - entry.metric.seconds / budgetSeconds) * 100) / 100 : 0,\n rank: index + 1,\n };\n if (entry.metric) {\n ranked.travelSeconds = entry.metric.seconds;\n ranked.travelMeters = entry.metric.meters;\n }\n return ranked;\n });\n\n return { origin, withinMinutes, mode, isochrone, results, count: results.length };\n}\n\nfunction withinBudget(metric: RouteMetric | null, budgetSeconds: number): boolean {\n return metric !== null && metric.seconds <= budgetSeconds;\n}\n\nasync function tryIsochrone(\n routing: RoutingProvider,\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n signal: AbortSignal | undefined,\n): Promise<PolygonRing | null> {\n if (!routing.isochrone) return null;\n try {\n return await routing.isochrone(origin, minutes, mode, signal ? { signal } : {});\n } catch {\n return null; // best-effort; fall back to the matrix-threshold path\n }\n}\n\nasync function safeMatrix(\n routing: RoutingProvider,\n origin: LatLng,\n pois: Poi[],\n mode: TravelMode,\n signal: AbortSignal | undefined,\n): Promise<(RouteMetric | null)[]> {\n if (pois.length === 0) return [];\n const points = pois.map((poi) => poi.location);\n try {\n return await routing.matrix(origin, points, mode, signal ? { signal } : {});\n } catch {\n if (routing instanceof HaversineRoutingProvider) return points.map(() => null);\n return new HaversineRoutingProvider().matrix(origin, points, mode);\n }\n}\n\n/** Resolve category terms to selectors, throwing on unknown terms with suggestions. */\nfunction resolveSelectors(categories: ReachableOptions['categories']) {\n if (!categories || categories.length === 0) return [];\n const { selectors, unknown } = resolveCategories(categories);\n if (unknown.length > 0) {\n const detail = unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n return selectors;\n}\n","import { resolveOrigin } from './origin';\nimport { nearestMatchingPoi } from './proximity';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories } from './taxonomy';\nimport type { GeocodingProvider, LatLng, NearbyOptions, Place, PlacesProvider } from './types';\n\n/** Everyday needs a well-served neighbourhood should provide nearby. */\nexport const DEFAULT_DAILY_NEEDS = [\n 'grocery',\n 'pharmacy',\n 'healthcare',\n 'food',\n 'finance',\n 'transport',\n 'education',\n 'park',\n] as const;\n\nexport interface GapOptions {\n /** Category terms to check (default: {@link DEFAULT_DAILY_NEEDS}). */\n categories?: string[];\n /** How far to look for the nearest instance, in metres (default 5000). */\n searchRadiusMeters?: number;\n /** Distance beyond which a category counts as a gap, in metres (default 1500). */\n thresholdMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface CategoryGap {\n /** The requested category term. */\n category: string;\n /** Distance to the nearest match, or null if none was found within the search radius. */\n nearestMeters: number | null;\n isGap: boolean;\n /** Data-completeness of the nearest match (a confidence hint), when known. */\n nearestCompleteness?: number;\n}\n\nexport interface GapReport {\n origin: Place;\n searchRadiusMeters: number;\n thresholdMeters: number;\n /** Every requested category with its nearest-match status. */\n gaps: CategoryGap[];\n /** Categories flagged as gaps — i.e. not found in OSM within the threshold. */\n missing: string[];\n}\n\nconst DEFAULT_SEARCH_RADIUS_M = 5000;\nconst DEFAULT_THRESHOLD_M = 1500;\n\n/**\n * Report which everyday amenities are missing or far from a location — the\n * inverse of \"what's nearby\". Because OSM under-maps some areas, absence is\n * framed as \"not found in OSM within the threshold\", never asserted as truth;\n * `nearestCompleteness` hints at data confidence.\n */\nexport async function detectGaps(\n query: string | LatLng,\n options: GapOptions = {},\n): Promise<GapReport> {\n const terms =\n options.categories && options.categories.length > 0\n ? options.categories\n : [...DEFAULT_DAILY_NEEDS];\n const searchRadiusMeters = options.searchRadiusMeters ?? DEFAULT_SEARCH_RADIUS_M;\n const thresholdMeters = options.thresholdMeters ?? DEFAULT_THRESHOLD_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const gaps: CategoryGap[] = terms.map((term) => {\n const selectors = resolveCategories([term]).selectors;\n const { meters, poi } = nearestMatchingPoi(origin.location, pois, selectors);\n const gap: CategoryGap = {\n category: term,\n nearestMeters: meters === null ? null : Math.round(meters),\n isGap: meters === null || meters > thresholdMeters,\n };\n if (poi?.completeness !== undefined) gap.nearestCompleteness = poi.completeness;\n return gap;\n });\n\n return {\n origin,\n searchRadiusMeters,\n thresholdMeters,\n gaps,\n missing: gaps.filter((gap) => gap.isGap).map((gap) => gap.category),\n };\n}\n","import { resolveOrigin } from './origin';\nimport { nearestMatchingPoi } from './proximity';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories } from './taxonomy';\nimport type { GeocodingProvider, LatLng, NearbyOptions, Place, PlacesProvider } from './types';\n\n/**\n * A daily-need category and how much it counts toward the walkability score.\n * Weights are relative — the score normalizes by their sum.\n */\nexport interface CategoryWeight {\n /** A natural-language category term (resolved via the taxonomy). */\n term: string;\n weight: number;\n}\n\n/**\n * Default basket of daily needs and weights, loosely modelled on the published\n * Walk Score categories but fully open and tunable. Groceries, food, pharmacy,\n * and transit count most; civic/leisure round out a complete neighbourhood.\n */\nexport const DEFAULT_WALK_CATEGORIES: CategoryWeight[] = [\n { term: 'grocery', weight: 3 },\n { term: 'food', weight: 2 },\n { term: 'pharmacy', weight: 2 },\n { term: 'transport', weight: 2 },\n { term: 'education', weight: 1 },\n { term: 'healthcare', weight: 1 },\n { term: 'finance', weight: 1 },\n { term: 'park', weight: 1 },\n { term: 'shopping', weight: 1 },\n];\n\nexport interface WalkabilityDecay {\n /** At/below this distance (m) a category scores full marks (default 400 ≈ 5-min walk). */\n idealMeters?: number;\n /** At/beyond this distance (m) a category scores zero (default 2400 ≈ 30-min walk). */\n maxMeters?: number;\n}\n\nexport interface WalkabilityOptions {\n /** Daily-need categories and weights (default {@link DEFAULT_WALK_CATEGORIES}). */\n categories?: CategoryWeight[];\n /** Distance-decay tuning (defaults: full credit ≤ 400 m, zero ≥ 2400 m). */\n decay?: WalkabilityDecay;\n /** How far to search for the nearest of each category (default = decay max). */\n searchRadiusMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface CategoryScore {\n category: string;\n weight: number;\n /** Distance to the nearest match, or null if none found within the search radius. */\n nearestMeters: number | null;\n /** Distance-decay sub-score in [0, 1]. */\n subScore: number;\n /** Data-completeness of the nearest match (a confidence hint), when known. */\n nearestCompleteness?: number;\n}\n\nexport interface WalkabilityReport {\n origin: Place;\n /** Overall walkability in [0, 100]; higher is more walkable. */\n score: number;\n /**\n * Confidence in [0, 1] reflecting OSM data density around the origin — low\n * where few categories were found or their tagging is sparse. A low score\n * with low confidence means \"thin data here\", not \"nothing here\".\n */\n confidence: number;\n /** Per-category nearest distance and sub-score. */\n breakdown: CategoryScore[];\n /** Categories with no match found within the search radius. */\n missing: string[];\n /** The distance-decay bounds actually used. */\n decay: { idealMeters: number; maxMeters: number };\n}\n\nconst DEFAULT_IDEAL_M = 400;\nconst DEFAULT_MAX_M = 2400;\n\n/**\n * Distance-decay sub-score: full credit within `ideal`, linearly declining to\n * zero at `max`. Deliberately simple and transparent so the score is auditable\n * and tunable, unlike opaque proprietary indices.\n */\nexport function walkSubScore(\n meters: number | null,\n idealMeters: number,\n maxMeters: number,\n): number {\n if (meters === null) return 0;\n if (meters <= idealMeters) return 1;\n if (meters >= maxMeters) return 0;\n return round2((maxMeters - meters) / (maxMeters - idealMeters));\n}\n\nconst round2 = (n: number): number => Math.round(n * 100) / 100;\nconst mean = (xs: number[]): number => (xs.length ? xs.reduce((s, x) => s + x, 0) / xs.length : 0);\n\n/**\n * Score how walkable / well-served a location is: a 0–100 number plus a full\n * per-category breakdown, the categories that are missing, and a data-confidence\n * note. An open, transparent, tunable, OSM-native alternative to proprietary\n * walkability indices — every input is visible and adjustable.\n */\nexport async function walkabilityScore(\n query: string | LatLng,\n options: WalkabilityOptions = {},\n): Promise<WalkabilityReport> {\n const categories =\n options.categories && options.categories.length > 0\n ? options.categories\n : DEFAULT_WALK_CATEGORIES;\n const idealMeters = options.decay?.idealMeters ?? DEFAULT_IDEAL_M;\n const maxMeters = options.decay?.maxMeters ?? DEFAULT_MAX_M;\n const searchRadiusMeters = options.searchRadiusMeters ?? maxMeters;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const terms = categories.map((c) => c.term);\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const breakdown: CategoryScore[] = categories.map(({ term, weight }) => {\n const selectors = resolveCategories([term]).selectors;\n const { meters, poi } = nearestMatchingPoi(origin.location, pois, selectors);\n const nearestMeters = meters === null ? null : Math.round(meters);\n const entry: CategoryScore = {\n category: term,\n weight,\n nearestMeters,\n subScore: walkSubScore(meters, idealMeters, maxMeters),\n };\n if (poi?.completeness !== undefined) entry.nearestCompleteness = poi.completeness;\n return entry;\n });\n\n const totalWeight = categories.reduce((sum, c) => sum + c.weight, 0) || 1;\n const weighted = breakdown.reduce((sum, b) => sum + b.weight * b.subScore, 0);\n const score = Math.round((100 * weighted) / totalWeight);\n\n const found = breakdown.filter((b) => b.nearestMeters !== null);\n const coverage = breakdown.length ? found.length / breakdown.length : 0;\n const avgCompleteness = mean(found.map((b) => b.nearestCompleteness ?? 0));\n const confidence = round2(0.7 * coverage + 0.3 * avgCompleteness);\n\n return {\n origin,\n score,\n confidence,\n breakdown,\n missing: breakdown.filter((b) => b.nearestMeters === null).map((b) => b.category),\n decay: { idealMeters, maxMeters },\n };\n}\n","import { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport type { GeocodingProvider, LatLng, Place, PlacesProvider } from './types';\nimport {\n DEFAULT_WALK_CATEGORIES,\n walkabilityScore,\n type CategoryScore,\n type CategoryWeight,\n type WalkabilityDecay,\n} from './walkability';\n\nexport interface CompareOptions {\n /** Dimensions and weights to compare on (default: the walkability basket). */\n categories?: CategoryWeight[];\n /** Distance-decay tuning, passed through to each location's scoring. */\n decay?: WalkabilityDecay;\n /** How far to search around each location (default = decay max). */\n searchRadiusMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface LocationScore {\n origin: Place;\n /** Walkability score (0–100) under the comparison weights. */\n score: number;\n confidence: number;\n breakdown: CategoryScore[];\n missing: string[];\n}\n\nexport interface DimensionWinner {\n category: string;\n weight: number;\n /** Index into `locations` of the best location for this dimension, or null if none has it. */\n bestIndex: number | null;\n}\n\nexport interface RankedLocation {\n /** Index into `locations` (input order). */\n index: number;\n score: number;\n origin: Place;\n}\n\nexport interface ComparisonReport {\n /** Each location's score, in input order. */\n locations: LocationScore[];\n /** Locations sorted best-first (ties: higher confidence, then input order). */\n ranked: RankedLocation[];\n /** The top-ranked location, or null if no candidates were given. */\n best: RankedLocation | null;\n /** For each dimension, which location is best served. */\n dimensions: DimensionWinner[];\n /** The weights actually used. */\n weights: CategoryWeight[];\n}\n\n/**\n * Compare N candidate locations across weighted daily-need dimensions and rank\n * them — a key-free, arbitrary-N, OSM-native relocation/siting scorecard. Pure\n * composition over {@link walkabilityScore}: each location is scored the same\n * way, then ranked and compared dimension-by-dimension.\n *\n * Out of scope by design (not in OSM): transit *frequency*, school quality,\n * crime, prices. We compare amenity *access*, and carry through walkability's\n * confidence so thin data reads as low confidence, not a confident zero.\n */\nexport async function compareLocations(\n queries: ReadonlyArray<string | LatLng>,\n options: CompareOptions = {},\n): Promise<ComparisonReport> {\n if (queries.length < 2) {\n throw new Error('compareLocations needs at least two locations to compare.');\n }\n const categories =\n options.categories && options.categories.length > 0\n ? options.categories\n : DEFAULT_WALK_CATEGORIES;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n // Sequential, with shared providers, so the OSM rate limiters stay polite.\n const locations: LocationScore[] = [];\n for (const query of queries) {\n const walk = await walkabilityScore(query, {\n categories,\n geocoder,\n places,\n ...(options.decay ? { decay: options.decay } : {}),\n ...(options.searchRadiusMeters ? { searchRadiusMeters: options.searchRadiusMeters } : {}),\n ...(options.language ? { language: options.language } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n locations.push({\n origin: walk.origin,\n score: walk.score,\n confidence: walk.confidence,\n breakdown: walk.breakdown,\n missing: walk.missing,\n });\n }\n\n const ranked: RankedLocation[] = locations\n .map((location, index) => ({ index, location }))\n .sort(\n (a, b) =>\n b.location.score - a.location.score ||\n b.location.confidence - a.location.confidence ||\n a.index - b.index,\n )\n .map(({ index, location }) => ({ index, score: location.score, origin: location.origin }));\n\n const dimensions: DimensionWinner[] = categories.map(({ term, weight }) => ({\n category: term,\n weight,\n bestIndex: bestForDimension(locations, term),\n }));\n\n return {\n locations,\n ranked,\n best: ranked[0] ?? null,\n dimensions,\n weights: categories,\n };\n}\n\n/** The index of the location best served for a category: highest sub-score, nearest on ties. */\nfunction bestForDimension(locations: readonly LocationScore[], term: string): number | null {\n let bestIndex: number | null = null;\n let bestSub = -1;\n let bestMeters = Infinity;\n locations.forEach((location, index) => {\n const entry = location.breakdown.find((b) => b.category === term);\n if (!entry || entry.nearestMeters === null) return;\n if (\n entry.subScore > bestSub ||\n (entry.subScore === bestSub && entry.nearestMeters < bestMeters)\n ) {\n bestSub = entry.subScore;\n bestMeters = entry.nearestMeters;\n bestIndex = index;\n }\n });\n return bestIndex;\n}\n","import { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport {\n HaversineRoutingProvider,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n} from './types';\n\nexport interface ErrandOptions {\n /** Categories/terms to hit one of each, e.g. ['pharmacy', 'atm', 'grocery']. */\n categories: Array<Category | (string & {})>;\n /** Travel mode for the cost matrix (default `'walk'`). */\n mode?: TravelMode;\n /** Optional fixed end point (a place name, \"lat,lng\", or coordinate). */\n end?: string | LatLng;\n /** Nearest candidates considered per category (default 5). */\n candidatesPerCategory?: number;\n /** How far to look for candidates, in metres (default 3000). */\n searchRadiusMeters?: number;\n /**\n * Cost engine for the matrix (default {@link HaversineRoutingProvider} — an\n * honest, instant, key-free straight-line MVP). Pass a real engine for road\n * times; note it issues one matrix request per point.\n */\n routing?: RoutingProvider;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface ErrandStop {\n category: string;\n poi: Poi;\n /** Leg from the previous point (origin or prior stop) to this stop. */\n legSeconds: number;\n legMeters: number;\n}\n\nexport interface ErrandPlan {\n origin: Place;\n end: Place | null;\n mode: TravelMode;\n /** Chosen places in visit order. */\n stops: ErrandStop[];\n totalSeconds: number;\n totalMeters: number;\n /** Requested categories with no candidate nearby — skipped, not faked. */\n missing: string[];\n candidatesPerCategory: number;\n}\n\nconst DEFAULT_CANDIDATES = 5;\nconst DEFAULT_SEARCH_RADIUS_M = 3000;\n/** Upper bound on distinct categories for the exact DP (2^C states). */\nconst MAX_CATEGORIES = 12;\n\n/**\n * Plan the shortest trip that buys/visits one of each requested category near an\n * origin — the **Generalized TSP** (\"pick one per set, then optimize\") that no\n * consumer app ships. Fetches the nearest candidates per category, builds a cost\n * matrix, and solves exactly via a grouped Held-Karp DP (instant at consumer\n * scale). Categories with no candidate are reported as `missing`, never faked.\n */\nexport async function planErrands(\n query: string | LatLng,\n options: ErrandOptions,\n): Promise<ErrandPlan> {\n const terms = options.categories ?? [];\n if (terms.length === 0) throw new Error('planErrands needs at least one category.');\n if (terms.length > MAX_CATEGORIES) {\n throw new Error(\n `planErrands supports up to ${MAX_CATEGORIES} categories (got ${terms.length}).`,\n );\n }\n const mode = options.mode ?? 'walk';\n const perCategory = options.candidatesPerCategory ?? DEFAULT_CANDIDATES;\n const searchRadiusMeters = options.searchRadiusMeters ?? DEFAULT_SEARCH_RADIUS_M;\n const routing = options.routing ?? new HaversineRoutingProvider();\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n const end = options.end !== undefined ? await resolveEnd(options.end, geocoder, options) : null;\n\n // One fetch over the union, then take the nearest K per category.\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const missing: string[] = [];\n const groups: { term: string; candidates: Poi[] }[] = [];\n for (const term of terms) {\n const selectors = resolveCategories([term]).selectors;\n const candidates = pois\n .filter((poi) => tagsMatchAnySelector(poi.tags, selectors))\n .sort(\n (a, b) =>\n haversineMeters(origin.location, a.location) -\n haversineMeters(origin.location, b.location),\n )\n .slice(0, perCategory);\n if (candidates.length === 0) missing.push(String(term));\n else groups.push({ term: String(term), candidates });\n }\n\n if (groups.length === 0) {\n return {\n origin,\n end,\n mode,\n stops: [],\n totalSeconds: 0,\n totalMeters: 0,\n missing,\n candidatesPerCategory: perCategory,\n };\n }\n\n const solution = await solveGeneralizedTsp(\n origin.location,\n groups,\n end?.location ?? null,\n mode,\n routing,\n );\n return {\n origin,\n end,\n mode,\n stops: solution.stops,\n totalSeconds: solution.totalSeconds,\n totalMeters: solution.totalMeters,\n missing,\n candidatesPerCategory: perCategory,\n };\n}\n\ninterface FlatNode {\n group: number;\n term: string;\n poi: Poi;\n}\n\n/** Exact grouped Held-Karp: visit one node per group, origin-anchored, optional fixed end. */\nasync function solveGeneralizedTsp(\n origin: LatLng,\n groups: { term: string; candidates: Poi[] }[],\n end: LatLng | null,\n mode: TravelMode,\n routing: RoutingProvider,\n): Promise<{ stops: ErrandStop[]; totalSeconds: number; totalMeters: number }> {\n const nodes: FlatNode[] = [];\n groups.forEach((group, groupIndex) => {\n for (const poi of group.candidates) nodes.push({ group: groupIndex, term: group.term, poi });\n });\n\n // points: 0 = origin, 1..N = nodes, optional last = end.\n const points: LatLng[] = [origin, ...nodes.map((node) => node.poi.location)];\n const endPointIndex = end ? points.push(end) - 1 : -1;\n const matrix = await buildCostMatrix(points, mode, routing);\n const seconds = (from: number, to: number): number => matrix[from]![to]!.seconds;\n\n const groupCount = groups.length;\n const nodeCount = nodes.length;\n const full = (1 << groupCount) - 1;\n const cost: number[][] = Array.from({ length: 1 << groupCount }, () =>\n new Array(nodeCount).fill(Infinity),\n );\n const parent: number[][] = Array.from({ length: 1 << groupCount }, () =>\n new Array(nodeCount).fill(-1),\n );\n\n for (let n = 0; n < nodeCount; n++) {\n cost[1 << nodes[n]!.group]![n] = seconds(0, n + 1);\n }\n for (let mask = 1; mask <= full; mask++) {\n const row = cost[mask]!;\n for (let n = 0; n < nodeCount; n++) {\n const here = row[n]!;\n if (here === Infinity || !(mask & (1 << nodes[n]!.group))) continue;\n for (let m = 0; m < nodeCount; m++) {\n const groupBit = 1 << nodes[m]!.group;\n if (mask & groupBit) continue; // that group already visited\n const nextMask = mask | groupBit;\n const candidate = here + seconds(n + 1, m + 1);\n if (candidate < cost[nextMask]![m]!) {\n cost[nextMask]![m] = candidate;\n parent[nextMask]![m] = n;\n }\n }\n }\n }\n\n let best = Infinity;\n let lastNode = -1;\n for (let n = 0; n < nodeCount; n++) {\n let total = cost[full]![n]!;\n if (total === Infinity) continue;\n if (endPointIndex >= 0) total += seconds(n + 1, endPointIndex);\n if (total < best) {\n best = total;\n lastNode = n;\n }\n }\n\n // Reconstruct the visit order.\n const order: number[] = [];\n let mask = full;\n let node = lastNode;\n while (node !== -1) {\n order.push(node);\n const previous = parent[mask]![node]!;\n mask &= ~(1 << nodes[node]!.group);\n node = previous;\n }\n order.reverse();\n\n const stops: ErrandStop[] = [];\n let previousPoint = 0; // origin\n let totalMeters = 0;\n for (const n of order) {\n const leg = matrix[previousPoint]![n + 1]!;\n totalMeters += leg.meters;\n stops.push({\n category: nodes[n]!.term,\n poi: nodes[n]!.poi,\n legSeconds: leg.seconds,\n legMeters: leg.meters,\n });\n previousPoint = n + 1;\n }\n if (endPointIndex >= 0) totalMeters += matrix[previousPoint]![endPointIndex]!.meters;\n\n return { stops, totalSeconds: best === Infinity ? 0 : best, totalMeters };\n}\n\n/** Build a full point-to-point cost matrix (one matrix request per source point). */\nasync function buildCostMatrix(\n points: LatLng[],\n mode: TravelMode,\n routing: RoutingProvider,\n): Promise<RouteMetric[][]> {\n const rows: RouteMetric[][] = [];\n for (const source of points) {\n const row = await routing.matrix(source, points, mode);\n rows.push(row.map((metric) => metric ?? { seconds: Infinity, meters: Infinity }));\n }\n return rows;\n}\n\nasync function resolveEnd(\n end: string | LatLng,\n geocoder: GeocodingProvider,\n options: ErrandOptions,\n): Promise<Place> {\n return resolveOrigin(end, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n}\n","import type { NearbyResult } from './nearby';\nimport type { RankedPoi } from './types';\n\n/**\n * ODbL attribution for exported OpenStreetMap data. OSM's licence lets you store\n * and redistribute results (unlike the commercial APIs) **provided you keep this\n * notice** — so exporters emit it and callers should keep it with the data.\n */\nexport const ODBL_ATTRIBUTION =\n '© OpenStreetMap contributors, ODbL (https://www.openstreetmap.org/copyright)';\n\nexport interface GeoJsonFeature {\n type: 'Feature';\n /** GeoJSON uses [longitude, latitude] order (RFC 7946). */\n geometry: { type: 'Point'; coordinates: [number, number] };\n properties: Record<string, unknown>;\n}\n\nexport interface GeoJsonFeatureCollection {\n type: 'FeatureCollection';\n /** ODbL attribution, per {@link ODBL_ATTRIBUTION}. */\n attribution: string;\n features: GeoJsonFeature[];\n}\n\n/** Serialize a nearby-search result to a GeoJSON FeatureCollection (one Point per POI). */\nexport function toGeoJSON(result: NearbyResult): GeoJsonFeatureCollection {\n return {\n type: 'FeatureCollection',\n attribution: ODBL_ATTRIBUTION,\n features: result.results.map(poiToFeature),\n };\n}\n\nfunction poiToFeature(poi: RankedPoi): GeoJsonFeature {\n const properties: Record<string, unknown> = {\n rank: poi.rank,\n name: poi.name ?? null,\n category: poi.category,\n kind: poi.kind ?? null,\n distanceMeters: Math.round(poi.distanceMeters),\n osmId: poi.id,\n };\n if (poi.completeness !== undefined) properties.completeness = poi.completeness;\n if (poi.lastVerified) properties.lastVerified = poi.lastVerified;\n if (poi.openState) properties.openState = poi.openState;\n if (poi.nextChange) properties.nextChange = poi.nextChange;\n return {\n type: 'Feature',\n geometry: { type: 'Point', coordinates: [poi.location.lng, poi.location.lat] },\n properties,\n };\n}\n\nconst CSV_COLUMNS = [\n 'rank',\n 'name',\n 'category',\n 'kind',\n 'distance_m',\n 'lat',\n 'lng',\n 'osm_id',\n 'completeness',\n 'last_verified',\n 'open_state',\n] as const;\n\n/** Serialize a nearby-search result to RFC 4180 CSV (header row + one row per POI). */\nexport function toCSV(result: NearbyResult): string {\n const rows = [CSV_COLUMNS.join(',')];\n for (const poi of result.results) {\n rows.push(\n [\n poi.rank,\n csvField(poi.name ?? ''),\n poi.category,\n csvField(poi.kind ?? ''),\n Math.round(poi.distanceMeters),\n poi.location.lat,\n poi.location.lng,\n poi.id,\n poi.completeness ?? '',\n poi.lastVerified ?? '',\n poi.openState ?? '',\n ].join(','),\n );\n }\n return rows.join('\\n');\n}\n\n/** Quote a CSV field when it contains a comma, quote, or newline (doubling quotes). */\nfunction csvField(value: string): string {\n return /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n}\n","import { ODBL_ATTRIBUTION } from './export';\nimport { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n PlacesProvider,\n Poi,\n} from './types';\n\n/**\n * A stored snapshot of an area's POIs. OSM data (ODbL) may be freely stored and\n * redistributed — the advantage commercial APIs forbid — so this is a portable,\n * offline-queryable dataset. Keep the `attribution` with the data.\n */\nexport interface SnapshotDataset {\n attribution: string;\n /** ISO timestamp the snapshot was captured. */\n createdAt: string;\n /** Center the snapshot was taken around. */\n center: LatLng;\n /** Radius captured, in metres. */\n radiusMeters: number;\n /** Normalized, deduplicated POIs in the area. */\n pois: Poi[];\n}\n\nexport interface SnapshotOptions {\n /** Radius to capture, in metres (default 2000). */\n radiusMeters?: number;\n /** Restrict the capture to these categories/terms (default: all amenities). */\n categories?: Array<Category | (string & {})>;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n /** Override the capture timestamp (ISO) — for deterministic output/tests. */\n createdAt?: string;\n}\n\nconst DEFAULT_SNAPSHOT_RADIUS_M = 2000;\n\n/**\n * Capture an area's POIs into a {@link SnapshotDataset} for offline reuse. Pair\n * with {@link DatasetPlacesProvider} to answer queries with no network calls.\n */\nexport async function snapshotArea(\n query: string | LatLng,\n options: SnapshotOptions = {},\n): Promise<SnapshotDataset> {\n const radiusMeters = options.radiusMeters ?? DEFAULT_SNAPSHOT_RADIUS_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n let selectors;\n if (options.categories && options.categories.length > 0) {\n const resolved = resolveCategories(options.categories);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n selectors = resolved.selectors;\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = { radiusMeters };\n if (selectors && selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n return {\n attribution: ODBL_ATTRIBUTION,\n createdAt: options.createdAt ?? new Date().toISOString(),\n center: origin.location,\n radiusMeters,\n pois,\n };\n}\n\n/**\n * A {@link PlacesProvider} backed by a stored {@link SnapshotDataset} — answers\n * nearby queries entirely from memory, with no network. Use a \"lat,lng\" query\n * (no geocoding) for a fully offline pipeline.\n */\nexport class DatasetPlacesProvider implements PlacesProvider {\n readonly name = 'dataset';\n private readonly pois: Poi[];\n\n constructor(dataset: SnapshotDataset) {\n this.pois = dataset.pois;\n }\n\n async findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]> {\n const { radiusMeters, selectors } = options;\n return this.pois.filter((poi) => {\n if (haversineMeters(center, poi.location) > radiusMeters) return false;\n if (selectors && selectors.length > 0) return tagsMatchAnySelector(poi.tags, selectors);\n return true;\n });\n }\n}\n","/**\n * @proximap/core — geospatial engine for places, proximity, and amenities.\n *\n * Defaults to OpenStreetMap (Nominatim + Overpass) with no API keys, but every\n * stage is pluggable via the provider interfaces in {@link ./types}.\n */\n\n/** Library version, kept in sync with package.json. */\nexport const VERSION = '1.0.0';\n\nexport * from './types';\nexport * from './geo';\nexport * from './categories';\nexport * from './taxonomy';\nexport * from './quality';\nexport * from './hours';\nexport * from './http';\nexport * from './providers';\nexport * from './ranking';\nexport * from './routing';\nexport * from './filters';\nexport * from './origin';\nexport * from './disambiguate';\nexport * from './proximity';\nexport * from './nearby';\nexport * from './reachable';\nexport * from './gaps';\nexport * from './walkability';\nexport * from './compare';\nexport * from './errands';\nexport * from './export';\nexport * from './snapshot';\n"],"mappings":"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","names":["a","shop","tourism","leisure","a","present","a","a","a","a","clamp01","a","resolveSelectors","a","a","DEFAULT_SEARCH_RADIUS_M","a","node","mask"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/geo.ts","../src/categories.ts","../src/taxonomy.ts","../src/quality.ts","../src/hours.ts","../src/http.ts","../src/providers/nominatim.ts","../src/providers/overpass.ts","../src/providers/valhalla.ts","../src/providers/osrm.ts","../src/ranking.ts","../src/routing.ts","../src/filters.ts","../src/origin.ts","../src/disambiguate.ts","../src/proximity.ts","../src/nearby.ts","../src/reachable.ts","../src/gaps.ts","../src/walkability.ts","../src/compare.ts","../src/errands.ts","../src/export.ts","../src/snapshot.ts","../src/index.ts"],"sourcesContent":["/**\n * Domain model for proximap. These types are provider-agnostic: OpenStreetMap\n * is the default backend, but anything implementing {@link GeocodingProvider}\n * and {@link PlacesProvider} can drive the same pipeline.\n */\n\n/** A WGS84 latitude/longitude coordinate, in decimal degrees. */\nexport interface LatLng {\n lat: number;\n lng: number;\n}\n\n/**\n * Normalized, top-level categories every POI is bucketed into. This is the\n * single source of truth — {@link Category} is derived from it.\n */\nexport const CATEGORIES = [\n 'food',\n 'grocery',\n 'shopping',\n 'healthcare',\n 'education',\n 'finance',\n 'transport',\n 'fuel',\n 'parking',\n 'accommodation',\n 'leisure',\n 'tourism',\n 'worship',\n 'public_service',\n 'utility',\n 'other',\n] as const;\n\n/** A normalized amenity/utility category. */\nexport type Category = (typeof CATEGORIES)[number];\n\n/**\n * A single OSM tag selector, e.g. `{ key: 'amenity', value: 'cafe' }` or\n * `{ key: 'cuisine', value: 'coffee_shop', regex: true }`. An omitted `value`\n * matches the mere presence of the key.\n */\nexport interface CategorySelector {\n key: string;\n value?: string;\n /** Treat `value` as an Overpass regular expression (the `~` operator). */\n regex?: boolean;\n}\n\n/** A geocoded location — the resolved origin of a search. */\nexport interface Place {\n /** Best-effort short name, e.g. \"Eiffel Tower\". */\n name: string;\n /** Full human-readable label from the geocoder. */\n displayName: string;\n location: LatLng;\n /** Geocoder-provided class/type, e.g. \"tourism\" or \"city\". */\n kind?: string;\n /** Bounding box as [south, north, west, east], if provided. */\n boundingBox?: [number, number, number, number];\n /** Identifier of the provider that produced this result, e.g. \"nominatim\". */\n source: string;\n /** Raw provider payload, for advanced consumers. */\n raw?: unknown;\n}\n\n/** A point of interest: an amenity, utility, shop, or similar feature. */\nexport interface Poi {\n /** Stable identifier, e.g. \"node/123\" or \"way/456\". */\n id: string;\n name?: string;\n category: Category;\n /** The specific source value behind the category, e.g. \"restaurant\". */\n kind?: string;\n location: LatLng;\n /** Original source tags/attributes (e.g. OSM tags). */\n tags: Record<string, string>;\n source: string;\n /** Share of expected tags present for this category, in [0, 1]. */\n completeness?: number;\n /** Best-effort last-verified date (YYYY-MM-DD) from check_date/survey/meta. */\n lastVerified?: string;\n}\n\n/** Whether a place is open at a given time: known states or \"not enough data\". */\nexport type OpenState = 'open' | 'closed' | 'unknown';\n\n/** A {@link Poi} enriched with distance from the search origin and a rank. */\nexport interface RankedPoi extends Poi {\n /** Great-circle distance from the origin, in metres. */\n distanceMeters: number;\n /** Composite score in [0, 1]; higher is better. */\n score: number;\n /** 1-based position after ranking. */\n rank: number;\n /** Open/closed/unknown at the queried time — set only when `open` was requested. */\n openState?: OpenState;\n /** ISO 8601 timestamp of the next open/closed transition, when computable. */\n nextChange?: string;\n /** Travel duration from the origin in seconds — set only when ranking by travel time. */\n travelSeconds?: number;\n /** Travel distance from the origin in metres — set only when ranking by travel time. */\n travelMeters?: number;\n /** A short human-readable reason for this rank — set only when `explain` is on. */\n rankingReason?: string;\n}\n\n/** Options for a geocoding lookup. */\nexport interface GeocodeOptions {\n /** Maximum number of candidates to return. */\n limit?: number;\n /** Preferred result language, e.g. \"en\". */\n language?: string;\n signal?: AbortSignal;\n}\n\n/** Resolves place names/addresses to coordinates (and optionally back). */\nexport interface GeocodingProvider {\n readonly name: string;\n geocode(query: string, options?: GeocodeOptions): Promise<Place[]>;\n reverse?(location: LatLng, options?: GeocodeOptions): Promise<Place | null>;\n}\n\n/** Options for a nearby-places search. */\nexport interface NearbyOptions {\n /** Search radius from the center, in metres. */\n radiusMeters: number;\n /** Restrict results to these normalized categories (post-classification). */\n categories?: Category[];\n /**\n * Tag selectors that drive a targeted query (and define what to keep). When\n * set, the provider should fetch only matching features instead of the broad\n * default set. Takes precedence over `categories` for query construction.\n */\n selectors?: CategorySelector[];\n /** Upper bound on POIs fetched from the provider. */\n limit?: number;\n signal?: AbortSignal;\n}\n\n/** Finds points of interest around a coordinate. */\nexport interface PlacesProvider {\n readonly name: string;\n findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]>;\n}\n","import type { LatLng } from './types';\n\n/** Mean Earth radius (IUGG), in metres. */\nconst EARTH_RADIUS_M = 6_371_008.8;\n\nconst toRadians = (degrees: number): number => (degrees * Math.PI) / 180;\n\n/**\n * Great-circle distance between two coordinates, in metres, via the haversine\n * formula. Accurate to within ~0.5% — more than enough to rank nearby places.\n */\nexport function haversineMeters(a: LatLng, b: LatLng): number {\n const dLat = toRadians(b.lat - a.lat);\n const dLng = toRadians(b.lng - a.lng);\n const lat1 = toRadians(a.lat);\n const lat2 = toRadians(b.lat);\n\n const sinLat = Math.sin(dLat / 2);\n const sinLng = Math.sin(dLng / 2);\n const h = sinLat * sinLat + Math.cos(lat1) * Math.cos(lat2) * sinLng * sinLng;\n return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h)));\n}\n\n/** Format a metre distance as a compact human string (\"125 m\", \"1.4 km\"). */\nexport function formatDistance(meters: number): string {\n if (!Number.isFinite(meters) || meters < 0) return '—';\n // Branch on the rounded value so e.g. 999.5 m renders as \"1.0 km\", not \"1000 m\".\n if (Math.round(meters) < 1000) return `${Math.round(meters)} m`;\n const km = meters / 1000;\n return `${km < 10 ? km.toFixed(1) : Math.round(km)} km`;\n}\n\n/** Format a second duration as a compact human string (\"8 min\", \"1 h 5 min\"). */\nexport function formatDuration(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '—';\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes} min`;\n const hours = Math.floor(minutes / 60);\n const remainder = minutes % 60;\n return remainder === 0 ? `${hours} h` : `${hours} h ${remainder} min`;\n}\n\n/**\n * Parse a \"lat,lng\" string (decimal degrees) into a {@link LatLng}, or return\n * null when the input is not a valid, in-range coordinate pair.\n */\nexport function parseCoordinates(input: string): LatLng | null {\n const match = input.trim().match(/^(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)$/);\n if (!match) return null;\n const lat = Number(match[1]);\n const lng = Number(match[2]);\n if (Number.isNaN(lat) || Number.isNaN(lng)) return null;\n if (lat < -90 || lat > 90 || lng < -180 || lng > 180) return null;\n return { lat, lng };\n}\n","import { CATEGORIES, type Category } from './types';\n\n/**\n * OSM `amenity=*` values grouped by normalized category. Kept as the readable\n * source for the flattened lookup built below.\n */\nconst AMENITY_GROUPS = {\n food: ['restaurant', 'cafe', 'fast_food', 'bar', 'pub', 'food_court', 'biergarten', 'ice_cream'],\n healthcare: [\n 'hospital',\n 'clinic',\n 'doctors',\n 'dentist',\n 'pharmacy',\n 'veterinary',\n 'nursing_home',\n ],\n education: ['school', 'college', 'university', 'kindergarten', 'library', 'language_school'],\n finance: ['bank', 'atm', 'bureau_de_change'],\n fuel: ['fuel', 'charging_station'],\n parking: ['parking', 'bicycle_parking', 'motorcycle_parking', 'parking_entrance'],\n transport: [\n 'bus_station',\n 'taxi',\n 'ferry_terminal',\n 'car_rental',\n 'bicycle_rental',\n 'car_sharing',\n ],\n worship: ['place_of_worship'],\n public_service: [\n 'police',\n 'fire_station',\n 'post_office',\n 'townhall',\n 'courthouse',\n 'community_centre',\n ],\n leisure: ['cinema', 'theatre', 'nightclub', 'arts_centre'],\n shopping: ['marketplace'],\n utility: [\n 'toilets',\n 'drinking_water',\n 'shower',\n 'recycling',\n 'waste_disposal',\n 'post_box',\n 'telephone',\n 'fountain',\n 'shelter',\n ],\n} satisfies Partial<Record<Category, string[]>>;\n\nconst AMENITY_TO_CATEGORY = new Map<string, Category>();\nfor (const [category, values] of Object.entries(AMENITY_GROUPS)) {\n for (const value of values) AMENITY_TO_CATEGORY.set(value, category as Category);\n}\n\n/** `shop=*` values that are really about groceries rather than retail. */\nconst GROCERY_SHOPS = new Set([\n 'supermarket',\n 'convenience',\n 'greengrocer',\n 'bakery',\n 'butcher',\n 'general',\n 'deli',\n 'farm',\n 'dairy',\n 'health_food',\n]);\n\n/** `tourism=*` values that represent places to stay. */\nconst LODGING_TOURISM = new Set([\n 'hotel',\n 'hostel',\n 'guest_house',\n 'motel',\n 'apartment',\n 'chalet',\n 'camp_site',\n 'caravan_site',\n]);\n\n/** `railway=*` values that are passenger access points. */\nconst TRANSPORT_RAILWAY = new Set(['station', 'halt', 'tram_stop', 'subway_entrance', 'stop']);\n\nexport interface Categorization {\n category: Category;\n /** The specific source value that drove the classification, if any. */\n kind?: string;\n}\n\n/**\n * Classify a set of OSM tags into a normalized {@link Category}. Keys are\n * checked in priority order; a present-but-unknown key falls back to 'other'\n * while preserving its value as `kind`.\n */\nexport function categorize(tags: Record<string, string>): Categorization {\n const {\n amenity,\n shop,\n tourism,\n leisure,\n healthcare,\n railway,\n public_transport,\n highway,\n aeroway,\n office,\n } = tags;\n\n if (amenity) {\n const category = AMENITY_TO_CATEGORY.get(amenity);\n return category ? { category, kind: amenity } : { category: 'other', kind: amenity };\n }\n if (shop) return { category: GROCERY_SHOPS.has(shop) ? 'grocery' : 'shopping', kind: shop };\n if (tourism)\n return { category: LODGING_TOURISM.has(tourism) ? 'accommodation' : 'tourism', kind: tourism };\n if (healthcare) return { category: 'healthcare', kind: healthcare };\n if (leisure) return { category: 'leisure', kind: leisure };\n if (railway && TRANSPORT_RAILWAY.has(railway)) return { category: 'transport', kind: railway };\n if (public_transport) return { category: 'transport', kind: public_transport };\n if (highway === 'bus_stop') return { category: 'transport', kind: 'bus_stop' };\n if (aeroway) return { category: 'transport', kind: aeroway };\n if (office)\n return {\n category: office === 'government' ? 'public_service' : 'other',\n kind: `office:${office}`,\n };\n return { category: 'other' };\n}\n\n/** Human-friendly display labels for each category. */\nexport const CATEGORY_LABELS: Record<Category, string> = {\n food: 'Food & drink',\n grocery: 'Groceries',\n shopping: 'Shopping',\n healthcare: 'Healthcare',\n education: 'Education',\n finance: 'Money',\n transport: 'Transport',\n fuel: 'Fuel & charging',\n parking: 'Parking',\n accommodation: 'Accommodation',\n leisure: 'Leisure',\n tourism: 'Tourism',\n worship: 'Places of worship',\n public_service: 'Public services',\n utility: 'Utilities',\n other: 'Other',\n};\n\n/** Type guard: is `value` one of the known {@link Category} names? */\nexport function isCategory(value: string): value is Category {\n return (CATEGORIES as readonly string[]).includes(value);\n}\n","import type { Category, CategorySelector } from './types';\n\n/**\n * Maps the words people (and agents) actually use — \"coffee\", \"chemist\",\n * \"petrol\" — to the OSM tag selectors that find them, and rolls each up to one\n * of the 16 normalized {@link Category} buckets. This is the query vocabulary;\n * `categorize()` is the inverse (tags -> category) used when reading results.\n */\ninterface TermDefinition {\n /** Canonical term. */\n term: string;\n category: Category;\n selectors: CategorySelector[];\n /** Alternative spellings/phrasings that resolve to this term. */\n synonyms?: string[];\n}\n\nconst a = (value: string): CategorySelector => ({ key: 'amenity', value });\nconst shop = (value: string): CategorySelector => ({ key: 'shop', value });\nconst leisure = (value: string): CategorySelector => ({ key: 'leisure', value });\nconst tourism = (value: string): CategorySelector => ({ key: 'tourism', value });\nconst rx = (key: string, value: string): CategorySelector => ({ key, value, regex: true });\nconst present = (key: string): CategorySelector => ({ key });\n\nconst TERMS: TermDefinition[] = [\n // food\n {\n term: 'food',\n category: 'food',\n selectors: [a('restaurant'), a('cafe'), a('fast_food'), a('bar'), a('pub'), a('food_court')],\n synonyms: ['food and drink', 'places to eat', 'eat', 'dining', 'somewhere to eat'],\n },\n { term: 'restaurant', category: 'food', selectors: [a('restaurant')], synonyms: ['restaurants'] },\n { term: 'cafe', category: 'food', selectors: [a('cafe')], synonyms: ['café', 'cafes'] },\n {\n term: 'coffee',\n category: 'food',\n selectors: [a('cafe'), rx('cuisine', 'coffee_shop'), shop('coffee')],\n synonyms: ['coffee shop', 'coffeehouse'],\n },\n {\n term: 'fast food',\n category: 'food',\n selectors: [a('fast_food')],\n synonyms: ['fastfood', 'takeaway food'],\n },\n { term: 'bar', category: 'food', selectors: [a('bar')], synonyms: ['bars'] },\n { term: 'pub', category: 'food', selectors: [a('pub')], synonyms: ['pubs'] },\n { term: 'pizza', category: 'food', selectors: [rx('cuisine', 'pizza')], synonyms: ['pizzeria'] },\n { term: 'ice cream', category: 'food', selectors: [a('ice_cream')], synonyms: ['gelato'] },\n\n // grocery\n {\n term: 'grocery',\n category: 'grocery',\n selectors: [shop('supermarket'), shop('convenience'), shop('greengrocer'), shop('grocery')],\n synonyms: ['groceries', 'grocery store', 'food shopping'],\n },\n { term: 'supermarket', category: 'grocery', selectors: [shop('supermarket')] },\n {\n term: 'convenience',\n category: 'grocery',\n selectors: [shop('convenience')],\n synonyms: ['convenience store', 'corner shop'],\n },\n { term: 'bakery', category: 'grocery', selectors: [shop('bakery')], synonyms: ['baker'] },\n\n // shopping\n {\n term: 'shopping',\n category: 'shopping',\n selectors: [present('shop')],\n synonyms: ['shops', 'stores', 'retail'],\n },\n {\n term: 'mall',\n category: 'shopping',\n selectors: [shop('mall'), shop('department_store')],\n synonyms: ['shopping mall', 'shopping centre', 'shopping center'],\n },\n {\n term: 'clothes',\n category: 'shopping',\n selectors: [shop('clothes')],\n synonyms: ['clothing', 'fashion'],\n },\n\n // healthcare\n {\n term: 'healthcare',\n category: 'healthcare',\n selectors: [\n a('hospital'),\n a('clinic'),\n a('doctors'),\n a('dentist'),\n a('pharmacy'),\n present('healthcare'),\n ],\n synonyms: ['health', 'medical', 'health care'],\n },\n {\n term: 'pharmacy',\n category: 'healthcare',\n selectors: [a('pharmacy'), rx('healthcare', 'pharmacy')],\n synonyms: ['chemist', 'drugstore', 'drug store'],\n },\n {\n term: 'hospital',\n category: 'healthcare',\n selectors: [a('hospital')],\n synonyms: ['hospitals', 'emergency room', 'a&e'],\n },\n { term: 'clinic', category: 'healthcare', selectors: [a('clinic'), rx('healthcare', 'clinic')] },\n {\n term: 'doctor',\n category: 'healthcare',\n selectors: [a('doctors'), rx('healthcare', 'doctor')],\n synonyms: ['doctors', 'gp', 'physician'],\n },\n {\n term: 'dentist',\n category: 'healthcare',\n selectors: [a('dentist'), rx('healthcare', 'dentist')],\n synonyms: ['dental'],\n },\n\n // education\n {\n term: 'education',\n category: 'education',\n selectors: [a('school'), a('college'), a('university'), a('kindergarten'), a('library')],\n synonyms: ['schools'],\n },\n { term: 'school', category: 'education', selectors: [a('school')] },\n {\n term: 'university',\n category: 'education',\n selectors: [a('university'), a('college')],\n synonyms: ['college', 'uni'],\n },\n { term: 'library', category: 'education', selectors: [a('library')], synonyms: ['libraries'] },\n\n // finance\n {\n term: 'finance',\n category: 'finance',\n selectors: [a('bank'), a('atm'), a('bureau_de_change')],\n synonyms: ['money', 'banking'],\n },\n {\n term: 'atm',\n category: 'finance',\n selectors: [a('atm')],\n synonyms: ['cash machine', 'cashpoint'],\n },\n { term: 'bank', category: 'finance', selectors: [a('bank')], synonyms: ['banks'] },\n\n // transport\n {\n term: 'transport',\n category: 'transport',\n selectors: [\n a('bus_station'),\n { key: 'public_transport', value: 'station' },\n rx('railway', 'station|halt|tram_stop|subway_entrance|stop'),\n { key: 'highway', value: 'bus_stop' },\n { key: 'aeroway', value: 'aerodrome' },\n ],\n synonyms: ['public transport', 'transit'],\n },\n {\n term: 'bus stop',\n category: 'transport',\n selectors: [{ key: 'highway', value: 'bus_stop' }, a('bus_station')],\n synonyms: ['bus', 'bus station'],\n },\n {\n term: 'train station',\n category: 'transport',\n selectors: [rx('railway', 'station|halt'), { key: 'public_transport', value: 'station' }],\n synonyms: ['railway station', 'train', 'metro', 'metro station', 'subway', 'subway station'],\n },\n { term: 'taxi', category: 'transport', selectors: [a('taxi')] },\n {\n term: 'airport',\n category: 'transport',\n selectors: [{ key: 'aeroway', value: 'aerodrome' }],\n synonyms: ['airports'],\n },\n\n // fuel\n {\n term: 'fuel',\n category: 'fuel',\n selectors: [a('fuel'), a('charging_station')],\n synonyms: ['petrol', 'gas', 'gas station', 'petrol station', 'filling station'],\n },\n {\n term: 'ev charging',\n category: 'fuel',\n selectors: [a('charging_station')],\n synonyms: ['charging station', 'ev charger', 'charger'],\n },\n\n // parking\n {\n term: 'parking',\n category: 'parking',\n selectors: [a('parking')],\n synonyms: ['car park', 'parking lot'],\n },\n {\n term: 'bicycle parking',\n category: 'parking',\n selectors: [a('bicycle_parking')],\n synonyms: ['bike parking'],\n },\n\n // accommodation\n {\n term: 'accommodation',\n category: 'accommodation',\n selectors: [tourism('hotel'), tourism('hostel'), tourism('guest_house'), tourism('motel')],\n synonyms: ['lodging', 'places to stay', 'stay'],\n },\n { term: 'hotel', category: 'accommodation', selectors: [tourism('hotel')], synonyms: ['hotels'] },\n { term: 'hostel', category: 'accommodation', selectors: [tourism('hostel')] },\n\n // leisure\n {\n term: 'leisure',\n category: 'leisure',\n selectors: [present('leisure')],\n synonyms: ['recreation'],\n },\n { term: 'park', category: 'leisure', selectors: [leisure('park')], synonyms: ['parks'] },\n {\n term: 'gym',\n category: 'leisure',\n selectors: [leisure('fitness_centre'), leisure('sports_centre')],\n synonyms: ['fitness', 'fitness centre', 'gymnasium'],\n },\n {\n term: 'cinema',\n category: 'leisure',\n selectors: [a('cinema')],\n synonyms: ['movie theater', 'movie theatre', 'movies'],\n },\n { term: 'playground', category: 'leisure', selectors: [leisure('playground')] },\n\n // tourism\n {\n term: 'tourism',\n category: 'tourism',\n selectors: [present('tourism')],\n synonyms: ['attractions', 'sights', 'things to do'],\n },\n { term: 'museum', category: 'tourism', selectors: [tourism('museum')], synonyms: ['museums'] },\n { term: 'viewpoint', category: 'tourism', selectors: [tourism('viewpoint')] },\n\n // worship\n {\n term: 'worship',\n category: 'worship',\n selectors: [a('place_of_worship')],\n synonyms: ['place of worship', 'church', 'mosque', 'temple', 'synagogue'],\n },\n\n // public_service\n {\n term: 'public_service',\n category: 'public_service',\n selectors: [a('police'), a('fire_station'), a('post_office'), a('townhall')],\n synonyms: ['public services', 'government', 'civic'],\n },\n {\n term: 'police',\n category: 'public_service',\n selectors: [a('police')],\n synonyms: ['police station'],\n },\n {\n term: 'post office',\n category: 'public_service',\n selectors: [a('post_office')],\n synonyms: ['postal'],\n },\n { term: 'fire station', category: 'public_service', selectors: [a('fire_station')] },\n\n // utility\n {\n term: 'utility',\n category: 'utility',\n selectors: [a('toilets'), a('drinking_water'), a('recycling'), a('post_box')],\n synonyms: ['utilities'],\n },\n {\n term: 'toilets',\n category: 'utility',\n selectors: [a('toilets')],\n synonyms: ['toilet', 'restroom', 'restrooms', 'wc', 'public toilet', 'bathroom'],\n },\n {\n term: 'drinking water',\n category: 'utility',\n selectors: [a('drinking_water')],\n synonyms: ['water fountain', 'water point'],\n },\n\n // other (catch-all; not directly queryable)\n { term: 'other', category: 'other', selectors: [] },\n];\n\nconst normalize = (term: string): string => term.trim().toLowerCase().replace(/\\s+/g, ' ');\n\nconst TERM_INDEX = new Map<string, TermDefinition>();\nfor (const def of TERMS) {\n TERM_INDEX.set(normalize(def.term), def);\n for (const synonym of def.synonyms ?? []) TERM_INDEX.set(normalize(synonym), def);\n}\n\nconst selectorKey = (s: CategorySelector): string => `${s.key}|${s.value ?? ''}|${s.regex ? 1 : 0}`;\n\nexport interface ResolvedCategories {\n /** Deduplicated selectors for all recognized terms. */\n selectors: CategorySelector[];\n /** Recognized terms with their canonical name and category. */\n matched: { input: string; term: string; category: Category }[];\n /** Terms that could not be resolved. */\n unknown: string[];\n}\n\n/** Resolve a list of natural-language terms (or category names) to selectors. */\nexport function resolveCategories(terms: readonly string[]): ResolvedCategories {\n const selectors: CategorySelector[] = [];\n const seen = new Set<string>();\n const matched: ResolvedCategories['matched'] = [];\n const unknown: string[] = [];\n\n for (const input of terms) {\n const def = TERM_INDEX.get(normalize(input));\n if (!def) {\n unknown.push(input);\n continue;\n }\n matched.push({ input, term: def.term, category: def.category });\n for (const selector of def.selectors) {\n const key = selectorKey(selector);\n if (!seen.has(key)) {\n seen.add(key);\n selectors.push(selector);\n }\n }\n }\n return { selectors, matched, unknown };\n}\n\n/** Is `term` a known category or synonym? */\nexport function isKnownTerm(term: string): boolean {\n return TERM_INDEX.has(normalize(term));\n}\n\n/** All canonical query terms with their top-level category. */\nexport function categoryVocabulary(): { term: string; category: Category }[] {\n return TERMS.map(({ term, category }) => ({ term, category }));\n}\n\nfunction editDistance(a: string, b: string): number {\n const m = a.length;\n const n = b.length;\n const row = Array.from({ length: n + 1 }, (_, i) => i);\n for (let i = 1; i <= m; i++) {\n let prev = row[0]!;\n row[0] = i;\n for (let j = 1; j <= n; j++) {\n const temp = row[j]!;\n row[j] = Math.min(row[j]! + 1, row[j - 1]! + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));\n prev = temp;\n }\n }\n return row[n]!;\n}\n\n/** Suggest known terms for an unrecognized input (typos, partial matches). */\nexport function suggestCategories(term: string, limit = 5): string[] {\n const query = normalize(term);\n const scored: { term: string; score: number }[] = [];\n for (const def of TERMS) {\n const candidates = [def.term, ...(def.synonyms ?? [])].map(normalize);\n let best = Infinity;\n for (const candidate of candidates) {\n if (candidate.includes(query) || query.includes(candidate)) best = Math.min(best, 0);\n else best = Math.min(best, editDistance(query, candidate));\n }\n if (best <= 2) scored.push({ term: def.term, score: best });\n }\n scored.sort((x, y) => x.score - y.score || x.term.localeCompare(y.term));\n return scored.slice(0, limit).map((s) => s.term);\n}\n\n/** Render a selector as an Overpass tag filter, e.g. `[\"amenity\"=\"cafe\"]`. */\nexport function selectorToOverpassFilter(selector: CategorySelector): string {\n if (selector.value === undefined) return `[\"${selector.key}\"]`;\n const op = selector.regex ? '~' : '=';\n return `[\"${selector.key}\"${op}\"${selector.value}\"]`;\n}\n\n/** Does a tag set satisfy a single selector? */\nexport function tagsMatchSelector(\n tags: Record<string, string>,\n selector: CategorySelector,\n): boolean {\n const value = tags[selector.key];\n if (value === undefined) return false;\n if (selector.value === undefined) return true;\n return selector.regex ? new RegExp(selector.value).test(value) : value === selector.value;\n}\n\n/** Does a tag set satisfy any of the selectors? */\nexport function tagsMatchAnySelector(\n tags: Record<string, string>,\n selectors: readonly CategorySelector[],\n): boolean {\n return selectors.some((selector) => tagsMatchSelector(tags, selector));\n}\n","import { haversineMeters } from './geo';\nimport type { Category, Poi } from './types';\n\n/**\n * Tags a well-described POI of each category tends to carry. Used to compute a\n * coarse completeness score — a trust signal that lets gap/score features treat\n * sparse OSM data honestly (low completeness, not a confident \"no\").\n */\nconst BASE_EXPECTED = ['name'];\nconst EXPECTED_BY_CATEGORY: Partial<Record<Category, string[]>> = {\n food: ['name', 'opening_hours', 'cuisine', 'website', 'phone', 'wheelchair'],\n grocery: ['name', 'opening_hours', 'website', 'phone'],\n shopping: ['name', 'opening_hours', 'website', 'phone'],\n healthcare: ['name', 'opening_hours', 'phone', 'website', 'wheelchair'],\n education: ['name', 'website', 'phone'],\n finance: ['name', 'opening_hours', 'operator'],\n transport: ['name', 'network', 'operator'],\n fuel: ['name', 'opening_hours', 'operator'],\n parking: ['capacity', 'fee', 'access'],\n accommodation: ['name', 'website', 'phone', 'stars'],\n leisure: ['name', 'opening_hours'],\n tourism: ['name', 'website', 'opening_hours'],\n worship: ['name', 'religion'],\n public_service: ['name', 'opening_hours', 'phone'],\n utility: ['fee', 'access'],\n other: ['name'],\n};\n\n/** Fraction of a category's expected tags that are present, in [0, 1]. */\nexport function completenessOf(category: Category, tags: Record<string, string>): number {\n const expected = EXPECTED_BY_CATEGORY[category] ?? BASE_EXPECTED;\n if (expected.length === 0) return 1;\n const present = expected.filter((key) => {\n const value = tags[key];\n return value !== undefined && value.trim() !== '';\n }).length;\n return Math.round((present / expected.length) * 100) / 100;\n}\n\nconst DATE_TAGS = ['check_date', 'check_date:opening_hours', 'survey:date'];\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}/;\n\n/**\n * Best-effort \"last verified\" date (YYYY-MM-DD): an explicit survey/check_date\n * tag if present, else the element's last-edit timestamp.\n */\nexport function lastVerifiedOf(\n tags: Record<string, string>,\n timestamp?: string,\n): string | undefined {\n for (const key of DATE_TAGS) {\n const value = tags[key];\n if (value && ISO_DATE.test(value)) return value.slice(0, 10);\n }\n if (timestamp && ISO_DATE.test(timestamp)) return timestamp.slice(0, 10);\n return undefined;\n}\n\nconst normalizeName = (poi: Poi): string => (poi.name ?? '').trim().toLowerCase();\n\n/** Heuristic: do two POIs describe the same real-world feature? Conservative. */\nfunction isDuplicate(a: Poi, b: Poi): boolean {\n if (a.category !== b.category) return false;\n const distance = haversineMeters(a.location, b.location);\n if (distance > 40) return false;\n\n const an = normalizeName(a);\n const bn = normalizeName(b);\n if (an && bn) return an === bn; // both named: names must agree\n // at least one unnamed: only merge near-coincident features of the same kind\n return distance <= 15 && a.kind === b.kind;\n}\n\n/** Higher = a better representative to keep when collapsing duplicates. */\nfunction richness(poi: Poi): number {\n let score = 0;\n if (poi.name) score += 100;\n score += (poi.completeness ?? 0) * 10;\n if (poi.id.startsWith('way/') || poi.id.startsWith('relation/')) score += 1;\n return score;\n}\n\n/**\n * Collapse duplicate representations of the same POI (e.g. a node and a building\n * way) into one, keeping the richer entry. Order of first appearance is kept.\n */\nexport function dedupePois(pois: Poi[]): Poi[] {\n const kept: Poi[] = [];\n for (const poi of pois) {\n const index = kept.findIndex((other) => isDuplicate(other, poi));\n if (index === -1) kept.push(poi);\n else if (richness(poi) > richness(kept[index]!)) kept[index] = poi;\n }\n return kept;\n}\n","import type { OpenState } from './types';\n\nexport interface OpeningEvaluation {\n state: OpenState;\n /** ISO 8601 timestamp of the next open/closed transition, when computable. */\n nextChange?: string;\n}\n\n/**\n * A small, dependency-free evaluator for the common subset of the OSM\n * `opening_hours` grammar: weekday ranges/lists, multiple time ranges,\n * overnight (midnight-wrapping) ranges, `24/7`, and `off`/`closed`. Public- and\n * school-holiday rules (`PH`/`SH`) are skipped (no holiday calendar), so a\n * normal day still evaluates from the regular rules.\n *\n * Anything outside this subset — `sunrise`/`sunset`, month/date/week selectors,\n * open-ended `08:00+`, etc. — yields `unknown` rather than a guess. A missing or\n * empty value is `unknown` too. We never assert a state we can't justify.\n *\n * Times are read from the local-time fields of `when`; to evaluate a POI in\n * another timezone, pass a `when` already shifted into that zone.\n */\nexport function isOpenAt(openingHours: string | undefined, when: Date): OpeningEvaluation {\n const raw = (openingHours ?? '').trim();\n if (!raw) return { state: 'unknown' };\n\n const lower = raw.toLowerCase();\n if (/^24\\s*\\/\\s*7$/.test(lower) || lower === '24/7') return { state: 'open' };\n if (lower === 'off' || lower === 'closed') return { state: 'closed' };\n if (hasUnsupported(lower)) return { state: 'unknown' };\n\n const segments = buildSegments(lower);\n if (!segments) return { state: 'unknown' };\n\n const day = when.getDay();\n const minute = when.getHours() * 60 + when.getMinutes();\n const open = isOpenInSegments(segments, day, minute);\n\n const evaluation: OpeningEvaluation = { state: open ? 'open' : 'closed' };\n const next = nextChange(segments, when, open);\n if (next) evaluation.nextChange = next;\n return evaluation;\n}\n\n// getDay(): 0 = Sunday … 6 = Saturday. OSM weeks run Mo…Su.\nconst DAY_INDEX: Record<string, number> = { su: 0, mo: 1, tu: 2, we: 3, th: 4, fr: 5, sa: 6 };\nconst WEEK_ORDER = ['mo', 'tu', 'we', 'th', 'fr', 'sa', 'su'];\nconst MONTHS = /\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\b/;\n\n/** Grammar we deliberately don't evaluate; presence ⇒ `unknown` (never a guess). */\nfunction hasUnsupported(s: string): boolean {\n return (\n /sunrise|sunset|dawn|dusk|easter|\\bweek\\b/.test(s) ||\n /\\+/.test(s) || // open-ended times like 08:00+\n /\\b\\d{4}\\b/.test(s) || // explicit years\n MONTHS.test(s)\n );\n}\n\ntype Interval = [number, number]; // [start, end) in minutes within a day\ntype Segments = Interval[][]; // [dayIndex 0..6] -> that day's open intervals\n\nfunction buildSegments(input: string): Segments | null {\n // Each day's own intervals, last matching rule wins (OSM `;` override).\n const own: (Interval[] | undefined)[] = new Array(7).fill(undefined);\n\n let applied = false;\n for (const rulePart of input.split(';')) {\n const rule = rulePart.trim();\n if (!rule) continue;\n const parsed = parseRule(rule.replace(/\\s*([,:\\-])\\s*/g, '$1'));\n if (parsed === 'unknown') return null;\n if (parsed === null) continue; // holiday-only rule: skip\n for (const day of parsed.days) own[day] = parsed.intervals;\n applied = true;\n }\n\n // Only holiday rules (or nothing) contributed a schedule: there is no regular\n // schedule to justify open/closed, so report `unknown` rather than `closed`.\n if (!applied) return null;\n\n // Expand into per-day segments, spilling overnight ranges into the next day.\n const segments: Segments = Array.from({ length: 7 }, () => [] as Interval[]);\n for (let day = 0; day < 7; day++) {\n const intervals = own[day];\n if (!intervals) continue;\n for (const [start, end] of intervals) {\n if (start === end)\n segments[day]!.push([0, 1440]); // full day (e.g. 00:00-00:00)\n else if (end > start) segments[day]!.push([start, end]);\n else {\n segments[day]!.push([start, 1440]); // head: until midnight\n segments[(day + 1) % 7]!.push([0, end]); // tail: early next day\n }\n }\n }\n return segments;\n}\n\ntype ParsedRule = { days: number[]; intervals: Interval[] };\n\nfunction parseRule(rule: string): ParsedRule | 'unknown' | null {\n let dayPart: string;\n let timePart: string;\n const space = rule.indexOf(' ');\n if (space === -1) {\n if (/\\d/.test(rule)) {\n dayPart = '';\n timePart = rule;\n } else if (rule === 'off' || rule === 'closed') {\n dayPart = '';\n timePart = rule;\n } else {\n dayPart = rule;\n timePart = '';\n }\n } else {\n dayPart = rule.slice(0, space);\n timePart = rule.slice(space + 1).trim();\n }\n\n const days = parseDays(dayPart);\n if (days === 'unknown') return 'unknown';\n if (days === null) return null;\n\n if (timePart === '') return { days, intervals: [[0, 1440]] as Interval[] };\n if (timePart === 'off' || timePart === 'closed') return { days, intervals: [] };\n\n const intervals = parseTimes(timePart);\n if (!intervals) return 'unknown';\n return { days, intervals };\n}\n\nfunction parseDays(part: string): number[] | 'unknown' | null {\n if (part === '') return [0, 1, 2, 3, 4, 5, 6];\n\n const days = new Set<number>();\n let sawReal = false;\n let sawHoliday = false;\n for (const token of part.split(',')) {\n if (token === 'ph' || token === 'sh') {\n sawHoliday = true;\n continue;\n }\n const match = token.match(/^([a-z]{2})(?:-([a-z]{2}))?$/);\n if (!match) return 'unknown';\n const from = match[1]!;\n if (!(from in DAY_INDEX)) return 'unknown';\n if (match[2] === undefined) {\n days.add(DAY_INDEX[from]!);\n sawReal = true;\n } else {\n const to = match[2];\n if (!(to in DAY_INDEX)) return 'unknown';\n addWeekdayRange(days, from, to);\n sawReal = true;\n }\n }\n if (!sawReal) return sawHoliday ? null : 'unknown';\n return [...days];\n}\n\n/** Add an OSM weekday range (Mo…Su order, wrapping, e.g. Fr-Mo). */\nfunction addWeekdayRange(set: Set<number>, from: string, to: string): void {\n let i = WEEK_ORDER.indexOf(from);\n const end = WEEK_ORDER.indexOf(to);\n for (;;) {\n set.add(DAY_INDEX[WEEK_ORDER[i]!]!);\n if (i === end) break;\n i = (i + 1) % 7;\n }\n}\n\nfunction parseTimes(part: string): Interval[] | null {\n const intervals: Interval[] = [];\n for (const piece of part.split(',')) {\n const match = piece.match(/^(\\d{1,2}):(\\d{2})-(\\d{1,2}):(\\d{2})$/);\n if (!match) return null;\n const h1 = Number(match[1]);\n const m1 = Number(match[2]);\n const h2 = Number(match[3]);\n const m2 = Number(match[4]);\n if (h1 > 24 || h2 > 24 || m1 > 59 || m2 > 59) return null;\n intervals.push([h1 * 60 + m1, h2 * 60 + m2]);\n }\n return intervals;\n}\n\nfunction isOpenInSegments(segments: Segments, day: number, minute: number): boolean {\n return segments[day]!.some(([start, end]) => minute >= start && minute < end);\n}\n\n/**\n * The next instant the open/closed state flips, searching up to 8 days ahead.\n * Returns undefined when nothing changes in that window (e.g. always closed).\n */\nfunction nextChange(segments: Segments, when: Date, currentlyOpen: boolean): string | undefined {\n const nowMinute = when.getHours() * 60 + when.getMinutes();\n const baseDay = when.getDay();\n\n const candidates: number[] = [];\n for (let offset = 0; offset <= 8; offset++) {\n const day = (baseDay + offset) % 7;\n for (const [start, end] of segments[day]!) {\n candidates.push(offset * 1440 + start, offset * 1440 + end);\n }\n }\n\n candidates.sort((a, b) => a - b);\n for (const candidate of candidates) {\n if (candidate <= nowMinute) continue;\n const day = (baseDay + Math.floor(candidate / 1440)) % 7;\n const open = isOpenInSegments(segments, day, candidate % 1440);\n if (open !== currentlyOpen) {\n const midnight = new Date(when.getFullYear(), when.getMonth(), when.getDate(), 0, 0, 0, 0);\n return new Date(midnight.getTime() + candidate * 60_000).toISOString();\n }\n }\n return undefined;\n}\n","/**\n * Minimal JSON-over-HTTP helper built on the platform `fetch`. Keeps the core\n * dependency-free while handling timeouts, cancellation, retries with backoff,\n * optional caching, and consistent error surfacing across providers.\n */\n\n/** Default contact identifier sent to OSM services; override per provider. */\nexport const DEFAULT_USER_AGENT = 'proximap/0.1 (+https://github.com/AmeyaBorkar/proximap)';\n\n/** A pluggable response cache. Values are parsed JSON. */\nexport interface RequestCache {\n get(key: string): Promise<unknown> | unknown;\n set(key: string, value: unknown): Promise<void> | void;\n}\n\n/** A process-local, unbounded cache. Opt-in — pass it to a provider to enable. */\nexport class InMemoryCache implements RequestCache {\n private readonly store = new Map<string, unknown>();\n get(key: string): unknown {\n return this.store.get(key);\n }\n set(key: string, value: unknown): void {\n this.store.set(key, value);\n }\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Serializes calls so that consecutive `acquire()`s are spaced at least\n * `minIntervalMs` apart — for honouring usage policies (e.g. Nominatim's 1 req/s).\n */\nexport class RateLimiter {\n private last = 0;\n private chain: Promise<void> = Promise.resolve();\n\n constructor(private readonly minIntervalMs: number) {}\n\n acquire(): Promise<void> {\n this.chain = this.chain.then(async () => {\n if (this.minIntervalMs <= 0) return;\n const wait = this.last + this.minIntervalMs - Date.now();\n if (wait > 0) await sleep(wait);\n this.last = Date.now();\n });\n return this.chain;\n }\n}\n\nexport interface RequestOptions {\n method?: 'GET' | 'POST';\n headers?: Record<string, string>;\n body?: string;\n /** External cancellation signal; merged with the internal timeout. */\n signal?: AbortSignal;\n /** Abort the request after this many milliseconds (default 20000). */\n timeoutMs?: number;\n /** Retry attempts on transient failures (429/5xx/timeout/network). Default 0. */\n retries?: number;\n /** Base backoff delay in ms, doubled each attempt (default 500). */\n retryDelayMs?: number;\n /** Optional response cache, keyed by method + url + body. */\n cache?: RequestCache;\n}\n\n/** Thrown when a request times out or returns a non-2xx response. */\nexport class HttpError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n message: string,\n ) {\n super(message);\n this.name = 'HttpError';\n }\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof HttpError) {\n return error.status === 0 || error.status === 429 || error.status >= 500;\n }\n if (error instanceof Error && error.name === 'AbortError') return false;\n return true; // treat unexpected network errors as transient\n}\n\nasync function fetchJsonOnce<T>(url: string, options: RequestOptions): Promise<T> {\n const { method = 'GET', headers = {}, body, signal, timeoutMs = 20_000 } = options;\n const timeout = AbortSignal.timeout(timeoutMs);\n const composite = signal ? AbortSignal.any([signal, timeout]) : timeout;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: { 'User-Agent': DEFAULT_USER_AGENT, Accept: 'application/json', ...headers },\n ...(body === undefined ? {} : { body }),\n signal: composite,\n });\n } catch (error) {\n if (timeout.aborted) throw new HttpError(0, url, `Request timed out after ${timeoutMs} ms`);\n throw error;\n }\n\n if (!response.ok) {\n const detail = await response.text().catch(() => '');\n throw new HttpError(\n response.status,\n url,\n `${response.status} ${response.statusText}: ${detail.slice(0, 200)}`.trim(),\n );\n }\n return (await response.json()) as T;\n}\n\n/** Perform an HTTP request and parse the JSON body as `T`, with retry + cache. */\nexport async function requestJson<T>(url: string, options: RequestOptions = {}): Promise<T> {\n const { method = 'GET', body, retries = 0, retryDelayMs = 500, cache } = options;\n const cacheKey = cache ? `${method} ${url} ${body ?? ''}` : undefined;\n\n if (cache && cacheKey !== undefined) {\n const cached = await cache.get(cacheKey);\n if (cached !== undefined) return cached as T;\n }\n\n let lastError: unknown;\n for (let attempt = 0; attempt <= retries; attempt++) {\n if (attempt > 0) await sleep(retryDelayMs * 2 ** (attempt - 1));\n try {\n const value = await fetchJsonOnce<T>(url, options);\n if (cache && cacheKey !== undefined) await cache.set(cacheKey, value);\n return value;\n } catch (error) {\n lastError = error;\n if (attempt === retries || !isRetryable(error)) throw error;\n }\n }\n throw lastError;\n}\n","import { DEFAULT_USER_AGENT, RateLimiter, requestJson, type RequestCache } from '../http';\nimport type { GeocodeOptions, GeocodingProvider, LatLng, Place } from '../types';\n\nexport interface NominatimOptions {\n /** Base URL of the Nominatim instance (no trailing slash required). */\n endpoint?: string;\n /** Contact User-Agent, required by the public OSM instance's usage policy. */\n userAgent?: string;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000, per OSM policy). */\n minIntervalMs?: number;\n /** Retry attempts on transient failures (default 2). */\n retries?: number;\n /** Optional response cache (opt-in). */\n cache?: RequestCache;\n}\n\ninterface NominatimResult {\n place_id: number;\n lat: string;\n lon: string;\n name?: string;\n display_name: string;\n class?: string;\n type?: string;\n /** Nominatim's relevance heuristic in [0, 1]; not correctness — used for ambiguity. */\n importance?: number;\n /** [south, north, west, east] as strings. */\n boundingbox?: [string, string, string, string];\n}\n\n/**\n * Geocoding via Nominatim (OpenStreetMap). Free and key-less; please honour the\n * usage policy (max ~1 req/s, valid User-Agent) or point `endpoint` at your own\n * instance for production traffic.\n */\nexport class NominatimGeocoder implements GeocodingProvider {\n readonly name = 'nominatim';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: NominatimOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://nominatim.openstreetmap.org').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 2;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async geocode(query: string, options: GeocodeOptions = {}): Promise<Place[]> {\n const params = new URLSearchParams({\n q: query,\n format: 'jsonv2',\n limit: String(options.limit ?? 5),\n });\n if (options.language) params.set('accept-language', options.language);\n const results = await this.request<NominatimResult[]>(`/search?${params}`, options.signal);\n return results.map((result) => this.toPlace(result));\n }\n\n async reverse(location: LatLng, options: GeocodeOptions = {}): Promise<Place | null> {\n const params = new URLSearchParams({\n lat: String(location.lat),\n lon: String(location.lng),\n format: 'jsonv2',\n });\n if (options.language) params.set('accept-language', options.language);\n const result = await this.request<NominatimResult | { error: unknown }>(\n `/reverse?${params}`,\n options.signal,\n );\n if (!result || 'error' in result) return null;\n return this.toPlace(result);\n }\n\n private async request<T>(path: string, signal: AbortSignal | undefined): Promise<T> {\n await this.limiter.acquire();\n return requestJson<T>(`${this.endpoint}${path}`, {\n headers: { 'User-Agent': this.userAgent },\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(signal ? { signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n }\n\n private toPlace(result: NominatimResult): Place {\n const fallbackName = result.display_name.split(',')[0]?.trim() ?? result.display_name;\n const place: Place = {\n name: result.name && result.name.length > 0 ? result.name : fallbackName,\n displayName: result.display_name,\n location: { lat: Number(result.lat), lng: Number(result.lon) },\n source: this.name,\n raw: result,\n };\n const kind = result.type ?? result.class;\n if (kind) place.kind = kind;\n if (result.boundingbox) {\n const [s, n, w, e] = result.boundingbox;\n place.boundingBox = [Number(s), Number(n), Number(w), Number(e)];\n }\n return place;\n }\n}\n","import { categorize } from '../categories';\nimport {\n DEFAULT_USER_AGENT,\n HttpError,\n RateLimiter,\n requestJson,\n type RequestCache,\n} from '../http';\nimport { completenessOf, dedupePois, lastVerifiedOf } from '../quality';\nimport { selectorToOverpassFilter } from '../taxonomy';\nimport type { CategorySelector, LatLng, NearbyOptions, PlacesProvider, Poi } from '../types';\n\nexport interface OverpassOptions {\n /** Overpass interpreter endpoint URL. */\n endpoint?: string;\n /** Contact User-Agent sent with each request. */\n userAgent?: string;\n /** Per-request timeout in milliseconds. */\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000). */\n minIntervalMs?: number;\n /** Retry attempts on transient failures (default 2). */\n retries?: number;\n /** Optional response cache (opt-in). */\n cache?: RequestCache;\n}\n\ninterface OverpassElement {\n type: 'node' | 'way' | 'relation';\n id: number;\n lat?: number;\n lon?: number;\n center?: { lat: number; lon: number };\n tags?: Record<string, string>;\n /** Last-edit time, present when the query requests `meta`. */\n timestamp?: string;\n}\n\ninterface OverpassResponse {\n elements?: OverpassElement[];\n /** Present when Overpass reports a runtime error/timeout (often with HTTP 200). */\n remark?: string;\n}\n\n/** Tag selectors fetched around the search center. Mirrors `categorize`. */\nconst SELECTORS = [\n 'nwr[\"amenity\"]',\n 'nwr[\"shop\"]',\n 'nwr[\"tourism\"]',\n 'nwr[\"leisure\"]',\n 'nwr[\"healthcare\"]',\n 'nwr[\"office\"=\"government\"]',\n 'nwr[\"railway\"~\"^(station|halt|tram_stop|subway_entrance|stop)$\"]',\n 'nwr[\"public_transport\"=\"station\"]',\n 'node[\"highway\"=\"bus_stop\"]',\n 'nwr[\"aeroway\"=\"aerodrome\"]',\n];\n\n/** Build the Overpass QL query for all relevant POIs within `radiusMeters`. */\nexport function buildOverpassQuery(center: LatLng, radiusMeters: number): string {\n const around = `around:${Math.max(1, Math.round(radiusMeters))},${center.lat},${center.lng}`;\n const body = SELECTORS.map((selector) => ` ${selector}(${around});`).join('\\n');\n return `[out:json][timeout:25];\\n(\\n${body}\\n);\\nout center tags meta;`;\n}\n\n/** Build an Overpass query that fetches only features matching `selectors`. */\nexport function buildTargetedOverpassQuery(\n center: LatLng,\n radiusMeters: number,\n selectors: CategorySelector[],\n): string {\n const around = `around:${Math.max(1, Math.round(radiusMeters))},${center.lat},${center.lng}`;\n const body = selectors\n .map((selector) => ` nwr${selectorToOverpassFilter(selector)}(${around});`)\n .join('\\n');\n return `[out:json][timeout:25];\\n(\\n${body}\\n);\\nout center tags meta;`;\n}\n\nfunction coordOf(lat: number | undefined, lon: number | undefined): LatLng | null {\n if (\n typeof lat === 'number' &&\n typeof lon === 'number' &&\n Number.isFinite(lat) &&\n Number.isFinite(lon)\n ) {\n return { lat, lng: lon };\n }\n return null;\n}\n\n/**\n * Nearby-places search via the Overpass API over OpenStreetMap data. Fetches\n * every relevant feature within the radius, classifies it, and (optionally)\n * filters by category — distance ranking happens upstream.\n */\nexport class OverpassPlacesProvider implements PlacesProvider {\n readonly name = 'overpass';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: OverpassOptions = {}) {\n this.endpoint = options.endpoint ?? 'https://overpass-api.de/api/interpreter';\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs ?? 30_000;\n this.retries = options.retries ?? 2;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]> {\n const query =\n options.selectors && options.selectors.length > 0\n ? buildTargetedOverpassQuery(center, options.radiusMeters, options.selectors)\n : buildOverpassQuery(center, options.radiusMeters);\n await this.limiter.acquire();\n const data = await requestJson<OverpassResponse>(this.endpoint, {\n method: 'POST',\n headers: {\n 'User-Agent': this.userAgent,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: `data=${encodeURIComponent(query)}`,\n timeoutMs: this.timeoutMs,\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n if (data.remark && /error|timed out|rate_limited|too many|memory/i.test(data.remark)) {\n throw new HttpError(0, this.endpoint, `Overpass: ${data.remark}`);\n }\n\n const wanted = options.categories ? new Set(options.categories) : null;\n const pois: Poi[] = [];\n for (const element of data.elements ?? []) {\n const poi = this.toPoi(element);\n if (!poi) continue;\n if (wanted && !wanted.has(poi.category)) continue;\n pois.push(poi);\n }\n return dedupePois(pois);\n }\n\n private toPoi(element: OverpassElement): Poi | null {\n const tags = element.tags;\n if (!tags) return null;\n const coords =\n element.type === 'node'\n ? coordOf(element.lat, element.lon)\n : element.center\n ? coordOf(element.center.lat, element.center.lon)\n : null;\n if (!coords) return null;\n\n const { category, kind } = categorize(tags);\n const poi: Poi = {\n id: `${element.type}/${element.id}`,\n category,\n location: coords,\n tags,\n source: this.name,\n };\n if (tags.name) poi.name = tags.name;\n if (kind) poi.kind = kind;\n poi.completeness = completenessOf(category, tags);\n const lastVerified = lastVerifiedOf(tags, element.timestamp);\n if (lastVerified) poi.lastVerified = lastVerified;\n return poi;\n }\n}\n","import {\n DEFAULT_USER_AGENT,\n HttpError,\n RateLimiter,\n requestJson,\n type RequestCache,\n} from '../http';\nimport type {\n PolygonRing,\n RouteMetric,\n RoutingProvider,\n RoutingRequestOptions,\n TravelMode,\n} from '../routing';\nimport type { LatLng } from '../types';\n\n/** proximap travel modes → Valhalla costing models. */\nconst COSTING: Record<TravelMode, string> = {\n walk: 'pedestrian',\n bike: 'bicycle',\n drive: 'auto',\n};\n\nexport interface ValhallaOptions {\n /** Base URL of the Valhalla instance (default: the key-free FOSSGIS instance). */\n endpoint?: string;\n userAgent?: string;\n timeoutMs?: number;\n /** Minimum spacing between requests in ms (default 1000). */\n minIntervalMs?: number;\n retries?: number;\n cache?: RequestCache;\n}\n\ninterface MatrixCell {\n distance: number | null; // kilometres\n time: number | null; // seconds\n to_index: number;\n from_index: number;\n}\ninterface MatrixResponse {\n sources_to_targets?: MatrixCell[][];\n error?: string;\n}\ninterface IsochroneResponse {\n features?: { geometry?: { type: string; coordinates: unknown } }[];\n error?: string;\n}\n\n/**\n * Routing via Valhalla. Defaults to the **key-free** public FOSSGIS instance,\n * which supports pedestrian/bicycle/auto matrices and real isochrone polygons —\n * please honour its ~1 req/s fair-use policy or self-host for volume.\n */\nexport class ValhallaRoutingProvider implements RoutingProvider {\n readonly name = 'valhalla';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: ValhallaOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://valhalla1.openstreetmap.de').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 1;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<(RouteMetric | null)[]> {\n if (targets.length === 0) return [];\n const body = JSON.stringify({\n sources: [{ lat: origin.lat, lon: origin.lng }],\n targets: targets.map((target) => ({ lat: target.lat, lon: target.lng })),\n costing: COSTING[mode],\n });\n const data = await this.post<MatrixResponse>('/sources_to_targets', body, options.signal);\n if (data.error) throw new HttpError(0, this.endpoint, `Valhalla: ${data.error}`);\n\n const row = data.sources_to_targets?.[0] ?? [];\n const metrics: (RouteMetric | null)[] = targets.map(() => null);\n for (const cell of row) {\n if (cell.time === null || cell.distance === null) continue;\n if (cell.to_index < 0 || cell.to_index >= metrics.length) continue;\n metrics[cell.to_index] = {\n seconds: Math.round(cell.time),\n meters: Math.round(cell.distance * 1000),\n };\n }\n return metrics;\n }\n\n async isochrone(\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<PolygonRing> {\n const body = JSON.stringify({\n locations: [{ lat: origin.lat, lon: origin.lng }],\n costing: COSTING[mode],\n contours: [{ time: minutes }],\n polygons: true,\n });\n const data = await this.post<IsochroneResponse>('/isochrone', body, options.signal);\n if (data.error) throw new HttpError(0, this.endpoint, `Valhalla: ${data.error}`);\n const ring = extractRing(data.features ?? []);\n if (!ring) throw new HttpError(0, this.endpoint, 'Valhalla: no isochrone polygon returned');\n return ring;\n }\n\n private async post<T>(path: string, body: string, signal: AbortSignal | undefined): Promise<T> {\n await this.limiter.acquire();\n return requestJson<T>(`${this.endpoint}${path}`, {\n method: 'POST',\n headers: { 'User-Agent': this.userAgent, 'Content-Type': 'application/json' },\n body,\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(signal ? { signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n }\n}\n\n/** Pull the outer ring out of a Valhalla isochrone FeatureCollection (Polygon/MultiPolygon). */\nfunction extractRing(\n features: { geometry?: { type: string; coordinates: unknown } }[],\n): PolygonRing | null {\n for (const feature of features) {\n const geometry = feature.geometry;\n if (!geometry) continue;\n const coords = geometry.coordinates;\n if (geometry.type === 'LineString' && isRing(coords)) return coords;\n if (geometry.type === 'Polygon' && Array.isArray(coords) && isRing(coords[0])) {\n return coords[0];\n }\n if (\n geometry.type === 'MultiPolygon' &&\n Array.isArray(coords) &&\n Array.isArray(coords[0]) &&\n isRing(coords[0][0])\n ) {\n return coords[0][0];\n }\n }\n return null;\n}\n\nfunction isRing(value: unknown): value is PolygonRing {\n return (\n Array.isArray(value) &&\n value.length >= 3 &&\n value.every(\n (point) =>\n Array.isArray(point) &&\n point.length >= 2 &&\n typeof point[0] === 'number' &&\n typeof point[1] === 'number',\n )\n );\n}\n","import { DEFAULT_USER_AGENT, RateLimiter, requestJson, type RequestCache } from '../http';\nimport type { RouteMetric, RoutingProvider, RoutingRequestOptions, TravelMode } from '../routing';\nimport type { LatLng } from '../types';\n\n/** proximap travel modes → OSRM profiles (self-hosted; the public demo is car-only). */\nconst PROFILE: Record<TravelMode, string> = { walk: 'foot', bike: 'bike', drive: 'driving' };\n\nexport interface OsrmOptions {\n /** Base URL of the OSRM instance (default: the public demo server). */\n endpoint?: string;\n userAgent?: string;\n timeoutMs?: number;\n minIntervalMs?: number;\n retries?: number;\n cache?: RequestCache;\n}\n\ninterface TableResponse {\n code: string;\n /** durations[i][j] seconds; sources=0 ⇒ durations[0] is origin→each coordinate. */\n durations?: (number | null)[][];\n distances?: (number | null)[][];\n}\n\n/**\n * Travel-time matrices via OSRM's Table service. The public demo server\n * (`router.project-osrm.org`) only offers the car profile; for walk/bike,\n * point `endpoint` at a self-hosted OSRM with the matching profile. Matrix only —\n * OSRM has no isochrone service (use {@link ValhallaRoutingProvider} for that).\n */\nexport class OsrmRoutingProvider implements RoutingProvider {\n readonly name = 'osrm';\n private readonly endpoint: string;\n private readonly userAgent: string;\n private readonly timeoutMs: number | undefined;\n private readonly retries: number;\n private readonly cache: RequestCache | undefined;\n private readonly limiter: RateLimiter;\n\n constructor(options: OsrmOptions = {}) {\n this.endpoint = (options.endpoint ?? 'https://router.project-osrm.org').replace(/\\/+$/, '');\n this.userAgent = options.userAgent ?? DEFAULT_USER_AGENT;\n this.timeoutMs = options.timeoutMs;\n this.retries = options.retries ?? 1;\n this.cache = options.cache;\n this.limiter = new RateLimiter(options.minIntervalMs ?? 1000);\n }\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options: RoutingRequestOptions = {},\n ): Promise<(RouteMetric | null)[]> {\n if (targets.length === 0) return [];\n const coordinates = [origin, ...targets].map((c) => `${c.lng},${c.lat}`).join(';');\n const params = new URLSearchParams({ sources: '0', annotations: 'duration,distance' });\n const url = `${this.endpoint}/table/v1/${PROFILE[mode]}/${coordinates}?${params}`;\n\n await this.limiter.acquire();\n const data = await requestJson<TableResponse>(url, {\n headers: { 'User-Agent': this.userAgent },\n retries: this.retries,\n ...(this.cache ? { cache: this.cache } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n ...(this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}),\n });\n\n const durations = data.durations?.[0] ?? [];\n const distances = data.distances?.[0] ?? [];\n return targets.map((_, index) => {\n const seconds = durations[index + 1]; // index 0 is the origin→origin self-pair\n if (seconds === null || seconds === undefined) return null;\n const meters = distances[index + 1];\n return { seconds: Math.round(seconds), meters: meters == null ? 0 : Math.round(meters) };\n });\n }\n}\n","import { haversineMeters } from './geo';\nimport type { Category, LatLng, Poi, RankedPoi } from './types';\n\n/** Inputs handed to a scorer for a single POI. */\nexport interface ScoreInput {\n poi: Poi;\n distanceMeters: number;\n /** Radius used to normalize distance into a [0, 1] proximity. */\n radiusMeters: number;\n}\n\nexport interface RankOptions {\n /** Per-category multipliers applied to the base score (default 1 each). */\n categoryWeights?: Partial<Record<Category, number>>;\n /** Custom scorer (higher = better); overrides the default proximity scorer. */\n scoreFn?: (input: ScoreInput) => number;\n /** Distance normalization radius; defaults to the farthest POI found. */\n radiusMeters?: number;\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\nfunction defaultScorer(weights?: Partial<Record<Category, number>>): (input: ScoreInput) => number {\n return ({ poi, distanceMeters, radiusMeters }) => {\n // Guard the divisor: a radiusMeters of 0 with a POI at the origin is 0/0 = NaN.\n const proximity = 1 - clamp01(distanceMeters / Math.max(radiusMeters, 1));\n const completeness = poi.name ? 0.05 : 0;\n const weight = weights?.[poi.category] ?? 1;\n return (proximity + completeness) * weight;\n };\n}\n\n/**\n * Rank POIs by proximity to `origin`. By default results are ordered nearest\n * first; supplying `categoryWeights` or a custom `scoreFn` switches to\n * highest-score first (ties broken by distance). Each result gains its\n * great-circle `distanceMeters`, a `score` in [0, 1], and a 1-based `rank`.\n */\nexport function rankByProximity(\n origin: LatLng,\n pois: Poi[],\n options: RankOptions = {},\n): RankedPoi[] {\n if (pois.length === 0) return [];\n\n const measured = pois.map((poi) => ({\n poi,\n distanceMeters: haversineMeters(origin, poi.location),\n }));\n const maxDistance = measured.reduce((max, m) => Math.max(max, m.distanceMeters), 0);\n const radiusMeters = options.radiusMeters ?? Math.max(maxDistance, 1);\n const score = options.scoreFn ?? defaultScorer(options.categoryWeights);\n\n const scored = measured.map(({ poi, distanceMeters }) => ({\n poi,\n distanceMeters,\n score: clamp01(score({ poi, distanceMeters, radiusMeters })),\n }));\n\n const byScore = Boolean(options.scoreFn || options.categoryWeights);\n // A stable `id` tie-break makes the order byte-identical across runs — agents\n // can rely on it (per the agent-native output goals).\n scored.sort((a, b) => {\n const primary = byScore\n ? b.score - a.score || a.distanceMeters - b.distanceMeters\n : a.distanceMeters - b.distanceMeters;\n return primary || a.poi.id.localeCompare(b.poi.id);\n });\n\n return scored.map((entry, index) => ({\n ...entry.poi,\n distanceMeters: entry.distanceMeters,\n score: entry.score,\n rank: index + 1,\n }));\n}\n","import { haversineMeters } from './geo';\nimport type { LatLng } from './types';\n\n/** How a route is travelled. Maps to engine-specific profiles per adapter. */\nexport type TravelMode = 'walk' | 'bike' | 'drive';\n\nexport interface RouteMetric {\n /** Travel duration in seconds. */\n seconds: number;\n /** Travel distance in metres. */\n meters: number;\n}\n\n/** A polygon ring as [longitude, latitude] pairs (GeoJSON order). */\nexport type PolygonRing = [number, number][];\n\nexport interface RoutingRequestOptions {\n signal?: AbortSignal;\n}\n\n/**\n * Routing is a commodity (OSRM/Valhalla/ORS self-host it); proximap's value is\n * *composing* it with the amenity layer. This is the seam: a one-to-many matrix\n * and an optional isochrone, with adapters for real engines and a haversine\n * fallback so everything degrades gracefully and works key-free out of the box.\n */\nexport interface RoutingProvider {\n readonly name: string;\n /** Durations/distances from one origin to many targets, aligned with `targets` (null = unreachable). */\n matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n options?: RoutingRequestOptions,\n ): Promise<(RouteMetric | null)[]>;\n /** Polygon reachable within `minutes`. Optional — absent ⇒ callers fall back to a matrix threshold. */\n isochrone?(\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n options?: RoutingRequestOptions,\n ): Promise<PolygonRing>;\n}\n\n/** Typical speeds (m/s) for the haversine fallback: ~5, ~15, ~40 km/h. */\nexport const MODE_SPEED_MPS: Record<TravelMode, number> = { walk: 1.4, bike: 4.2, drive: 11.1 };\n\n/**\n * Straight-line routing: distance via haversine, duration via a per-mode speed,\n * isochrone as a circle. The key-free, network-free default — a floor that always\n * works; pass a real {@link RoutingProvider} (Valhalla/OSRM) for road accuracy.\n */\nexport class HaversineRoutingProvider implements RoutingProvider {\n readonly name = 'haversine';\n\n async matrix(\n origin: LatLng,\n targets: readonly LatLng[],\n mode: TravelMode,\n ): Promise<RouteMetric[]> {\n const speed = MODE_SPEED_MPS[mode];\n return targets.map((target) => {\n const meters = haversineMeters(origin, target);\n return { meters: Math.round(meters), seconds: Math.round(meters / speed) };\n });\n }\n\n async isochrone(origin: LatLng, minutes: number, mode: TravelMode): Promise<PolygonRing> {\n return circlePolygon(origin, MODE_SPEED_MPS[mode] * minutes * 60);\n }\n}\n\n/** Approximate a circle of `radiusMeters` around `center` as a polygon ring ([lng, lat]). */\nexport function circlePolygon(center: LatLng, radiusMeters: number, steps = 48): PolygonRing {\n const latDelta = radiusMeters / 111_320;\n const lngDelta = radiusMeters / (111_320 * Math.cos((center.lat * Math.PI) / 180));\n const ring: PolygonRing = [];\n for (let i = 0; i <= steps; i++) {\n const angle = (2 * Math.PI * i) / steps;\n ring.push([center.lng + lngDelta * Math.cos(angle), center.lat + latDelta * Math.sin(angle)]);\n }\n return ring;\n}\n\n/** Ray-casting point-in-polygon test; `ring` is [lng, lat] pairs. */\nexport function pointInPolygon(point: LatLng, ring: PolygonRing): boolean {\n const x = point.lng;\n const y = point.lat;\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const a = ring[i]!;\n const b = ring[j]!;\n const intersect =\n a[1] > y !== b[1] > y && x < ((b[0] - a[0]) * (y - a[1])) / (b[1] - a[1]) + a[0];\n if (intersect) inside = !inside;\n }\n return inside;\n}\n","import type { ScoreInput } from './ranking';\n\n/**\n * Composable consumer/accessibility facets that OSM tags carry but most apps\n * never expose as combinable filters: dietary options, cuisine, payment\n * methods, connectivity, seating, and step-free access. Each facet compiles to\n * a predicate over a POI's tags; a POI must satisfy *all* active facets.\n *\n * Sparsity note: a missing tag is treated as \"unknown\", which means the POI is\n * not a positive match — never that it \"fails\". We don't claim a place lacks a\n * feature, only that OSM doesn't record it.\n */\nexport interface FacetFilters {\n /** Dietary options, e.g. \"vegan\", \"vegetarian\", \"halal\" (matches diet:<x>=yes|only). */\n diet?: string | string[];\n /** Cuisine tokens, e.g. \"italian\", \"pizza\" (matches the ;-separated cuisine tag). */\n cuisine?: string | string[];\n /** Accepted payments, e.g. \"contactless\", \"cards\", \"visa\" (matches payment:<x>). */\n payment?: string | string[];\n /** Require internet access (internet_access present and not \"no\"). */\n internetAccess?: boolean;\n /** Require outdoor seating. */\n outdoorSeating?: boolean;\n /** Require takeaway (takeaway=yes|only). */\n takeaway?: boolean;\n /** Require delivery. */\n delivery?: boolean;\n /** Required wheelchair value(s), e.g. \"yes\" or [\"yes\", \"limited\"]. */\n wheelchair?: string | string[];\n /** Raw tag constraints: `true` = present, `false` = absent, string = equals. */\n tags?: Record<string, string | boolean>;\n}\n\n/** A compiled facet check over a POI's tag set. */\nexport type FacetPredicate = (tags: Record<string, string>) => boolean;\n\nconst toArray = (v?: string | string[]): string[] =>\n v === undefined ? [] : Array.isArray(v) ? v : [v];\n\n/** Values that count as \"yes\" for binary OSM facets where only yes/only apply. */\nconst YES_ONLY = new Set(['yes', 'only']);\nconst NEGATIVE = new Set(['no', 'false', '0', '']);\n\n/** True when a tag is present and not an explicit negative (yes/wlan/customers/…). */\nconst isAffirmative = (value: string | undefined): boolean =>\n value !== undefined && !NEGATIVE.has(value.trim().toLowerCase());\n\nconst cuisineTokens = (tags: Record<string, string>): string[] =>\n (tags.cuisine ?? '')\n .toLowerCase()\n .split(';')\n .map((s) => s.trim())\n .filter(Boolean);\n\n/** Compile a {@link FacetFilters} object into a list of tag predicates (AND-ed). */\nexport function compileFacets(filters: FacetFilters): FacetPredicate[] {\n const preds: FacetPredicate[] = [];\n\n for (const diet of toArray(filters.diet)) {\n const key = `diet:${diet.toLowerCase()}`;\n preds.push((t) => YES_ONLY.has((t[key] ?? '').toLowerCase()));\n }\n for (const cuisine of toArray(filters.cuisine)) {\n const want = cuisine.toLowerCase();\n preds.push((t) => cuisineTokens(t).includes(want));\n }\n for (const payment of toArray(filters.payment)) {\n const key = `payment:${payment.toLowerCase()}`;\n preds.push((t) => isAffirmative(t[key]));\n }\n if (filters.internetAccess) preds.push((t) => isAffirmative(t.internet_access));\n if (filters.outdoorSeating) preds.push((t) => isAffirmative(t.outdoor_seating));\n if (filters.takeaway) preds.push((t) => YES_ONLY.has((t.takeaway ?? '').toLowerCase()));\n if (filters.delivery) preds.push((t) => isAffirmative(t.delivery));\n\n const wheelchair = toArray(filters.wheelchair).map((v) => v.toLowerCase());\n if (wheelchair.length > 0) {\n preds.push((t) => wheelchair.includes((t.wheelchair ?? '').toLowerCase()));\n }\n\n for (const [key, value] of Object.entries(filters.tags ?? {})) {\n if (value === true) preds.push((t) => t[key] !== undefined);\n else if (value === false) preds.push((t) => t[key] === undefined);\n else {\n const want = String(value).toLowerCase();\n preds.push((t) => (t[key] ?? '').toLowerCase() === want);\n }\n }\n return preds;\n}\n\n/** Does a tag set satisfy every compiled facet predicate? */\nexport function matchesFacets(\n tags: Record<string, string>,\n predicates: readonly FacetPredicate[],\n): boolean {\n return predicates.every((predicate) => predicate(tags));\n}\n\nconst clamp01 = (n: number): number => Math.min(1, Math.max(0, n));\n\n/**\n * A ranking scorer for accessibility-first search: step-free (`wheelchair=yes`)\n * POIs rank above `limited`, which rank above unknown/none — with distance\n * breaking ties *within* each tier. Tiers occupy non-overlapping score bands so\n * an accessible-but-slightly-farther place still outranks a closer inaccessible\n * one, as the use case demands.\n */\nexport function accessibleScorer(): (input: ScoreInput) => number {\n return ({ poi, distanceMeters, radiusMeters }) => {\n // Guard the divisor: a radiusMeters of 0 with a POI at the origin is 0/0 = NaN.\n const proximity = 1 - clamp01(distanceMeters / Math.max(radiusMeters, 1));\n const wheelchair = (poi.tags.wheelchair ?? '').toLowerCase();\n const tierBase = wheelchair === 'yes' ? 0.66 : wheelchair === 'limited' ? 0.33 : 0;\n // Span 0.32 (< the 0.33 tier gap) keeps the bands strictly separated, so a\n // step-free POI at the radius edge still outranks a `limited` one at the origin.\n return tierBase + proximity * 0.32;\n };\n}\n","import { parseCoordinates } from './geo';\nimport type { GeocodingProvider, LatLng, Place } from './types';\n\nexport interface ResolveOriginOptions {\n language?: string;\n signal?: AbortSignal;\n}\n\n/**\n * Resolve a place name, a \"lat,lng\" string, or a {@link LatLng} into an origin\n * {@link Place}. Coordinate inputs are enriched with a best-effort reverse\n * geocode when the provider supports it, falling back to the raw coordinates.\n */\nexport async function resolveOrigin(\n query: string | LatLng,\n geocoder: GeocodingProvider,\n options: ResolveOriginOptions = {},\n): Promise<Place> {\n if (typeof query !== 'string') return originFromCoords(query, geocoder, options);\n\n const coords = parseCoordinates(query);\n if (coords) return originFromCoords(coords, geocoder, options);\n\n const matches = await geocoder.geocode(query, { limit: 1, ...geoOptions(options) });\n const first = matches[0];\n if (!first) throw new Error(`No location found for query: \"${query}\"`);\n return first;\n}\n\nasync function originFromCoords(\n coords: LatLng,\n geocoder: GeocodingProvider,\n options: ResolveOriginOptions,\n): Promise<Place> {\n const label = `${coords.lat}, ${coords.lng}`;\n const fallback: Place = {\n name: label,\n displayName: label,\n location: coords,\n source: 'coordinates',\n };\n if (geocoder.reverse) {\n try {\n const reversed = await geocoder.reverse(coords, geoOptions(options));\n if (reversed) return reversed;\n } catch {\n // Reverse geocoding is best-effort; fall back to the raw coordinates.\n }\n }\n return fallback;\n}\n\nfunction geoOptions(options: ResolveOriginOptions): { language?: string; signal?: AbortSignal } {\n const out: { language?: string; signal?: AbortSignal } = {};\n if (options.language) out.language = options.language;\n if (options.signal) out.signal = options.signal;\n return out;\n}\n","import { haversineMeters } from './geo';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport type { GeocodingProvider, Place } from './types';\n\nexport interface DisambiguateOptions {\n geocoder?: GeocodingProvider;\n /** How many candidates to fetch/return (default 5). */\n limit?: number;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface Disambiguation {\n query: string;\n /**\n * True when several plausible, geographically distinct candidates exist (e.g.\n * the ~90 US \"Springfield\"s). Callers should present `candidates` rather than\n * silently trusting `best`, since geocoder relevance is not correctness.\n */\n ambiguous: boolean;\n /** The top-ranked candidate (the geocoder's best guess), or null if none. */\n best: Place | null;\n /** Ranked candidates to disambiguate between. */\n candidates: Place[];\n}\n\n/** Distinct places this far apart (km) with similar names/relevance ⇒ ambiguous. */\nconst DISTINCT_KM = 25;\n/** A rival is \"comparably relevant\" if its importance is ≥ this fraction of the best's. */\nconst RIVAL_IMPORTANCE_RATIO = 0.8;\n\n/**\n * Geocode a query and decide whether it is ambiguous — multiple distinct places\n * a human would need to choose between — instead of silently taking result #1.\n * This is the agent-safety guard against confidently-wrong locations.\n */\nexport async function disambiguateLocation(\n query: string,\n options: DisambiguateOptions = {},\n): Promise<Disambiguation> {\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const limit = options.limit ?? 5;\n const candidates = await geocoder.geocode(query, {\n limit,\n ...(options.language ? { language: options.language } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n\n const best = candidates[0] ?? null;\n return { query, ambiguous: isAmbiguous(candidates), best, candidates };\n}\n\nconst importanceOf = (place: Place): number => {\n const raw = place.raw as { importance?: number } | undefined;\n return typeof raw?.importance === 'number' ? raw.importance : 0;\n};\n\nconst shortName = (place: Place): string =>\n (place.name || place.displayName.split(',')[0] || '').trim().toLowerCase();\n\nfunction isAmbiguous(candidates: Place[]): boolean {\n if (candidates.length < 2) return false;\n const best = candidates[0]!;\n const bestImportance = importanceOf(best);\n const bestName = shortName(best);\n\n return candidates.slice(1).some((rival) => {\n const farApart = haversineMeters(best.location, rival.location) > DISTINCT_KM * 1000;\n if (!farApart) return false;\n // A genuine rival is either comparably relevant, or shares the same name\n // (two real \"Springfield\"s) — both signal a real choice for the user.\n const comparablyRelevant =\n bestImportance > 0 && importanceOf(rival) >= bestImportance * RIVAL_IMPORTANCE_RATIO;\n const sameName = bestName.length > 0 && shortName(rival) === bestName;\n return comparablyRelevant || sameName;\n });\n}\n","import { haversineMeters } from './geo';\nimport { tagsMatchAnySelector } from './taxonomy';\nimport type { CategorySelector, LatLng, Poi } from './types';\n\n/** The closest POI matching a selector set, with its distance from an origin. */\nexport interface NearestMatch {\n /** Distance to the nearest match in metres, or null if none matched. */\n meters: number | null;\n /** The nearest matching POI, or null if none matched. */\n poi: Poi | null;\n}\n\n/**\n * Find the nearest POI to `origin` whose tags satisfy any of `selectors`.\n * Shared by the gap and walkability features; returns nulls (never throws)\n * when nothing matches, so callers can frame absence honestly.\n */\nexport function nearestMatchingPoi(\n origin: LatLng,\n pois: readonly Poi[],\n selectors: readonly CategorySelector[],\n): NearestMatch {\n let bestMeters: number | null = null;\n let bestPoi: Poi | null = null;\n for (const poi of pois) {\n if (!tagsMatchAnySelector(poi.tags, selectors)) continue;\n const meters = haversineMeters(origin, poi.location);\n if (bestMeters === null || meters < bestMeters) {\n bestMeters = meters;\n bestPoi = poi;\n }\n }\n return { meters: bestMeters, poi: bestPoi };\n}\n","import { CATEGORY_LABELS } from './categories';\nimport { accessibleScorer, compileFacets, matchesFacets, type FacetFilters } from './filters';\nimport { formatDistance, formatDuration, haversineMeters } from './geo';\nimport { isOpenAt, type OpeningEvaluation } from './hours';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { rankByProximity, type RankOptions } from './ranking';\nimport {\n HaversineRoutingProvider,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n CategorySelector,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n RankedPoi,\n} from './types';\n\nexport interface FindNearbyOptions {\n /** Search radius in metres (default 1000). */\n radiusMeters?: number;\n /**\n * Restrict to these categories. Accepts the 16 normalized category names and\n * natural-language terms (\"coffee\", \"pharmacy\", \"petrol\"). Unknown terms throw\n * with suggestions.\n */\n categories?: Array<Category | (string & {})>;\n /** Max results to return after ranking (default 30; <= 0 means no limit). */\n limit?: number;\n /** Preferred language for geocoding results. */\n language?: string;\n /** Override the geocoder (default: Nominatim/OSM). */\n geocoder?: GeocodingProvider;\n /** Override the places provider (default: Overpass/OSM). */\n places?: PlacesProvider;\n /**\n * Composable consumer/accessibility facets (diet, payment, wifi, wheelchair…).\n * A POI must satisfy every active facet; a missing tag means \"not a match\",\n * never asserted as the feature being absent.\n */\n filters?: FacetFilters;\n /**\n * Accessibility-first ranking: step-free POIs rank above `limited`, above\n * unknown/none, with distance breaking ties within each tier. Ignored when a\n * custom `rank.scoreFn` is supplied.\n */\n accessible?: boolean;\n /**\n * Keep only places open at this time and annotate each result with\n * `openState`/`nextChange`. `'now'` uses the current time; `{ at }` takes an\n * ISO string or Date. Places whose hours are unknown are kept and labelled\n * `unknown` (never silently dropped); only confirmed-closed places are\n * removed. Times are read as the POI's local wall-clock (see {@link isOpenAt}).\n */\n open?: 'now' | { at: string | Date };\n /**\n * Order results by straight-line `'distance'` (default) or by `'travelTime'`.\n * Travel-time ranking attaches `travelSeconds`/`travelMeters` to each result.\n */\n rankBy?: 'distance' | 'travelTime';\n /** Travel mode for `rankBy: 'travelTime'` (default `'walk'`). */\n mode?: TravelMode;\n /**\n * Routing engine for travel-time ranking (default: {@link HaversineRoutingProvider},\n * key-free straight-line estimates). Pass a real engine for road-network times;\n * if it errors, ranking falls back to haversine and `result.routing.fellBack` is set.\n */\n routing?: RoutingProvider;\n /** Attach a short `rankingReason` to each result (e.g. \"closest open cafe, 240 m\"). */\n explain?: boolean;\n /** Ranking tweaks (category weights or a custom scorer). */\n rank?: RankOptions;\n signal?: AbortSignal;\n}\n\nexport interface NearbyResult {\n origin: Place;\n results: RankedPoi[];\n /** Number of POIs found before `limit` was applied. */\n total: number;\n /** Set when `rankBy: 'travelTime'` was used: which engine answered, and the mode. */\n routing?: { provider: string; mode: TravelMode; fellBack: boolean };\n}\n\nconst DEFAULT_RADIUS_M = 1000;\nconst DEFAULT_LIMIT = 30;\n\n/**\n * Resolve a place name or coordinate to an origin, find surrounding amenities,\n * and rank them by distance. This is the headline entry point of proximap.\n *\n * @param query A place name/address, a \"lat,lng\" string, or a {@link LatLng}.\n */\nexport async function findNearbyAmenities(\n query: string | LatLng,\n options: FindNearbyOptions = {},\n): Promise<NearbyResult> {\n const radiusMeters = options.radiusMeters ?? DEFAULT_RADIUS_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const selectors = resolveSelectors(options.categories);\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = { radiusMeters };\n if (selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n\n const found = await places.findNearby(origin.location, nearbyOptions);\n let pois =\n selectors.length > 0 ? found.filter((poi) => tagsMatchAnySelector(poi.tags, selectors)) : found;\n\n if (options.filters) {\n const predicates = compileFacets(options.filters);\n if (predicates.length > 0) pois = pois.filter((poi) => matchesFacets(poi.tags, predicates));\n }\n\n // Drop confirmed-closed places (keep open + unknown), remembering each\n // evaluation so it can annotate the ranked results below.\n let openEval: Map<string, OpeningEvaluation> | null = null;\n if (options.open) {\n const when = options.open === 'now' ? new Date() : new Date(options.open.at);\n openEval = new Map();\n pois = pois.filter((poi) => {\n const evaluation = isOpenAt(poi.tags.opening_hours, when);\n openEval!.set(poi.id, evaluation);\n return evaluation.state !== 'closed';\n });\n }\n\n let ranked: RankedPoi[];\n let routingInfo: NearbyResult['routing'];\n if (options.rankBy === 'travelTime') {\n const outcome = await rankByTravelTime(origin.location, pois, {\n mode: options.mode ?? 'walk',\n routing: options.routing ?? new HaversineRoutingProvider(),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n ranked = outcome.ranked;\n routingInfo = outcome.info;\n } else {\n const rankOptions: RankOptions = { radiusMeters, ...options.rank };\n if (options.accessible && !rankOptions.scoreFn) rankOptions.scoreFn = accessibleScorer();\n ranked = rankByProximity(origin.location, pois, rankOptions);\n }\n\n if (openEval) {\n ranked = ranked.map((poi) => {\n const evaluation = openEval!.get(poi.id);\n if (!evaluation) return poi;\n const annotated: RankedPoi = { ...poi, openState: evaluation.state };\n if (evaluation.nextChange) annotated.nextChange = evaluation.nextChange;\n return annotated;\n });\n }\n\n if (options.explain) {\n const mode = options.mode ?? 'walk';\n ranked = ranked.map((poi) => ({ ...poi, rankingReason: rankingReasonFor(poi, mode) }));\n }\n\n const limit = options.limit ?? DEFAULT_LIMIT;\n const results = limit > 0 ? ranked.slice(0, limit) : ranked;\n return {\n origin,\n results,\n total: ranked.length,\n ...(routingInfo ? { routing: routingInfo } : {}),\n };\n}\n\nconst MODE_ADVERB: Record<TravelMode, string> = {\n walk: 'on foot',\n bike: 'by bike',\n drive: 'by car',\n};\n\n/** Ordinal closeness word: 1 → \"closest\", 2 → \"2nd-closest\", … */\nfunction ordinalCloseness(rank: number): string {\n if (rank === 1) return 'closest';\n const mod100 = rank % 100;\n const mod10 = rank % 10;\n const suffix =\n mod10 === 1 && mod100 !== 11\n ? 'st'\n : mod10 === 2 && mod100 !== 12\n ? 'nd'\n : mod10 === 3 && mod100 !== 13\n ? 'rd'\n : 'th';\n return `${rank}${suffix}-closest`;\n}\n\n/** A short, deterministic explanation of a result's rank, e.g. \"closest open cafe, 240 m\". */\nfunction rankingReasonFor(poi: RankedPoi, mode: TravelMode): string {\n const what = poi.kind ?? CATEGORY_LABELS[poi.category].toLowerCase();\n const openness = poi.openState === 'open' ? 'open ' : '';\n if (poi.travelSeconds !== undefined) {\n return `${ordinalCloseness(poi.rank)} ${openness}${what} ${MODE_ADVERB[mode]}, ${formatDuration(poi.travelSeconds)}`;\n }\n return `${ordinalCloseness(poi.rank)} ${openness}${what}, ${formatDistance(poi.distanceMeters)}`;\n}\n\n/** Cap on candidates sent to a routing matrix — bounds cost and public-engine limits. */\nconst TRAVEL_MATRIX_CAP = 80;\n\n/**\n * Rank POIs by travel duration. Trims to the nearest {@link TRAVEL_MATRIX_CAP}\n * candidates by straight-line distance first (to bound matrix size), then orders\n * by the routing engine's durations — falling back to haversine if it errors.\n */\nasync function rankByTravelTime(\n origin: LatLng,\n pois: Poi[],\n options: { mode: TravelMode; routing: RoutingProvider; signal?: AbortSignal },\n): Promise<{ ranked: RankedPoi[]; info: NonNullable<NearbyResult['routing']> }> {\n const candidates = [...pois]\n .sort((a, b) => haversineMeters(origin, a.location) - haversineMeters(origin, b.location))\n .slice(0, TRAVEL_MATRIX_CAP);\n const points = candidates.map((poi) => poi.location);\n const requestOptions = options.signal ? { signal: options.signal } : {};\n\n let metrics: (RouteMetric | null)[];\n let provider = options.routing.name;\n let fellBack = false;\n try {\n metrics = await options.routing.matrix(origin, points, options.mode, requestOptions);\n } catch (error) {\n if (options.routing instanceof HaversineRoutingProvider) throw error;\n const fallback = new HaversineRoutingProvider();\n metrics = await fallback.matrix(origin, points, options.mode);\n provider = fallback.name;\n fellBack = true;\n }\n\n const reachable = candidates\n .map((poi, index) => ({ poi, metric: metrics[index] ?? null }))\n .filter((entry): entry is { poi: Poi; metric: RouteMetric } => entry.metric !== null)\n .sort((a, b) => a.metric.seconds - b.metric.seconds);\n\n const slowest = reachable.reduce((max, entry) => Math.max(max, entry.metric.seconds), 0) || 1;\n const ranked: RankedPoi[] = reachable.map((entry, index) => ({\n ...entry.poi,\n distanceMeters: haversineMeters(origin, entry.poi.location),\n score: Math.round((1 - entry.metric.seconds / slowest) * 100) / 100,\n rank: index + 1,\n travelSeconds: entry.metric.seconds,\n travelMeters: entry.metric.meters,\n }));\n\n return { ranked, info: { provider, mode: options.mode, fellBack } };\n}\n\n/** Resolve requested category terms to selectors, throwing on unknown terms. */\nfunction resolveSelectors(categories: FindNearbyOptions['categories']): CategorySelector[] {\n if (!categories || categories.length === 0) return [];\n const { selectors, unknown } = resolveCategories(categories);\n if (unknown.length > 0) {\n const detail = unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n return selectors;\n}\n","import { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport {\n HaversineRoutingProvider,\n MODE_SPEED_MPS,\n pointInPolygon,\n type PolygonRing,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n RankedPoi,\n} from './types';\n\nexport interface ReachableOptions {\n /** Time budget in minutes. */\n within: number;\n /** Travel mode (default `'walk'`). */\n mode?: TravelMode;\n /** Restrict to these categories/terms (default: all amenities). */\n categories?: Array<Category | (string & {})>;\n /**\n * Routing engine (default {@link HaversineRoutingProvider}). A provider with an\n * `isochrone` method gives a true reachability polygon; otherwise membership is\n * decided by a travel-time matrix threshold.\n */\n routing?: RoutingProvider;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface ReachableResult {\n origin: Place;\n withinMinutes: number;\n mode: TravelMode;\n /** The isochrone polygon used ([lng, lat] ring), or null if none was available. */\n isochrone: PolygonRing | null;\n /** Amenities reachable within the budget, soonest first. */\n results: RankedPoi[];\n count: number;\n}\n\n/** Don't fetch beyond this radius even for long drive budgets (keeps Overpass sane). */\nconst MAX_FETCH_RADIUS_M = 15_000;\nconst REACHABLE_MATRIX_CAP = 100;\n\n/**\n * Return the amenities reachable within a time budget — the *answer*, not just a\n * polygon. With an isochrone-capable engine, membership is the true road-network\n * polygon (point-in-polygon); otherwise it falls back to a travel-time matrix\n * threshold. Results are annotated with travel time and sorted soonest-first.\n */\nexport async function reachableAmenities(\n query: string | LatLng,\n options: ReachableOptions,\n): Promise<ReachableResult> {\n const withinMinutes = options.within;\n if (!(withinMinutes > 0)) {\n throw new Error('reachableAmenities needs a positive `within` (minutes).');\n }\n const mode = options.mode ?? 'walk';\n const routing = options.routing ?? new HaversineRoutingProvider();\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n const budgetSeconds = withinMinutes * 60;\n\n const selectors = resolveSelectors(options.categories);\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n // Straight-line reach is an upper bound on what any route could cover.\n const fetchRadius = Math.min(\n Math.round(MODE_SPEED_MPS[mode] * budgetSeconds),\n MAX_FETCH_RADIUS_M,\n );\n const nearbyOptions: NearbyOptions = { radiusMeters: fetchRadius };\n if (selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n const found = await places.findNearby(origin.location, nearbyOptions);\n const pois =\n selectors.length > 0 ? found.filter((poi) => tagsMatchAnySelector(poi.tags, selectors)) : found;\n\n const isochrone = await tryIsochrone(\n routing,\n origin.location,\n withinMinutes,\n mode,\n options.signal,\n );\n\n // With a polygon, membership is point-in-polygon; otherwise a matrix threshold.\n const candidates = isochrone\n ? pois.filter((poi) => pointInPolygon(poi.location, isochrone))\n : pois;\n const nearest = [...candidates]\n .sort(\n (a, b) =>\n haversineMeters(origin.location, a.location) - haversineMeters(origin.location, b.location),\n )\n .slice(0, REACHABLE_MATRIX_CAP);\n\n const metrics = await safeMatrix(routing, origin.location, nearest, mode, options.signal);\n const members = nearest\n .map((poi, index) => ({ poi, metric: metrics[index] ?? null }))\n .filter((entry) => isochrone !== null || withinBudget(entry.metric, budgetSeconds));\n\n members.sort(\n (a, b) =>\n (a.metric?.seconds ?? Infinity) - (b.metric?.seconds ?? Infinity) ||\n haversineMeters(origin.location, a.poi.location) -\n haversineMeters(origin.location, b.poi.location),\n );\n\n const results: RankedPoi[] = members.map((entry, index) => {\n const ranked: RankedPoi = {\n ...entry.poi,\n distanceMeters: haversineMeters(origin.location, entry.poi.location),\n // Clamp to [0, 1]: an isochrone can enclose a POI whose matrix travel\n // time still exceeds the budget, which would otherwise score negative.\n score: entry.metric\n ? Math.max(0, Math.round((1 - entry.metric.seconds / budgetSeconds) * 100) / 100)\n : 0,\n rank: index + 1,\n };\n if (entry.metric) {\n ranked.travelSeconds = entry.metric.seconds;\n ranked.travelMeters = entry.metric.meters;\n }\n return ranked;\n });\n\n return { origin, withinMinutes, mode, isochrone, results, count: results.length };\n}\n\nfunction withinBudget(metric: RouteMetric | null, budgetSeconds: number): boolean {\n return metric !== null && metric.seconds <= budgetSeconds;\n}\n\nasync function tryIsochrone(\n routing: RoutingProvider,\n origin: LatLng,\n minutes: number,\n mode: TravelMode,\n signal: AbortSignal | undefined,\n): Promise<PolygonRing | null> {\n if (!routing.isochrone) return null;\n try {\n return await routing.isochrone(origin, minutes, mode, signal ? { signal } : {});\n } catch {\n return null; // best-effort; fall back to the matrix-threshold path\n }\n}\n\nasync function safeMatrix(\n routing: RoutingProvider,\n origin: LatLng,\n pois: Poi[],\n mode: TravelMode,\n signal: AbortSignal | undefined,\n): Promise<(RouteMetric | null)[]> {\n if (pois.length === 0) return [];\n const points = pois.map((poi) => poi.location);\n try {\n return await routing.matrix(origin, points, mode, signal ? { signal } : {});\n } catch {\n if (routing instanceof HaversineRoutingProvider) return points.map(() => null);\n return new HaversineRoutingProvider().matrix(origin, points, mode);\n }\n}\n\n/** Resolve category terms to selectors, throwing on unknown terms with suggestions. */\nfunction resolveSelectors(categories: ReachableOptions['categories']) {\n if (!categories || categories.length === 0) return [];\n const { selectors, unknown } = resolveCategories(categories);\n if (unknown.length > 0) {\n const detail = unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n return selectors;\n}\n","import { resolveOrigin } from './origin';\nimport { nearestMatchingPoi } from './proximity';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories } from './taxonomy';\nimport type { GeocodingProvider, LatLng, NearbyOptions, Place, PlacesProvider } from './types';\n\n/** Everyday needs a well-served neighbourhood should provide nearby. */\nexport const DEFAULT_DAILY_NEEDS = [\n 'grocery',\n 'pharmacy',\n 'healthcare',\n 'food',\n 'finance',\n 'transport',\n 'education',\n 'park',\n] as const;\n\nexport interface GapOptions {\n /** Category terms to check (default: {@link DEFAULT_DAILY_NEEDS}). */\n categories?: string[];\n /** How far to look for the nearest instance, in metres (default 5000). */\n searchRadiusMeters?: number;\n /** Distance beyond which a category counts as a gap, in metres (default 1500). */\n thresholdMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface CategoryGap {\n /** The requested category term. */\n category: string;\n /** Distance to the nearest match, or null if none was found within the search radius. */\n nearestMeters: number | null;\n isGap: boolean;\n /** Data-completeness of the nearest match (a confidence hint), when known. */\n nearestCompleteness?: number;\n}\n\nexport interface GapReport {\n origin: Place;\n searchRadiusMeters: number;\n thresholdMeters: number;\n /** Every requested category with its nearest-match status. */\n gaps: CategoryGap[];\n /** Categories flagged as gaps — i.e. not found in OSM within the threshold. */\n missing: string[];\n}\n\nconst DEFAULT_SEARCH_RADIUS_M = 5000;\nconst DEFAULT_THRESHOLD_M = 1500;\n\n/**\n * Report which everyday amenities are missing or far from a location — the\n * inverse of \"what's nearby\". Because OSM under-maps some areas, absence is\n * framed as \"not found in OSM within the threshold\", never asserted as truth;\n * `nearestCompleteness` hints at data confidence.\n */\nexport async function detectGaps(\n query: string | LatLng,\n options: GapOptions = {},\n): Promise<GapReport> {\n const terms =\n options.categories && options.categories.length > 0\n ? options.categories\n : [...DEFAULT_DAILY_NEEDS];\n const searchRadiusMeters = options.searchRadiusMeters ?? DEFAULT_SEARCH_RADIUS_M;\n const thresholdMeters = options.thresholdMeters ?? DEFAULT_THRESHOLD_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const gaps: CategoryGap[] = terms.map((term) => {\n const selectors = resolveCategories([term]).selectors;\n const { meters, poi } = nearestMatchingPoi(origin.location, pois, selectors);\n const gap: CategoryGap = {\n category: term,\n nearestMeters: meters === null ? null : Math.round(meters),\n isGap: meters === null || meters > thresholdMeters,\n };\n if (poi?.completeness !== undefined) gap.nearestCompleteness = poi.completeness;\n return gap;\n });\n\n return {\n origin,\n searchRadiusMeters,\n thresholdMeters,\n gaps,\n missing: gaps.filter((gap) => gap.isGap).map((gap) => gap.category),\n };\n}\n","import { resolveOrigin } from './origin';\nimport { nearestMatchingPoi } from './proximity';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories } from './taxonomy';\nimport type { GeocodingProvider, LatLng, NearbyOptions, Place, PlacesProvider } from './types';\n\n/**\n * A daily-need category and how much it counts toward the walkability score.\n * Weights are relative — the score normalizes by their sum.\n */\nexport interface CategoryWeight {\n /** A natural-language category term (resolved via the taxonomy). */\n term: string;\n weight: number;\n}\n\n/**\n * Default basket of daily needs and weights, loosely modelled on the published\n * Walk Score categories but fully open and tunable. Groceries, food, pharmacy,\n * and transit count most; civic/leisure round out a complete neighbourhood.\n */\nexport const DEFAULT_WALK_CATEGORIES: CategoryWeight[] = [\n { term: 'grocery', weight: 3 },\n { term: 'food', weight: 2 },\n { term: 'pharmacy', weight: 2 },\n { term: 'transport', weight: 2 },\n { term: 'education', weight: 1 },\n { term: 'healthcare', weight: 1 },\n { term: 'finance', weight: 1 },\n { term: 'park', weight: 1 },\n { term: 'shopping', weight: 1 },\n];\n\nexport interface WalkabilityDecay {\n /** At/below this distance (m) a category scores full marks (default 400 ≈ 5-min walk). */\n idealMeters?: number;\n /** At/beyond this distance (m) a category scores zero (default 2400 ≈ 30-min walk). */\n maxMeters?: number;\n}\n\nexport interface WalkabilityOptions {\n /** Daily-need categories and weights (default {@link DEFAULT_WALK_CATEGORIES}). */\n categories?: CategoryWeight[];\n /** Distance-decay tuning (defaults: full credit ≤ 400 m, zero ≥ 2400 m). */\n decay?: WalkabilityDecay;\n /** How far to search for the nearest of each category (default = decay max). */\n searchRadiusMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface CategoryScore {\n category: string;\n weight: number;\n /** Distance to the nearest match, or null if none found within the search radius. */\n nearestMeters: number | null;\n /** Distance-decay sub-score in [0, 1]. */\n subScore: number;\n /** Data-completeness of the nearest match (a confidence hint), when known. */\n nearestCompleteness?: number;\n}\n\nexport interface WalkabilityReport {\n origin: Place;\n /** Overall walkability in [0, 100]; higher is more walkable. */\n score: number;\n /**\n * Confidence in [0, 1] reflecting OSM data density around the origin — low\n * where few categories were found or their tagging is sparse. A low score\n * with low confidence means \"thin data here\", not \"nothing here\".\n */\n confidence: number;\n /** Per-category nearest distance and sub-score. */\n breakdown: CategoryScore[];\n /** Categories with no match found within the search radius. */\n missing: string[];\n /** The distance-decay bounds actually used. */\n decay: { idealMeters: number; maxMeters: number };\n}\n\nconst DEFAULT_IDEAL_M = 400;\nconst DEFAULT_MAX_M = 2400;\n\n/**\n * Distance-decay sub-score: full credit within `ideal`, linearly declining to\n * zero at `max`. Deliberately simple and transparent so the score is auditable\n * and tunable, unlike opaque proprietary indices.\n */\nexport function walkSubScore(\n meters: number | null,\n idealMeters: number,\n maxMeters: number,\n): number {\n if (meters === null) return 0;\n if (meters <= idealMeters) return 1;\n if (meters >= maxMeters) return 0;\n return round2((maxMeters - meters) / (maxMeters - idealMeters));\n}\n\nconst round2 = (n: number): number => Math.round(n * 100) / 100;\nconst mean = (xs: number[]): number => (xs.length ? xs.reduce((s, x) => s + x, 0) / xs.length : 0);\n\n/**\n * Score how walkable / well-served a location is: a 0–100 number plus a full\n * per-category breakdown, the categories that are missing, and a data-confidence\n * note. An open, transparent, tunable, OSM-native alternative to proprietary\n * walkability indices — every input is visible and adjustable.\n */\nexport async function walkabilityScore(\n query: string | LatLng,\n options: WalkabilityOptions = {},\n): Promise<WalkabilityReport> {\n const categories =\n options.categories && options.categories.length > 0\n ? options.categories\n : DEFAULT_WALK_CATEGORIES;\n const idealMeters = options.decay?.idealMeters ?? DEFAULT_IDEAL_M;\n const maxMeters = options.decay?.maxMeters ?? DEFAULT_MAX_M;\n const searchRadiusMeters = options.searchRadiusMeters ?? maxMeters;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const terms = categories.map((c) => c.term);\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const breakdown: CategoryScore[] = categories.map(({ term, weight }) => {\n const selectors = resolveCategories([term]).selectors;\n const { meters, poi } = nearestMatchingPoi(origin.location, pois, selectors);\n const nearestMeters = meters === null ? null : Math.round(meters);\n const entry: CategoryScore = {\n category: term,\n weight,\n nearestMeters,\n subScore: walkSubScore(meters, idealMeters, maxMeters),\n };\n if (poi?.completeness !== undefined) entry.nearestCompleteness = poi.completeness;\n return entry;\n });\n\n const totalWeight = categories.reduce((sum, c) => sum + c.weight, 0) || 1;\n const weighted = breakdown.reduce((sum, b) => sum + b.weight * b.subScore, 0);\n const score = Math.round((100 * weighted) / totalWeight);\n\n const found = breakdown.filter((b) => b.nearestMeters !== null);\n const coverage = breakdown.length ? found.length / breakdown.length : 0;\n const avgCompleteness = mean(found.map((b) => b.nearestCompleteness ?? 0));\n const confidence = round2(0.7 * coverage + 0.3 * avgCompleteness);\n\n return {\n origin,\n score,\n confidence,\n breakdown,\n missing: breakdown.filter((b) => b.nearestMeters === null).map((b) => b.category),\n decay: { idealMeters, maxMeters },\n };\n}\n","import { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport type { GeocodingProvider, LatLng, Place, PlacesProvider } from './types';\nimport {\n DEFAULT_WALK_CATEGORIES,\n walkabilityScore,\n type CategoryScore,\n type CategoryWeight,\n type WalkabilityDecay,\n} from './walkability';\n\nexport interface CompareOptions {\n /** Dimensions and weights to compare on (default: the walkability basket). */\n categories?: CategoryWeight[];\n /** Distance-decay tuning, passed through to each location's scoring. */\n decay?: WalkabilityDecay;\n /** How far to search around each location (default = decay max). */\n searchRadiusMeters?: number;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface LocationScore {\n origin: Place;\n /** Walkability score (0–100) under the comparison weights. */\n score: number;\n confidence: number;\n breakdown: CategoryScore[];\n missing: string[];\n}\n\nexport interface DimensionWinner {\n category: string;\n weight: number;\n /** Index into `locations` of the best location for this dimension, or null if none has it. */\n bestIndex: number | null;\n}\n\nexport interface RankedLocation {\n /** Index into `locations` (input order). */\n index: number;\n score: number;\n origin: Place;\n}\n\nexport interface ComparisonReport {\n /** Each location's score, in input order. */\n locations: LocationScore[];\n /** Locations sorted best-first (ties: higher confidence, then input order). */\n ranked: RankedLocation[];\n /** The top-ranked location, or null if no candidates were given. */\n best: RankedLocation | null;\n /** For each dimension, which location is best served. */\n dimensions: DimensionWinner[];\n /** The weights actually used. */\n weights: CategoryWeight[];\n}\n\n/**\n * Compare N candidate locations across weighted daily-need dimensions and rank\n * them — a key-free, arbitrary-N, OSM-native relocation/siting scorecard. Pure\n * composition over {@link walkabilityScore}: each location is scored the same\n * way, then ranked and compared dimension-by-dimension.\n *\n * Out of scope by design (not in OSM): transit *frequency*, school quality,\n * crime, prices. We compare amenity *access*, and carry through walkability's\n * confidence so thin data reads as low confidence, not a confident zero.\n */\nexport async function compareLocations(\n queries: ReadonlyArray<string | LatLng>,\n options: CompareOptions = {},\n): Promise<ComparisonReport> {\n if (queries.length < 2) {\n throw new Error('compareLocations needs at least two locations to compare.');\n }\n const categories =\n options.categories && options.categories.length > 0\n ? options.categories\n : DEFAULT_WALK_CATEGORIES;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n // Sequential, with shared providers, so the OSM rate limiters stay polite.\n const locations: LocationScore[] = [];\n for (const query of queries) {\n const walk = await walkabilityScore(query, {\n categories,\n geocoder,\n places,\n ...(options.decay ? { decay: options.decay } : {}),\n ...(options.searchRadiusMeters ? { searchRadiusMeters: options.searchRadiusMeters } : {}),\n ...(options.language ? { language: options.language } : {}),\n ...(options.signal ? { signal: options.signal } : {}),\n });\n locations.push({\n origin: walk.origin,\n score: walk.score,\n confidence: walk.confidence,\n breakdown: walk.breakdown,\n missing: walk.missing,\n });\n }\n\n const ranked: RankedLocation[] = locations\n .map((location, index) => ({ index, location }))\n .sort(\n (a, b) =>\n b.location.score - a.location.score ||\n b.location.confidence - a.location.confidence ||\n a.index - b.index,\n )\n .map(({ index, location }) => ({ index, score: location.score, origin: location.origin }));\n\n const dimensions: DimensionWinner[] = categories.map(({ term, weight }) => ({\n category: term,\n weight,\n bestIndex: bestForDimension(locations, term),\n }));\n\n return {\n locations,\n ranked,\n best: ranked[0] ?? null,\n dimensions,\n weights: categories,\n };\n}\n\n/** The index of the location best served for a category: highest sub-score, nearest on ties. */\nfunction bestForDimension(locations: readonly LocationScore[], term: string): number | null {\n let bestIndex: number | null = null;\n let bestSub = -1;\n let bestMeters = Infinity;\n locations.forEach((location, index) => {\n const entry = location.breakdown.find((b) => b.category === term);\n if (!entry || entry.nearestMeters === null) return;\n if (\n entry.subScore > bestSub ||\n (entry.subScore === bestSub && entry.nearestMeters < bestMeters)\n ) {\n bestSub = entry.subScore;\n bestMeters = entry.nearestMeters;\n bestIndex = index;\n }\n });\n return bestIndex;\n}\n","import { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport {\n HaversineRoutingProvider,\n type RouteMetric,\n type RoutingProvider,\n type TravelMode,\n} from './routing';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n Place,\n PlacesProvider,\n Poi,\n} from './types';\n\nexport interface ErrandOptions {\n /** Categories/terms to hit one of each, e.g. ['pharmacy', 'atm', 'grocery']. */\n categories: Array<Category | (string & {})>;\n /** Travel mode for the cost matrix (default `'walk'`). */\n mode?: TravelMode;\n /** Optional fixed end point (a place name, \"lat,lng\", or coordinate). */\n end?: string | LatLng;\n /** Nearest candidates considered per category (default 5). */\n candidatesPerCategory?: number;\n /** How far to look for candidates, in metres (default 3000). */\n searchRadiusMeters?: number;\n /**\n * Cost engine for the matrix (default {@link HaversineRoutingProvider} — an\n * honest, instant, key-free straight-line MVP). Pass a real engine for road\n * times; note it issues one matrix request per point.\n */\n routing?: RoutingProvider;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n}\n\nexport interface ErrandStop {\n category: string;\n poi: Poi;\n /** Leg from the previous point (origin or prior stop) to this stop. */\n legSeconds: number;\n legMeters: number;\n}\n\nexport interface ErrandPlan {\n origin: Place;\n end: Place | null;\n mode: TravelMode;\n /** Chosen places in visit order. */\n stops: ErrandStop[];\n totalSeconds: number;\n totalMeters: number;\n /** Requested categories with no candidate nearby — skipped, not faked. */\n missing: string[];\n candidatesPerCategory: number;\n}\n\nconst DEFAULT_CANDIDATES = 5;\nconst DEFAULT_SEARCH_RADIUS_M = 3000;\n/** Upper bound on distinct categories for the exact DP (2^C states). */\nconst MAX_CATEGORIES = 12;\n\n/**\n * Plan the shortest trip that buys/visits one of each requested category near an\n * origin — the **Generalized TSP** (\"pick one per set, then optimize\") that no\n * consumer app ships. Fetches the nearest candidates per category, builds a cost\n * matrix, and solves exactly via a grouped Held-Karp DP (instant at consumer\n * scale). Categories with no candidate are reported as `missing`, never faked.\n */\nexport async function planErrands(\n query: string | LatLng,\n options: ErrandOptions,\n): Promise<ErrandPlan> {\n const terms = options.categories ?? [];\n if (terms.length === 0) throw new Error('planErrands needs at least one category.');\n if (terms.length > MAX_CATEGORIES) {\n throw new Error(\n `planErrands supports up to ${MAX_CATEGORIES} categories (got ${terms.length}).`,\n );\n }\n const mode = options.mode ?? 'walk';\n const perCategory = options.candidatesPerCategory ?? DEFAULT_CANDIDATES;\n const searchRadiusMeters = options.searchRadiusMeters ?? DEFAULT_SEARCH_RADIUS_M;\n const routing = options.routing ?? new HaversineRoutingProvider();\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n const resolved = resolveCategories(terms);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n const end = options.end !== undefined ? await resolveEnd(options.end, geocoder, options) : null;\n\n // One fetch over the union, then take the nearest K per category.\n const nearbyOptions: NearbyOptions = {\n radiusMeters: searchRadiusMeters,\n selectors: resolved.selectors,\n };\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n const missing: string[] = [];\n const groups: { term: string; candidates: Poi[] }[] = [];\n for (const term of terms) {\n const selectors = resolveCategories([term]).selectors;\n const candidates = pois\n .filter((poi) => tagsMatchAnySelector(poi.tags, selectors))\n .sort(\n (a, b) =>\n haversineMeters(origin.location, a.location) -\n haversineMeters(origin.location, b.location),\n )\n .slice(0, perCategory);\n if (candidates.length === 0) missing.push(String(term));\n else groups.push({ term: String(term), candidates });\n }\n\n if (groups.length === 0) {\n return {\n origin,\n end,\n mode,\n stops: [],\n totalSeconds: 0,\n totalMeters: 0,\n missing,\n candidatesPerCategory: perCategory,\n };\n }\n\n const solution = await solveGeneralizedTsp(\n origin.location,\n groups,\n end?.location ?? null,\n mode,\n routing,\n );\n return {\n origin,\n end,\n mode,\n stops: solution.stops,\n totalSeconds: solution.totalSeconds,\n totalMeters: solution.totalMeters,\n missing,\n candidatesPerCategory: perCategory,\n };\n}\n\ninterface FlatNode {\n group: number;\n term: string;\n poi: Poi;\n}\n\n/** Exact grouped Held-Karp: visit one node per group, origin-anchored, optional fixed end. */\nasync function solveGeneralizedTsp(\n origin: LatLng,\n groups: { term: string; candidates: Poi[] }[],\n end: LatLng | null,\n mode: TravelMode,\n routing: RoutingProvider,\n): Promise<{ stops: ErrandStop[]; totalSeconds: number; totalMeters: number }> {\n const nodes: FlatNode[] = [];\n groups.forEach((group, groupIndex) => {\n for (const poi of group.candidates) nodes.push({ group: groupIndex, term: group.term, poi });\n });\n\n // points: 0 = origin, 1..N = nodes, optional last = end.\n const points: LatLng[] = [origin, ...nodes.map((node) => node.poi.location)];\n const endPointIndex = end ? points.push(end) - 1 : -1;\n const matrix = await buildCostMatrix(points, mode, routing);\n const seconds = (from: number, to: number): number => matrix[from]![to]!.seconds;\n\n const groupCount = groups.length;\n const nodeCount = nodes.length;\n const full = (1 << groupCount) - 1;\n const cost: number[][] = Array.from({ length: 1 << groupCount }, () =>\n new Array(nodeCount).fill(Infinity),\n );\n const parent: number[][] = Array.from({ length: 1 << groupCount }, () =>\n new Array(nodeCount).fill(-1),\n );\n\n for (let n = 0; n < nodeCount; n++) {\n cost[1 << nodes[n]!.group]![n] = seconds(0, n + 1);\n }\n for (let mask = 1; mask <= full; mask++) {\n const row = cost[mask]!;\n for (let n = 0; n < nodeCount; n++) {\n const here = row[n]!;\n if (here === Infinity || !(mask & (1 << nodes[n]!.group))) continue;\n for (let m = 0; m < nodeCount; m++) {\n const groupBit = 1 << nodes[m]!.group;\n if (mask & groupBit) continue; // that group already visited\n const nextMask = mask | groupBit;\n const candidate = here + seconds(n + 1, m + 1);\n if (candidate < cost[nextMask]![m]!) {\n cost[nextMask]![m] = candidate;\n parent[nextMask]![m] = n;\n }\n }\n }\n }\n\n let best = Infinity;\n let lastNode = -1;\n for (let n = 0; n < nodeCount; n++) {\n let total = cost[full]![n]!;\n if (total === Infinity) continue;\n if (endPointIndex >= 0) total += seconds(n + 1, endPointIndex);\n if (total < best) {\n best = total;\n lastNode = n;\n }\n }\n\n // Reconstruct the visit order.\n const order: number[] = [];\n let mask = full;\n let node = lastNode;\n while (node !== -1) {\n order.push(node);\n const previous = parent[mask]![node]!;\n mask &= ~(1 << nodes[node]!.group);\n node = previous;\n }\n order.reverse();\n\n const stops: ErrandStop[] = [];\n let previousPoint = 0; // origin\n let totalMeters = 0;\n for (const n of order) {\n const leg = matrix[previousPoint]![n + 1]!;\n totalMeters += leg.meters;\n stops.push({\n category: nodes[n]!.term,\n poi: nodes[n]!.poi,\n legSeconds: leg.seconds,\n legMeters: leg.meters,\n });\n previousPoint = n + 1;\n }\n if (endPointIndex >= 0) totalMeters += matrix[previousPoint]![endPointIndex]!.meters;\n\n return { stops, totalSeconds: best === Infinity ? 0 : best, totalMeters };\n}\n\n/** Build a full point-to-point cost matrix (one matrix request per source point). */\nasync function buildCostMatrix(\n points: LatLng[],\n mode: TravelMode,\n routing: RoutingProvider,\n): Promise<RouteMetric[][]> {\n const rows: RouteMetric[][] = [];\n for (const source of points) {\n const row = await routing.matrix(source, points, mode);\n rows.push(row.map((metric) => metric ?? { seconds: Infinity, meters: Infinity }));\n }\n return rows;\n}\n\nasync function resolveEnd(\n end: string | LatLng,\n geocoder: GeocodingProvider,\n options: ErrandOptions,\n): Promise<Place> {\n return resolveOrigin(end, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n}\n","import type { NearbyResult } from './nearby';\nimport type { RankedPoi } from './types';\n\n/**\n * ODbL attribution for exported OpenStreetMap data. OSM's licence lets you store\n * and redistribute results (unlike the commercial APIs) **provided you keep this\n * notice** — so exporters emit it and callers should keep it with the data.\n */\nexport const ODBL_ATTRIBUTION =\n '© OpenStreetMap contributors, ODbL (https://www.openstreetmap.org/copyright)';\n\nexport interface GeoJsonFeature {\n type: 'Feature';\n /** GeoJSON uses [longitude, latitude] order (RFC 7946). */\n geometry: { type: 'Point'; coordinates: [number, number] };\n properties: Record<string, unknown>;\n}\n\nexport interface GeoJsonFeatureCollection {\n type: 'FeatureCollection';\n /** ODbL attribution, per {@link ODBL_ATTRIBUTION}. */\n attribution: string;\n features: GeoJsonFeature[];\n}\n\n/** Serialize a nearby-search result to a GeoJSON FeatureCollection (one Point per POI). */\nexport function toGeoJSON(result: NearbyResult): GeoJsonFeatureCollection {\n return {\n type: 'FeatureCollection',\n attribution: ODBL_ATTRIBUTION,\n features: result.results.map(poiToFeature),\n };\n}\n\nfunction poiToFeature(poi: RankedPoi): GeoJsonFeature {\n const properties: Record<string, unknown> = {\n rank: poi.rank,\n name: poi.name ?? null,\n category: poi.category,\n kind: poi.kind ?? null,\n distanceMeters: Math.round(poi.distanceMeters),\n osmId: poi.id,\n };\n if (poi.completeness !== undefined) properties.completeness = poi.completeness;\n if (poi.lastVerified) properties.lastVerified = poi.lastVerified;\n if (poi.openState) properties.openState = poi.openState;\n if (poi.nextChange) properties.nextChange = poi.nextChange;\n return {\n type: 'Feature',\n geometry: { type: 'Point', coordinates: [poi.location.lng, poi.location.lat] },\n properties,\n };\n}\n\nconst CSV_COLUMNS = [\n 'rank',\n 'name',\n 'category',\n 'kind',\n 'distance_m',\n 'lat',\n 'lng',\n 'osm_id',\n 'completeness',\n 'last_verified',\n 'open_state',\n] as const;\n\n/** Serialize a nearby-search result to RFC 4180 CSV (header row + one row per POI). */\nexport function toCSV(result: NearbyResult): string {\n const rows = [CSV_COLUMNS.join(',')];\n for (const poi of result.results) {\n rows.push(\n [\n poi.rank,\n csvField(poi.name ?? ''),\n poi.category,\n csvField(poi.kind ?? ''),\n Math.round(poi.distanceMeters),\n poi.location.lat,\n poi.location.lng,\n poi.id,\n poi.completeness ?? '',\n poi.lastVerified ?? '',\n poi.openState ?? '',\n ].join(','),\n );\n }\n return rows.join('\\n');\n}\n\n/** Quote a CSV field when it contains a comma, quote, or newline (doubling quotes). */\nfunction csvField(value: string): string {\n return /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n}\n","import { ODBL_ATTRIBUTION } from './export';\nimport { haversineMeters } from './geo';\nimport { resolveOrigin } from './origin';\nimport { NominatimGeocoder } from './providers/nominatim';\nimport { OverpassPlacesProvider } from './providers/overpass';\nimport { resolveCategories, suggestCategories, tagsMatchAnySelector } from './taxonomy';\nimport type {\n Category,\n GeocodingProvider,\n LatLng,\n NearbyOptions,\n PlacesProvider,\n Poi,\n} from './types';\n\n/**\n * A stored snapshot of an area's POIs. OSM data (ODbL) may be freely stored and\n * redistributed — the advantage commercial APIs forbid — so this is a portable,\n * offline-queryable dataset. Keep the `attribution` with the data.\n */\nexport interface SnapshotDataset {\n attribution: string;\n /** ISO timestamp the snapshot was captured. */\n createdAt: string;\n /** Center the snapshot was taken around. */\n center: LatLng;\n /** Radius captured, in metres. */\n radiusMeters: number;\n /** Normalized, deduplicated POIs in the area. */\n pois: Poi[];\n}\n\nexport interface SnapshotOptions {\n /** Radius to capture, in metres (default 2000). */\n radiusMeters?: number;\n /** Restrict the capture to these categories/terms (default: all amenities). */\n categories?: Array<Category | (string & {})>;\n geocoder?: GeocodingProvider;\n places?: PlacesProvider;\n language?: string;\n signal?: AbortSignal;\n /** Override the capture timestamp (ISO) — for deterministic output/tests. */\n createdAt?: string;\n}\n\nconst DEFAULT_SNAPSHOT_RADIUS_M = 2000;\n\n/**\n * Capture an area's POIs into a {@link SnapshotDataset} for offline reuse. Pair\n * with {@link DatasetPlacesProvider} to answer queries with no network calls.\n */\nexport async function snapshotArea(\n query: string | LatLng,\n options: SnapshotOptions = {},\n): Promise<SnapshotDataset> {\n const radiusMeters = options.radiusMeters ?? DEFAULT_SNAPSHOT_RADIUS_M;\n const geocoder = options.geocoder ?? new NominatimGeocoder();\n const places = options.places ?? new OverpassPlacesProvider();\n\n let selectors;\n if (options.categories && options.categories.length > 0) {\n const resolved = resolveCategories(options.categories);\n if (resolved.unknown.length > 0) {\n const detail = resolved.unknown\n .map((term) => {\n const suggestions = suggestCategories(term);\n return suggestions.length > 0\n ? `\"${term}\" (did you mean: ${suggestions.join(', ')}?)`\n : `\"${term}\"`;\n })\n .join('; ');\n throw new Error(`Unknown categor${resolved.unknown.length > 1 ? 'ies' : 'y'}: ${detail}`);\n }\n selectors = resolved.selectors;\n }\n\n const origin = await resolveOrigin(query, geocoder, {\n language: options.language,\n signal: options.signal,\n });\n\n const nearbyOptions: NearbyOptions = { radiusMeters };\n if (selectors && selectors.length > 0) nearbyOptions.selectors = selectors;\n if (options.signal) nearbyOptions.signal = options.signal;\n const pois = await places.findNearby(origin.location, nearbyOptions);\n\n return {\n attribution: ODBL_ATTRIBUTION,\n createdAt: options.createdAt ?? new Date().toISOString(),\n center: origin.location,\n radiusMeters,\n pois,\n };\n}\n\n/**\n * A {@link PlacesProvider} backed by a stored {@link SnapshotDataset} — answers\n * nearby queries entirely from memory, with no network. Use a \"lat,lng\" query\n * (no geocoding) for a fully offline pipeline.\n */\nexport class DatasetPlacesProvider implements PlacesProvider {\n readonly name = 'dataset';\n private readonly pois: Poi[];\n\n constructor(dataset: SnapshotDataset) {\n this.pois = dataset.pois;\n }\n\n async findNearby(center: LatLng, options: NearbyOptions): Promise<Poi[]> {\n const { radiusMeters, selectors } = options;\n return this.pois.filter((poi) => {\n if (haversineMeters(center, poi.location) > radiusMeters) return false;\n if (selectors && selectors.length > 0) return tagsMatchAnySelector(poi.tags, selectors);\n return true;\n });\n }\n}\n","/**\n * @proximap/core — geospatial engine for places, proximity, and amenities.\n *\n * Defaults to OpenStreetMap (Nominatim + Overpass) with no API keys, but every\n * stage is pluggable via the provider interfaces in {@link ./types}.\n */\n\n/** Library version, kept in sync with package.json. */\nexport const VERSION = '1.0.1';\n\nexport * from './types';\nexport * from './geo';\nexport * from './categories';\nexport * from './taxonomy';\nexport * from './quality';\nexport * from './hours';\nexport * from './http';\nexport * from './providers';\nexport * from './ranking';\nexport * from './routing';\nexport * from './filters';\nexport * from './origin';\nexport * from './disambiguate';\nexport * from './proximity';\nexport * from './nearby';\nexport * from './reachable';\nexport * from './gaps';\nexport * from './walkability';\nexport * from './compare';\nexport * from './errands';\nexport * from './export';\nexport * from './snapshot';\n"],"mappings":"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","names":["a","shop","tourism","leisure","a","present","a","a","a","a","clamp01","a","resolveSelectors","a","a","DEFAULT_SEARCH_RADIUS_M","a","node","mask"]}
|