@unmap/geocoding 0.10.0 → 0.13.0
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/README.md +14 -4
- package/dist/index.d.ts +801 -3
- package/dist/index.js +1078 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,743 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-free port of Township Canada's legal-land regex grammar
|
|
3
|
+
* (`api/packages/shared/regex_rules.js` + `helpers.processInput`).
|
|
4
|
+
*
|
|
5
|
+
* A successful parse is a structured candidate, not a geocoder hit. Coordinates,
|
|
6
|
+
* civic addresses, and place names return null — those stay on the civic path.
|
|
7
|
+
*/
|
|
8
|
+
type LegalLandSystem = "DLS" | "NTS" | "GTS" | "FPS" | "RIVER_LOT";
|
|
9
|
+
type LegalLandUnit = "lsd" | "qtr" | "half" | "sec" | "twp" | "nts" | "nts_sheet" | "lot" | "con" | "grid" | "section" | "unit" | "river_lot";
|
|
10
|
+
type LegalLandComponents = {
|
|
11
|
+
meridian?: string;
|
|
12
|
+
range?: string;
|
|
13
|
+
township?: string;
|
|
14
|
+
section?: string;
|
|
15
|
+
quarter?: string;
|
|
16
|
+
half?: string;
|
|
17
|
+
lsd?: string;
|
|
18
|
+
quarterUnit?: string;
|
|
19
|
+
ntsUnit?: string;
|
|
20
|
+
block?: string;
|
|
21
|
+
series?: string;
|
|
22
|
+
mapArea?: string;
|
|
23
|
+
mapSheet?: string;
|
|
24
|
+
fpsUnit?: string;
|
|
25
|
+
fpsSection?: string;
|
|
26
|
+
latDeg?: string;
|
|
27
|
+
latMin?: string;
|
|
28
|
+
lonDeg?: string;
|
|
29
|
+
lonMin?: string;
|
|
30
|
+
lot?: string;
|
|
31
|
+
concession?: string;
|
|
32
|
+
townshipName?: string;
|
|
33
|
+
kind?: string;
|
|
34
|
+
parts?: string[];
|
|
35
|
+
};
|
|
36
|
+
type LegalLandParse = {
|
|
37
|
+
system: LegalLandSystem;
|
|
38
|
+
canonical: string;
|
|
39
|
+
unit: LegalLandUnit;
|
|
40
|
+
components: LegalLandComponents;
|
|
41
|
+
province?: string;
|
|
42
|
+
};
|
|
43
|
+
declare function parseLegalLand(q: string): LegalLandParse | null;
|
|
44
|
+
/** Public `legal_land.system` values on a geocode result (LOCATION-MODEL). */
|
|
45
|
+
type LegalLandLayerSystem = "dls" | "nts" | "fps" | "on" | "river_lot";
|
|
46
|
+
/** True when `q` is a well-formed legal-land description, not a civic query. */
|
|
47
|
+
declare function looksLikeLegalLand(q: string): boolean;
|
|
48
|
+
interface LegalLandDetails {
|
|
49
|
+
system: LegalLandLayerSystem;
|
|
50
|
+
province?: string;
|
|
51
|
+
canonical: string;
|
|
52
|
+
unit: LegalLandUnit;
|
|
53
|
+
components: LegalLandComponents;
|
|
54
|
+
}
|
|
55
|
+
/** Map a parse onto the public result block. Does not invent coordinates. */
|
|
56
|
+
declare function toLegalLandDetails(parsed: LegalLandParse): LegalLandDetails;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Point-in-polygon identify: which catalog features contain a coordinate.
|
|
60
|
+
*
|
|
61
|
+
* Identify-capable layers are a subset of the gateway catalog. `parks`,
|
|
62
|
+
* `rail`, `aboriginal-lands`, `energy.ccs`, and `energy.geothermal` are
|
|
63
|
+
* catalogued for tiles/overlays but have no identify table, so they 400 here
|
|
64
|
+
* rather than return a silent miss. Energy tables are empty-safe: a missing
|
|
65
|
+
* relation is `[]` for that layer, not a 500.
|
|
66
|
+
*/
|
|
67
|
+
declare const LAND_IDENTIFY_LAYER_IDS: readonly ["municipalities", "survey.dls", "survey.nts", "survey.parcels"];
|
|
68
|
+
declare const ENERGY_IDENTIFY_LAYER_IDS: readonly ["energy.wells", "energy.pipelines", "energy.facilities"];
|
|
69
|
+
/** Routed through the Land container as extra tables. Not a fourth container. */
|
|
70
|
+
declare const MINING_IDENTIFY_LAYER_IDS: readonly ["mining.occurrences", "mining.prospectivity", "mining.sk-potential"];
|
|
71
|
+
declare const IDENTIFY_LAYER_IDS: readonly ["municipalities", "survey.dls", "survey.nts", "survey.parcels", "energy.wells", "energy.pipelines", "energy.facilities", "energy.fields", "energy.petroleum-tenure", "energy.mineral-tenure", "mining.occurrences", "mining.prospectivity", "mining.sk-potential"];
|
|
72
|
+
type IdentifyLayerId = (typeof IDENTIFY_LAYER_IDS)[number];
|
|
73
|
+
/** Default when `layers` is omitted: core only, so an account key without add-ons can identify. */
|
|
74
|
+
declare const DEFAULT_IDENTIFY_LAYERS: readonly IdentifyLayerId[];
|
|
75
|
+
declare function isIdentifyLayer(id: string): id is IdentifyLayerId;
|
|
76
|
+
declare function isLandIdentifyLayer(id: IdentifyLayerId): boolean;
|
|
77
|
+
declare function isEnergyIdentifyLayer(id: IdentifyLayerId): boolean;
|
|
78
|
+
declare function knownIdentifyLayers(): string;
|
|
79
|
+
declare function parseIdentifyLayers(raw: string | undefined): {
|
|
80
|
+
layers: IdentifyLayerId[];
|
|
81
|
+
} | {
|
|
82
|
+
error: string;
|
|
83
|
+
};
|
|
84
|
+
declare function parseIdentifyAt(at: string | undefined, lon: string | undefined, lat: string | undefined): {
|
|
85
|
+
at: [number, number];
|
|
86
|
+
} | {
|
|
87
|
+
error: string;
|
|
88
|
+
};
|
|
89
|
+
interface IdentifyOptions {
|
|
90
|
+
/** `[lng, lat]` in WGS84 degrees. */
|
|
91
|
+
at: [number, number];
|
|
92
|
+
/** Identify-capable catalog ids. Default `municipalities`. Survey layers need `legal-land`. Energy layers need `energy`. `survey.parcels` needs `agriculture`. Mining layers need `mining`. */
|
|
93
|
+
layers?: readonly string[];
|
|
94
|
+
/** Include polygons when the row has one. */
|
|
95
|
+
geometry?: boolean;
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
}
|
|
98
|
+
interface IdentifyResult {
|
|
99
|
+
layer: IdentifyLayerId;
|
|
100
|
+
id: string;
|
|
101
|
+
name: string;
|
|
102
|
+
province?: string;
|
|
103
|
+
unit?: string;
|
|
104
|
+
survey_system?: string;
|
|
105
|
+
uwi?: string;
|
|
106
|
+
licence?: string;
|
|
107
|
+
operator?: string;
|
|
108
|
+
status?: string;
|
|
109
|
+
pid?: string;
|
|
110
|
+
geometry?: {
|
|
111
|
+
type: "Polygon" | "MultiPolygon";
|
|
112
|
+
coordinates: number[][][] | number[][][][];
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
interface IdentifyResponse {
|
|
116
|
+
at: [number, number];
|
|
117
|
+
results: IdentifyResult[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Bbox query against identify-capable catalog layers. Same PostGIS tables as
|
|
122
|
+
* `/identify`; tile-only catalog ids are rejected (gateway 501), not silently empty.
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
declare const QUERY_LAYER_IDS: readonly ["municipalities", "survey.dls", "survey.nts", "survey.parcels", "energy.wells", "energy.pipelines", "energy.facilities", "energy.fields", "energy.petroleum-tenure", "energy.mineral-tenure", "mining.occurrences", "mining.prospectivity", "mining.sk-potential"];
|
|
126
|
+
type QueryLayerId = IdentifyLayerId;
|
|
127
|
+
declare const DEFAULT_QUERY_LIMIT = 50;
|
|
128
|
+
declare const MAX_QUERY_LIMIT = 50;
|
|
129
|
+
type QueryBbox = [number, number, number, number];
|
|
130
|
+
declare function knownQueryLayers(): string;
|
|
131
|
+
declare function parseQueryLayer(raw: string | undefined): {
|
|
132
|
+
layer: QueryLayerId;
|
|
133
|
+
} | {
|
|
134
|
+
unknown: string;
|
|
135
|
+
error: string;
|
|
136
|
+
} | {
|
|
137
|
+
error: string;
|
|
138
|
+
};
|
|
139
|
+
declare function parseQueryBbox(raw: string | undefined): {
|
|
140
|
+
bbox: QueryBbox;
|
|
141
|
+
} | {
|
|
142
|
+
error: string;
|
|
143
|
+
};
|
|
144
|
+
/** Missing or non-numeric `limit` falls back; a finite value is clamped to 1–50. */
|
|
145
|
+
declare function parseQueryLimit(raw: string | undefined): number;
|
|
146
|
+
interface QueryOptions {
|
|
147
|
+
/** Identify-capable catalog id. Tile-only catalog ids are a 501. */
|
|
148
|
+
layer: string;
|
|
149
|
+
/** `[minLng, minLat, maxLng, maxLat]` in WGS84 degrees. */
|
|
150
|
+
bbox: QueryBbox;
|
|
151
|
+
/** Max features. Default 50, clamped to 1–50. */
|
|
152
|
+
limit?: number;
|
|
153
|
+
/** Include geometry when the row has one. */
|
|
154
|
+
geometry?: boolean;
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
}
|
|
157
|
+
interface QueryResponse {
|
|
158
|
+
layer: QueryLayerId;
|
|
159
|
+
bbox: QueryBbox;
|
|
160
|
+
results: IdentifyResult[];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Structured forward geocoding: the caller hands over the components of an address instead of
|
|
165
|
+
* one string, and the engine keeps them apart all the way through.
|
|
166
|
+
*
|
|
167
|
+
* The distinction that matters, and the one the docs state plainly: some fields CONSTRAIN the
|
|
168
|
+
* result set and some only RANK it.
|
|
169
|
+
*
|
|
170
|
+
* - `address` and `city` are retrieval and ranking. They decide which rows are candidates and
|
|
171
|
+
* which candidate wins.
|
|
172
|
+
* - `region` and `postalcode` constrain, under one rule: **a supplied component excludes
|
|
173
|
+
* records that contradict it, and never excludes records that are silent about it.** A row
|
|
174
|
+
* in Ontario is out when you asked for Alberta; a row with no province at all stays in and
|
|
175
|
+
* comes back with `region_not_verified` in its `match_reasons`.
|
|
176
|
+
* - `country` is validated, not matched: the corpus is Canada only.
|
|
177
|
+
*
|
|
178
|
+
* The alternative — joining the fields into one string and running the free-text engine — is
|
|
179
|
+
* what this exists instead of. It would make `region` a scoring token that any row could match
|
|
180
|
+
* by coincidence, which is how "Kelowna, BC" used to return the province of British Columbia.
|
|
181
|
+
*/
|
|
182
|
+
interface StructuredQuery {
|
|
183
|
+
/** Street line: civic number and street name, as the caller would write it on an envelope. */
|
|
184
|
+
address?: string;
|
|
185
|
+
city?: string;
|
|
186
|
+
/** Two-letter code, normalised from any accepted spelling ("Québec" and "qc" both give "QC"). */
|
|
187
|
+
region?: string;
|
|
188
|
+
/** Six characters, no space, uppercase ("M5H2N2"). */
|
|
189
|
+
postalcode?: string;
|
|
190
|
+
/** Always "CA". Present so the field can be supplied and validated, not so it can vary. */
|
|
191
|
+
country?: string;
|
|
192
|
+
}
|
|
193
|
+
interface StructuredOptions extends StructuredQuery {
|
|
194
|
+
limit?: number;
|
|
195
|
+
lang?: string;
|
|
196
|
+
/** [minLng, minLat, maxLng, maxLat] hard filter, same semantics as `search`. */
|
|
197
|
+
bbox?: [number, number, number, number];
|
|
198
|
+
/** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */
|
|
199
|
+
focus?: [number, number];
|
|
200
|
+
/** Civic result types to allow (`address`, `street`, `locality`, `region`, `poi`). */
|
|
201
|
+
layers?: string;
|
|
202
|
+
signal?: AbortSignal;
|
|
203
|
+
}
|
|
204
|
+
/** The query fields, in the order the docs and the CLI list them. */
|
|
205
|
+
declare const STRUCTURED_FIELDS: readonly ["address", "city", "region", "postalcode", "country"];
|
|
206
|
+
type StructuredParseResult = {
|
|
207
|
+
query: StructuredQuery;
|
|
208
|
+
} | {
|
|
209
|
+
error: string;
|
|
210
|
+
};
|
|
211
|
+
/** Normalise and validate raw structured input. Returns the cleaned query or one error string. */
|
|
212
|
+
declare function parseStructuredQuery(raw: Partial<Record<string, string | undefined>>): StructuredParseResult;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Energy-query detect for wells / facilities.
|
|
216
|
+
*
|
|
217
|
+
* UWI grammars are Township Canada's `web/server/utils/regex.js` (`uwi`, `bc_uwi`).
|
|
218
|
+
* Well licences are only treated as energy when they carry a clear prefix (`W`,
|
|
219
|
+
* `WL`, `LIC` / `LICENCE` / `LICENSE`). Bare digits stay civic.
|
|
220
|
+
* Facility names are too generic: they are energy only when the caller asks
|
|
221
|
+
* (`?layers=well|facility` or `profile`/`source=energy`).
|
|
222
|
+
*/
|
|
223
|
+
declare const ENERGY_SEARCH_LAYERS: readonly ["well", "facility"];
|
|
224
|
+
type EnergySearchLayer = (typeof ENERGY_SEARCH_LAYERS)[number];
|
|
225
|
+
type EnergyQueryKind = "uwi" | "licence";
|
|
226
|
+
interface EnergyQuery {
|
|
227
|
+
kind: EnergyQueryKind;
|
|
228
|
+
/** Trimmed original. */
|
|
229
|
+
raw: string;
|
|
230
|
+
/** Lookup key: UWI uppercased, or licence digits. */
|
|
231
|
+
key: string;
|
|
232
|
+
}
|
|
233
|
+
declare function parseEnergyQuery(q: string): EnergyQuery | null;
|
|
234
|
+
/** True when `q` is a UWI or a prefixed well licence, not a civic address. */
|
|
235
|
+
declare function looksLikeEnergyQuery(q: string): boolean;
|
|
236
|
+
declare function parseEnergySearchLayers(raw: string | undefined): EnergySearchLayer[];
|
|
237
|
+
declare function isEnergySearchSource(raw: string | undefined): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Whether this search should take the energy branch (addon gate + table lookup).
|
|
240
|
+
* Civic strings stay civic unless the caller asked for energy layers/source.
|
|
241
|
+
*/
|
|
242
|
+
declare function energySearchRequested(q: string, opts?: {
|
|
243
|
+
layers?: string;
|
|
244
|
+
profile?: string;
|
|
245
|
+
source?: string;
|
|
246
|
+
}): boolean;
|
|
247
|
+
interface EnergyDetails {
|
|
248
|
+
uwi?: string;
|
|
249
|
+
licence?: string;
|
|
250
|
+
operator?: string;
|
|
251
|
+
status?: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* RFC 4180 CSV, streaming, with the Canadian-address-file details that break naive splitters.
|
|
256
|
+
*
|
|
257
|
+
* Hand-written rather than a dependency for two reasons that both matter here. `@unmap/cli` is
|
|
258
|
+
* deliberately a zero-runtime-dependency Node tool, and the hosted batch consumer is a
|
|
259
|
+
* Cloudflare Worker — one codec that runs in both, with no bundler shims, is worth the ~150
|
|
260
|
+
* lines. The other reason is that the failure modes below are the whole job: a parser that gets
|
|
261
|
+
* them wrong corrupts a customer's file silently, which is worse than refusing it.
|
|
262
|
+
*
|
|
263
|
+
* What it handles, and why each one is here:
|
|
264
|
+
*
|
|
265
|
+
* - **A UTF-8 BOM.** Excel writes one. Left in place it becomes part of the first header name,
|
|
266
|
+
* so `--query-column address` fails to find `address` and the error blames the user.
|
|
267
|
+
* - **Quoted commas and quoted newlines.** "Gatineau, QC" and a multi-line note field are
|
|
268
|
+
* ordinary in address exports. A record can therefore span several lines, which is why a
|
|
269
|
+
* hosted job cannot chunk a CSV at arbitrary newlines.
|
|
270
|
+
* - **Escaped quotes** (`""` inside a quoted field).
|
|
271
|
+
* - **CRLF, LF and a lone CR** as record separators, mixed within one file.
|
|
272
|
+
* - **Leading zeros** — postal codes and civic numbers. Everything stays a string, always.
|
|
273
|
+
* - **Ragged rows.** A row with fewer or more fields than the header is a fact about the file,
|
|
274
|
+
* not a crash: it is reported as an error for that row and the rest of the file continues.
|
|
275
|
+
*
|
|
276
|
+
* The parser is a state machine over chunks, so a 50 MiB file never has to be a 50 MiB string.
|
|
277
|
+
*/
|
|
278
|
+
interface CsvRecord {
|
|
279
|
+
/** 1-based index among data records, excluding the header. Stable across resumes. */
|
|
280
|
+
index: number;
|
|
281
|
+
/** Field values in header order, exactly as they appeared. Never coerced. */
|
|
282
|
+
values: string[];
|
|
283
|
+
}
|
|
284
|
+
interface CsvProblem {
|
|
285
|
+
index: number;
|
|
286
|
+
code: "too_few_fields" | "too_many_fields";
|
|
287
|
+
message: string;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Incremental parser. Feed it strings; it yields complete records.
|
|
291
|
+
*
|
|
292
|
+
* Deliberately not an async iterator: the caller owns the read loop, which is what lets the CLI
|
|
293
|
+
* and the Worker use their own very different stream APIs over the same state machine.
|
|
294
|
+
*/
|
|
295
|
+
declare class CsvParser {
|
|
296
|
+
private field;
|
|
297
|
+
private row;
|
|
298
|
+
private inQuotes;
|
|
299
|
+
/** A quote seen inside a quoted field: either an escape or the close. */
|
|
300
|
+
private pendingQuote;
|
|
301
|
+
/** A CR seen outside quotes: end of record, and a following LF is part of the same break. */
|
|
302
|
+
private pendingCr;
|
|
303
|
+
private started;
|
|
304
|
+
private rowHasContent;
|
|
305
|
+
/**
|
|
306
|
+
* Feed one chunk. Returns the records completed by it.
|
|
307
|
+
*
|
|
308
|
+
* A record that ends exactly at a chunk boundary is returned here; a partial one is held until
|
|
309
|
+
* the next chunk, or until `end()`.
|
|
310
|
+
*/
|
|
311
|
+
write(chunk: string): string[][];
|
|
312
|
+
/** Flush a final record with no trailing newline. */
|
|
313
|
+
end(): string[][];
|
|
314
|
+
/** True when the stream ended inside an unterminated quoted field. */
|
|
315
|
+
get unterminated(): boolean;
|
|
316
|
+
private finishRow;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Align a raw row against the header.
|
|
320
|
+
*
|
|
321
|
+
* Ragged rows are reported, not repaired and not thrown: a short row is padded so the caller can
|
|
322
|
+
* still carry its original values into the output, and a long row keeps its extra fields for the
|
|
323
|
+
* same reason. Discarding either would lose customer data in a file we were only asked to
|
|
324
|
+
* annotate.
|
|
325
|
+
*/
|
|
326
|
+
declare function alignRow(header: readonly string[], values: readonly string[], index: number): {
|
|
327
|
+
record: CsvRecord;
|
|
328
|
+
problem?: CsvProblem;
|
|
329
|
+
};
|
|
330
|
+
/**
|
|
331
|
+
* Quote a value for output: only when it has to be, and always the same way.
|
|
332
|
+
*
|
|
333
|
+
* The tab is in the set even though RFC 4180 does not require it. It is there for
|
|
334
|
+
* `escapeForSpreadsheet` below, which neutralises a formula by prefixing one: unquoted, a
|
|
335
|
+
* leading tab is read inconsistently across spreadsheet apps, and quoted it is unambiguous
|
|
336
|
+
* everywhere. A tab anywhere else in a field is rare enough that quoting it costs nothing.
|
|
337
|
+
*/
|
|
338
|
+
declare function encodeField(value: string): string;
|
|
339
|
+
/**
|
|
340
|
+
* Whether a value would be read as a formula by a spreadsheet.
|
|
341
|
+
*
|
|
342
|
+
* Excel, Sheets and LibreOffice all evaluate a cell beginning `=`, `+`, `-`, `@`, and CSV is the
|
|
343
|
+
* classic delivery vehicle for that (CSV injection). It matters here because a geocoder's output
|
|
344
|
+
* is a file people open in a spreadsheet, and because Canadian address data really does contain
|
|
345
|
+
* values that start this way — a negative latitude, "-123 Main St" in a malformed row.
|
|
346
|
+
*
|
|
347
|
+
* Detection is separated from mitigation on purpose: `escapeForSpreadsheet` prefixes a tab so
|
|
348
|
+
* the cell displays as text, and the caller decides whether to apply it, because prefixing
|
|
349
|
+
* changes the bytes and a pipeline reading the file programmatically wants the original.
|
|
350
|
+
*/
|
|
351
|
+
declare function looksLikeFormula(value: string): boolean;
|
|
352
|
+
declare function escapeForSpreadsheet(value: string): string;
|
|
353
|
+
/** One output line, terminated with CRLF (what RFC 4180 specifies and Excel expects). */
|
|
354
|
+
declare function encodeRow(values: readonly string[], opts?: {
|
|
355
|
+
spreadsheetSafe?: boolean;
|
|
356
|
+
}): string;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* The output schema a batch geocode writes, shared by the local CLI and the hosted service.
|
|
360
|
+
*
|
|
361
|
+
* One schema, one place, because the two are meant to be interchangeable: a customer who
|
|
362
|
+
* prototypes with `unmap geocode csv` and then moves the job to the dashboard should get the
|
|
363
|
+
* same columns in the same order with the same meanings, and diffing the two files should show
|
|
364
|
+
* nothing but the coordinates.
|
|
365
|
+
*/
|
|
366
|
+
/** Prefix on every column this adds, so a customer's own `status` column is never shadowed. */
|
|
367
|
+
declare const OUTPUT_PREFIX = "unmap_";
|
|
368
|
+
/**
|
|
369
|
+
* What happened to one input row. Distinct from `match_type`, which is about the QUALITY of an
|
|
370
|
+
* answer — a row can have `status: "matched"` and `match_type: "fallback"`, and that pairing is
|
|
371
|
+
* the common case worth reviewing.
|
|
372
|
+
*/
|
|
373
|
+
type RowStatus =
|
|
374
|
+
/** A result came back and is good enough to use without looking at it. */
|
|
375
|
+
"matched"
|
|
376
|
+
/** A result came back, but something about it should be looked at. See `unmap_review`. */
|
|
377
|
+
| "review"
|
|
378
|
+
/** The query ran and the corpus has nothing. Not an error, and it is billed. */
|
|
379
|
+
| "no_match"
|
|
380
|
+
/** The row never became a query: no address to send. Not billed. */
|
|
381
|
+
| "invalid"
|
|
382
|
+
/** The query ran and failed permanently (bad credentials, exhausted budget, repeated 5xx). */
|
|
383
|
+
| "failed"
|
|
384
|
+
/** Not attempted: the run was cancelled, paused or interrupted before reaching this row. */
|
|
385
|
+
| "unprocessed";
|
|
386
|
+
/**
|
|
387
|
+
* Why a row is in the review file. Row-level, and deliberately a superset of the result-level
|
|
388
|
+
* `match_reasons`: two of these are about the SHAPE of the answer rather than its content.
|
|
389
|
+
*/
|
|
390
|
+
type ReviewReason = GeocodeMatchReason
|
|
391
|
+
/** The result is coarser than the row asked for. */
|
|
392
|
+
| "fallback_match"
|
|
393
|
+
/** More than one candidate scored closely; the top one may not be the intended one. */
|
|
394
|
+
| "ambiguous"
|
|
395
|
+
/** Nothing came back at all. */
|
|
396
|
+
| "no_match"
|
|
397
|
+
/** The row could not be turned into a query. */
|
|
398
|
+
| "invalid_input";
|
|
399
|
+
declare const OUTPUT_COLUMNS: readonly ["unmap_status", "unmap_id", "unmap_name", "unmap_lng", "unmap_lat", "unmap_layer", "unmap_source", "unmap_match_type", "unmap_precision", "unmap_review"];
|
|
400
|
+
type OutputColumn = (typeof OUTPUT_COLUMNS)[number];
|
|
401
|
+
/**
|
|
402
|
+
* Resolve a collision between the columns this writes and the customer's own header.
|
|
403
|
+
*
|
|
404
|
+
* Silently overwriting a column called `unmap_status` would destroy data; refusing the file
|
|
405
|
+
* outright would strand anyone who ran the tool twice on its own output, which is exactly what
|
|
406
|
+
* people do. So a collision is suffixed (`unmap_status_2`) and reported, and the caller decides
|
|
407
|
+
* whether to say something. Comparison is case-insensitive because spreadsheets are.
|
|
408
|
+
*/
|
|
409
|
+
declare function resolveOutputHeader(inputHeader: readonly string[]): {
|
|
410
|
+
header: string[];
|
|
411
|
+
renamed: Array<{
|
|
412
|
+
column: OutputColumn;
|
|
413
|
+
to: string;
|
|
414
|
+
}>;
|
|
415
|
+
};
|
|
416
|
+
/**
|
|
417
|
+
* When the top result is close enough to the runner-up that picking it is a coin flip.
|
|
418
|
+
*
|
|
419
|
+
* Ratio, not a difference: BM25 scores are not on an absolute scale and are not comparable
|
|
420
|
+
* across queries, so "within 5%" means something for every query while "within 2 points" means
|
|
421
|
+
* something only for the query it was tuned on. 0.95 is deliberately tight — this flags a row
|
|
422
|
+
* for a human to look at, and a review file that flags a third of the rows gets ignored whole.
|
|
423
|
+
*/
|
|
424
|
+
declare const AMBIGUOUS_SCORE_RATIO = 0.95;
|
|
425
|
+
/**
|
|
426
|
+
* Classify one row's outcome from what the API returned.
|
|
427
|
+
*
|
|
428
|
+
* The rule the whole review file rests on: **nothing is silently discarded.** Every input row
|
|
429
|
+
* gets exactly one record in the main output, and every row that is not plainly usable gets a
|
|
430
|
+
* reason here and a line in the review file. A row that "failed quietly" is the outcome this
|
|
431
|
+
* function exists to make impossible.
|
|
432
|
+
*/
|
|
433
|
+
declare function classify(results: readonly GeocodeResult[]): {
|
|
434
|
+
status: RowStatus;
|
|
435
|
+
reasons: ReviewReason[];
|
|
436
|
+
};
|
|
437
|
+
/** The ten values, in `OUTPUT_COLUMNS` order, for one row. */
|
|
438
|
+
declare function outputValues(status: RowStatus, reasons: readonly ReviewReason[], top: GeocodeResult | undefined): string[];
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* `layers=` on `/geocode/search`, `/geocode/autocomplete` and `/geocode/structured`.
|
|
442
|
+
*
|
|
443
|
+
* The parameter already existed and already meant one thing: `layers=well,facility` sends the
|
|
444
|
+
* query to the energy container instead of the civic one. Civic result types are added to the
|
|
445
|
+
* same parameter rather than to a new one, because from the caller's side they are the same
|
|
446
|
+
* question — "which kinds of thing may come back" — and two parameters answering it would have
|
|
447
|
+
* to define what happens when both are set.
|
|
448
|
+
*
|
|
449
|
+
* What the two sets do is genuinely different, and that is why they cannot be mixed:
|
|
450
|
+
* `well`/`facility` select a BACKEND (a different container, a different corpus, gated by the
|
|
451
|
+
* `energy` add-on); the civic values FILTER results inside one corpus. `layers=address,well`
|
|
452
|
+
* has no meaning that is not a guess, so it is a 400 rather than a silent choice.
|
|
453
|
+
*
|
|
454
|
+
* Unknown values are also a 400. That is a tightening: before this, unrecognised tokens were
|
|
455
|
+
* dropped silently, so `layers=adress` searched everything and looked like it had worked.
|
|
456
|
+
*/
|
|
457
|
+
declare const CIVIC_SEARCH_LAYERS: readonly ["address", "street", "locality", "region", "poi"];
|
|
458
|
+
type CivicSearchLayer = (typeof CIVIC_SEARCH_LAYERS)[number];
|
|
459
|
+
type SearchLayersParse = {
|
|
460
|
+
kind: "none";
|
|
461
|
+
} | {
|
|
462
|
+
kind: "civic";
|
|
463
|
+
layers: CivicSearchLayer[];
|
|
464
|
+
} | {
|
|
465
|
+
kind: "energy";
|
|
466
|
+
layers: EnergySearchLayer[];
|
|
467
|
+
} | {
|
|
468
|
+
error: string;
|
|
469
|
+
};
|
|
470
|
+
/** Every accepted value, for error messages and docs. */
|
|
471
|
+
declare function knownSearchLayers(): string;
|
|
472
|
+
declare function parseSearchLayers(raw: string | undefined): SearchLayersParse;
|
|
473
|
+
/**
|
|
474
|
+
* `focus=lng,lat`: a soft proximity preference.
|
|
475
|
+
*
|
|
476
|
+
* It moves ranking toward the point and excludes nothing — a relevant result on the other side
|
|
477
|
+
* of the country still comes back, behind the near ones. That is the whole distinction from
|
|
478
|
+
* `bbox`, which is a hard filter, and the docs and the UI copy have to keep saying it: "prefer
|
|
479
|
+
* nearby" is not "restrict to this area".
|
|
480
|
+
*/
|
|
481
|
+
type FocusParse = {
|
|
482
|
+
focus: [number, number];
|
|
483
|
+
} | {
|
|
484
|
+
error: string;
|
|
485
|
+
} | undefined;
|
|
486
|
+
declare function parseFocus(raw: string | undefined): FocusParse;
|
|
487
|
+
|
|
488
|
+
/** Every recognised spelling, lowercase, mapped to its two-letter code. */
|
|
489
|
+
declare const REGIONS: Readonly<Record<string, string>>;
|
|
490
|
+
/**
|
|
491
|
+
* Spellings sorted longest first, so "newfoundland and labrador" is tried before
|
|
492
|
+
* "newfoundland". Computed after the dotted-form loop, so generated keys sort with the rest.
|
|
493
|
+
*/
|
|
494
|
+
declare const REGION_KEYS: readonly string[];
|
|
495
|
+
/** The thirteen codes, for validation messages. */
|
|
496
|
+
declare const REGION_CODES: readonly string[];
|
|
497
|
+
/**
|
|
498
|
+
* Resolve any accepted spelling to its two-letter code. Accent- and case-insensitive, so
|
|
499
|
+
* "Québec", "quebec" and "QC" all give "QC". Undefined when nothing matches.
|
|
500
|
+
*/
|
|
501
|
+
declare function parseRegion(raw: string | undefined | null): string | undefined;
|
|
502
|
+
/**
|
|
503
|
+
* Every spelling the corpus may hold for a region code, uppercased: the code itself plus each
|
|
504
|
+
* name that maps to it. The loaders do not normalise `region` (OpenAddresses passes the source
|
|
505
|
+
* value through, so `ONTARIO` and `ON` both occur), so a filter or boost must match all of them.
|
|
506
|
+
*/
|
|
507
|
+
declare function regionForms(code: string): string[];
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* ParcelMap BC PID detect. Grammar matches Township Canada
|
|
511
|
+
* `web/server/api/parcels/search.get.js`: nine digits, optional dashes
|
|
512
|
+
* (`010-867-813` or `010867813`). PIN, plan number, and parcel-name
|
|
513
|
+
* fuzzy match are not distinctive enough to steal civic search.
|
|
514
|
+
*/
|
|
515
|
+
interface ParcelQuery {
|
|
516
|
+
/** Trimmed original. */
|
|
517
|
+
raw: string;
|
|
518
|
+
/** Lookup key: `XXX-XXX-XXX`. */
|
|
519
|
+
pid: string;
|
|
520
|
+
}
|
|
521
|
+
declare function parseParcel(q: string): ParcelQuery | null;
|
|
522
|
+
/** True when `q` is a ParcelMap BC PID, not a civic address. */
|
|
523
|
+
declare function looksLikeParcel(q: string): boolean;
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Canadian rural and alternate delivery addresses.
|
|
527
|
+
*
|
|
528
|
+
* A rural route is not a place. `RR 2` names a mail carrier's circuit through a community, and
|
|
529
|
+
* the same number exists in hundreds of communities; the site of the mailbox is not in any open
|
|
530
|
+
* dataset. So this does not geocode the route. It splits the Canada Post delivery terms off the
|
|
531
|
+
* front of a query, hands the rest to civic search, and returns the parsed terms alongside the
|
|
532
|
+
* community that was found. "RR 2 Okotoks" resolves to Okotoks, labelled rural route 2, which is
|
|
533
|
+
* the honest answer: the town is known, the mailbox is not.
|
|
534
|
+
*
|
|
535
|
+
* Recognised, per Canada Post's addressing guide:
|
|
536
|
+
* RR / R.R. / RURAL ROUTE a rural route, numbered
|
|
537
|
+
* SS / SUBURBAN SERVICE a suburban service route, numbered
|
|
538
|
+
* MR / MOBILE ROUTE a mobile route, numbered
|
|
539
|
+
* GD / GENERAL DELIVERY counter pickup, never numbered
|
|
540
|
+
* SITE / COMP(ARTMENT) / BOX the position within the route
|
|
541
|
+
* STN / RPO / LCD the retail outlet the route runs from
|
|
542
|
+
*
|
|
543
|
+
* Deliberately not a locality detector: a query has to carry one of the delivery keywords above
|
|
544
|
+
* for this to fire, so an ordinary civic address is untouched.
|
|
545
|
+
*/
|
|
546
|
+
type RuralRouteKind = "rural-route" | "suburban-service" | "mobile-route" | "general-delivery";
|
|
547
|
+
interface RuralRouteQuery {
|
|
548
|
+
/** Trimmed original. */
|
|
549
|
+
raw: string;
|
|
550
|
+
kind: RuralRouteKind;
|
|
551
|
+
/** Route number as written. Absent for general delivery, which is never numbered. */
|
|
552
|
+
number?: string;
|
|
553
|
+
/** Position within the route, when given. */
|
|
554
|
+
site?: string;
|
|
555
|
+
compartment?: string;
|
|
556
|
+
box?: string;
|
|
557
|
+
/** The retail outlet the route runs from, e.g. "MAIN" in "STN MAIN". */
|
|
558
|
+
station?: string;
|
|
559
|
+
/**
|
|
560
|
+
* What is left after the delivery terms are removed: normally the community, and the only
|
|
561
|
+
* part that can actually be geocoded. Empty when the query was delivery terms and nothing
|
|
562
|
+
* else, which cannot be resolved to anywhere.
|
|
563
|
+
*/
|
|
564
|
+
remainder: string;
|
|
565
|
+
}
|
|
566
|
+
declare function parseRuralRoute(q: string): RuralRouteQuery | null;
|
|
567
|
+
/**
|
|
568
|
+
* True when `q` carries Canada Post delivery terms.
|
|
569
|
+
*
|
|
570
|
+
* Note this is NOT a routing signal in the way `looksLikeLegalLand` is: a rural-route query is
|
|
571
|
+
* still answered by civic search, on its remainder. It exists so the caller can strip the terms
|
|
572
|
+
* and label the result, not so it can pick a different backend.
|
|
573
|
+
*/
|
|
574
|
+
declare function looksLikeRuralRoute(q: string): boolean;
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Typed client for the Places API (gateway `/places/*`).
|
|
578
|
+
*
|
|
579
|
+
* It lives in `@unmap/geocoding` rather than in a package of its own because places ARE the
|
|
580
|
+
* geocoding corpus: the same rows, the same container, the same taxonomy. A twelfth npm package
|
|
581
|
+
* would also mean a twelfth entry in the changesets fixed group and a twelfth trusted publisher
|
|
582
|
+
* to configure, which is real founder work for no boundary that exists in the code.
|
|
583
|
+
*
|
|
584
|
+
* `Geocoder.nearby()` is untouched and still answers in the geocoder's own shape. This is the
|
|
585
|
+
* Place shape over the same rows.
|
|
586
|
+
*/
|
|
587
|
+
/** A Canadian point of interest, as `/places/*` returns it. */
|
|
588
|
+
interface Place {
|
|
589
|
+
/** `um:place:<id>`. Opaque: a corpus rebuild may renumber the tail, so do not parse it. */
|
|
590
|
+
id: string;
|
|
591
|
+
/** The corpus id, for `POST /places/{id}/feedback`. */
|
|
592
|
+
source_id: string;
|
|
593
|
+
name: string;
|
|
594
|
+
/** Every language and alternate spelling the corpus holds, including syllabics where recorded. */
|
|
595
|
+
names: Record<string, string>;
|
|
596
|
+
/** Taxonomy id, e.g. `finance.bank`. */
|
|
597
|
+
category: string | null;
|
|
598
|
+
/** The part before the dot, e.g. `finance`. */
|
|
599
|
+
category_group: string | null;
|
|
600
|
+
/** `[lng, lat]`, WGS84. */
|
|
601
|
+
coordinates: [number, number];
|
|
602
|
+
address: {
|
|
603
|
+
housenumber?: string;
|
|
604
|
+
street?: string;
|
|
605
|
+
city?: string;
|
|
606
|
+
region?: string;
|
|
607
|
+
postcode?: string;
|
|
608
|
+
country?: string;
|
|
609
|
+
};
|
|
610
|
+
/** Metres from the anchor. Only on `nearby()`. */
|
|
611
|
+
distance?: number;
|
|
612
|
+
/** Registry id of the dataset, for provenance. */
|
|
613
|
+
source: string;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* The envelope `/places/*` answers in.
|
|
617
|
+
*
|
|
618
|
+
* `meta` is deliberately surfaced rather than unwrapped away: a caller that cannot see
|
|
619
|
+
* `attribution` cannot comply with the licence the data came under, and one that cannot see
|
|
620
|
+
* `status` cannot tell fresh data from stale.
|
|
621
|
+
*/
|
|
622
|
+
interface PlacesResponse<T> {
|
|
623
|
+
data: T;
|
|
624
|
+
meta: {
|
|
625
|
+
status: "current" | "stale" | "partial" | "unknown";
|
|
626
|
+
attribution: string;
|
|
627
|
+
unmap_updated_at: string;
|
|
628
|
+
source?: string;
|
|
629
|
+
source_updated_at?: string | null;
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
interface PlacesOptions {
|
|
633
|
+
key: string;
|
|
634
|
+
gateway?: string;
|
|
635
|
+
fetch?: typeof fetch;
|
|
636
|
+
}
|
|
637
|
+
interface PlaceSearchOptions {
|
|
638
|
+
limit?: number;
|
|
639
|
+
lang?: string;
|
|
640
|
+
bbox?: [number, number, number, number];
|
|
641
|
+
signal?: AbortSignal;
|
|
642
|
+
}
|
|
643
|
+
interface PlaceNearbyOptions {
|
|
644
|
+
/** `[lng, lat]`. Exactly one of `near` or `bbox`. */
|
|
645
|
+
near?: [number, number];
|
|
646
|
+
bbox?: [number, number, number, number];
|
|
647
|
+
/** Metres, clamped server-side to 50..50000. */
|
|
648
|
+
radius?: number;
|
|
649
|
+
limit?: number;
|
|
650
|
+
lang?: string;
|
|
651
|
+
signal?: AbortSignal;
|
|
652
|
+
}
|
|
653
|
+
declare class PlacesError extends Error {
|
|
654
|
+
readonly status: number;
|
|
655
|
+
readonly code?: string | undefined;
|
|
656
|
+
constructor(status: number, message: string, code?: string | undefined);
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Canadian places.
|
|
660
|
+
*
|
|
661
|
+
* const places = new Places({ key: "um_live_..." });
|
|
662
|
+
* const { data } = await places.nearby("bakery", { near: [-114.07, 51.05] });
|
|
663
|
+
*
|
|
664
|
+
* ## Fields this does not carry
|
|
665
|
+
*
|
|
666
|
+
* No `phone`, `website`, `opening_hours`, `accessibility` or `operator`. The corpus does not hold
|
|
667
|
+
* them, so they are ABSENT from the response rather than null: an absent key is a statement about
|
|
668
|
+
* unmap's coverage and is true, while `"phone": null` would be a statement about the place and
|
|
669
|
+
* usually a false one. The types above reflect that rather than declaring optional fields nothing
|
|
670
|
+
* ever populates.
|
|
671
|
+
*/
|
|
672
|
+
declare class Places {
|
|
673
|
+
private readonly key;
|
|
674
|
+
private readonly base;
|
|
675
|
+
private readonly fetchImpl;
|
|
676
|
+
constructor(opts: PlacesOptions);
|
|
677
|
+
/**
|
|
678
|
+
* Search places by name.
|
|
679
|
+
*
|
|
680
|
+
* Unlike `Geocoder.search()`, which returns whatever best matches and for "Calgary" is the city
|
|
681
|
+
* itself, this returns places.
|
|
682
|
+
*/
|
|
683
|
+
search(q: string, opts?: PlaceSearchOptions): Promise<PlacesResponse<Place[]>>;
|
|
684
|
+
/**
|
|
685
|
+
* Places of a category near a point.
|
|
686
|
+
*
|
|
687
|
+
* `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French
|
|
688
|
+
* (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `"boulangerie"` works.
|
|
689
|
+
*/
|
|
690
|
+
nearby(category: string | readonly string[], opts?: PlaceNearbyOptions): Promise<PlacesResponse<Place[]>>;
|
|
691
|
+
/**
|
|
692
|
+
* One place by its unmap id.
|
|
693
|
+
*
|
|
694
|
+
* Throws `PlacesError` with status 404 when it does not exist. A malformed id is also a 404,
|
|
695
|
+
* because from a caller's side "does not exist" and "is not a well-formed id" are the same
|
|
696
|
+
* answer; the gateway's message distinguishes them as far as it honestly can.
|
|
697
|
+
*/
|
|
698
|
+
get(id: string, opts?: {
|
|
699
|
+
lang?: string;
|
|
700
|
+
signal?: AbortSignal;
|
|
701
|
+
}): Promise<PlacesResponse<Place>>;
|
|
702
|
+
private request;
|
|
703
|
+
}
|
|
704
|
+
|
|
1
705
|
interface GeocoderOptions {
|
|
2
706
|
key: string;
|
|
3
707
|
gateway?: string;
|
|
4
708
|
/** Override the fetch implementation (tests, non-global environments). */
|
|
5
709
|
fetch?: typeof fetch;
|
|
6
710
|
}
|
|
7
|
-
type GeocodeLayer = "address" | "street" | "locality" | "region" | "poi";
|
|
711
|
+
type GeocodeLayer = "address" | "street" | "locality" | "region" | "poi" | "legal_land" | "well" | "facility" | "parcel";
|
|
712
|
+
/**
|
|
713
|
+
* Which dataset the returned record came from. Conflation
|
|
714
|
+
* (`pipeline/geocoding/conflate.sql`) keeps one survivor per place and deletes the rest, so
|
|
715
|
+
* this is the SURVIVING record's source, not every dataset that agreed on the place. See
|
|
716
|
+
* `docs/services/geocoding-sources.md`.
|
|
717
|
+
*/
|
|
718
|
+
type GeocodeSource = "osm" | "openaddresses" | "statcan_nar" | "nrcan_cgndb" | "wof" | "geonames";
|
|
719
|
+
/**
|
|
720
|
+
* How well the result answers what was asked, as a category — never a probability, and never
|
|
721
|
+
* derived from `score` (which stays raw BM25 relevance).
|
|
722
|
+
*
|
|
723
|
+
* - `exact` — every component the query supplied and the corpus supports was matched.
|
|
724
|
+
* - `partial` — something supplied did not match, or could not be verified.
|
|
725
|
+
* - `fallback` — the result is COARSER than the query asked for: a civic number was requested
|
|
726
|
+
* and the result carries none. A street or locality standing in for an address is always
|
|
727
|
+
* this, never `exact`.
|
|
728
|
+
* - `unknown` — input-match diagnostics do not apply (reverse, nearby, autocomplete).
|
|
729
|
+
*/
|
|
730
|
+
type GeocodeMatchType = "exact" | "partial" | "fallback" | "unknown";
|
|
731
|
+
/**
|
|
732
|
+
* Positional resolution the SOURCE supports, separate from how well the input matched.
|
|
733
|
+
*
|
|
734
|
+
* `point` means a discrete point record in the source dataset — an address point or a POI. It
|
|
735
|
+
* is **not** a rooftop-accuracy claim, and it never means interpolated: the engine performs no
|
|
736
|
+
* interpolation. `locality` and `region` are centroids.
|
|
737
|
+
*/
|
|
738
|
+
type GeocodePrecision = "point" | "street" | "locality" | "region" | "unknown";
|
|
739
|
+
/** Stable reason codes explaining a non-`exact` `match_type`. Additive; never renamed. */
|
|
740
|
+
type GeocodeMatchReason = "housenumber_not_matched" | "housenumber_differs" | "unit_not_verified" | "unit_differs" | "postalcode_not_verified" | "postalcode_differs" | "region_not_verified" | "region_differs" | "terms_unmatched" | "fuzzy_match";
|
|
8
741
|
interface GeocodeResult {
|
|
9
742
|
id: string;
|
|
10
743
|
name: string;
|
|
@@ -12,6 +745,16 @@ interface GeocodeResult {
|
|
|
12
745
|
lng: number;
|
|
13
746
|
lat: number;
|
|
14
747
|
score?: number;
|
|
748
|
+
/** Originating dataset of the returned record. */
|
|
749
|
+
source?: GeocodeSource | string;
|
|
750
|
+
/** That dataset's own identifier for the record, when it published one. */
|
|
751
|
+
source_id?: string;
|
|
752
|
+
/** How well the result answers the query. `unknown` where input matching does not apply. */
|
|
753
|
+
match_type?: GeocodeMatchType;
|
|
754
|
+
/** Positional resolution the source supports. Not an accuracy figure. */
|
|
755
|
+
precision?: GeocodePrecision;
|
|
756
|
+
/** Why `match_type` is not `exact`. Empty or absent when it is. */
|
|
757
|
+
match_reasons?: GeocodeMatchReason[];
|
|
15
758
|
/** Canonical category id (e.g. "health.pharmacy") when the feature carries one. */
|
|
16
759
|
category?: string;
|
|
17
760
|
/** Spheroidal metres from the query anchor; only `/geocode/nearby` results carry it. */
|
|
@@ -24,13 +767,43 @@ interface GeocodeResult {
|
|
|
24
767
|
region?: string;
|
|
25
768
|
postcode?: string;
|
|
26
769
|
country?: string;
|
|
770
|
+
/** Unit/suite, where the source published one. Most of the corpus does not. */
|
|
771
|
+
unit?: string;
|
|
772
|
+
};
|
|
773
|
+
/** Structured legal-land parse; only `layer: "legal_land"` results carry it. */
|
|
774
|
+
legal_land?: LegalLandDetails;
|
|
775
|
+
/** Well / facility extras; only `layer: "well" | "facility"` results carry them. */
|
|
776
|
+
uwi?: string;
|
|
777
|
+
licence?: string;
|
|
778
|
+
operator?: string;
|
|
779
|
+
status?: string;
|
|
780
|
+
/** ParcelMap BC PID; only `layer: "parcel"` results carry it. */
|
|
781
|
+
pid?: string;
|
|
782
|
+
/** Survey-cell bounds `[minLng, minLat, maxLng, maxLat]` when the lookup row has a geom. */
|
|
783
|
+
bbox?: [number, number, number, number];
|
|
784
|
+
/** Survey polygon when `?geometry=true` and the lookup row has one. */
|
|
785
|
+
geometry?: {
|
|
786
|
+
type: "Polygon" | "MultiPolygon";
|
|
787
|
+
coordinates: number[][][] | number[][][][];
|
|
27
788
|
};
|
|
28
789
|
}
|
|
29
790
|
interface SearchOptions {
|
|
30
791
|
limit?: number;
|
|
31
792
|
lang?: string;
|
|
32
|
-
/** [minLng, minLat, maxLng, maxLat]
|
|
793
|
+
/** [minLng, minLat, maxLng, maxLat] hard filter. Excludes; see `focus` to merely prefer. */
|
|
33
794
|
bbox?: [number, number, number, number];
|
|
795
|
+
/** [lng, lat] soft proximity preference. Moves ranking toward the point, excludes nothing. */
|
|
796
|
+
focus?: [number, number];
|
|
797
|
+
/** Include the survey polygon on a legal-land hit when the table has one. */
|
|
798
|
+
geometry?: boolean;
|
|
799
|
+
/**
|
|
800
|
+
* Which result types may come back. Civic values (`address`, `street`, `locality`, `region`,
|
|
801
|
+
* `poi`) filter within the civic corpus; energy values (`well`, `facility`) select the energy
|
|
802
|
+
* corpus instead and need the energy add-on. The two sets cannot be mixed.
|
|
803
|
+
*/
|
|
804
|
+
layers?: string;
|
|
805
|
+
/** Province or territory, any accepted spelling. Excludes rows in another province. */
|
|
806
|
+
region?: string;
|
|
34
807
|
signal?: AbortSignal;
|
|
35
808
|
}
|
|
36
809
|
interface NearbyOptions {
|
|
@@ -62,6 +835,15 @@ declare class Geocoder {
|
|
|
62
835
|
search(q: string, opts?: SearchOptions): Promise<GeocodeResult[]>;
|
|
63
836
|
/** Type-ahead suggestions for partial input. */
|
|
64
837
|
autocomplete(q: string, opts?: SearchOptions): Promise<GeocodeResult[]>;
|
|
838
|
+
/**
|
|
839
|
+
* Forward geocoding from address components rather than one string.
|
|
840
|
+
*
|
|
841
|
+
* `address` and `city` decide which rows are candidates and which wins. `region` and
|
|
842
|
+
* `postalcode` narrow: they drop rows that contradict them and keep rows that are silent
|
|
843
|
+
* about them, which come back with `region_not_verified` / `postalcode_not_verified` in
|
|
844
|
+
* `match_reasons`. Needs at least one of `address`, `city` or `postalcode`.
|
|
845
|
+
*/
|
|
846
|
+
structured(opts: StructuredOptions): Promise<GeocodeResult[]>;
|
|
65
847
|
/**
|
|
66
848
|
* Category search: POIs of a kind near a point, sorted by distance (metres, in each
|
|
67
849
|
* result's `distance`). `category` takes canonical taxonomy ids ("health.pharmacy") or
|
|
@@ -74,7 +856,23 @@ declare class Geocoder {
|
|
|
74
856
|
lang?: string;
|
|
75
857
|
signal?: AbortSignal;
|
|
76
858
|
}): Promise<GeocodeResult[]>;
|
|
859
|
+
/**
|
|
860
|
+
* Which catalog features contain a point. Default layer is `municipalities`
|
|
861
|
+
* (core). `survey.dls` / `survey.nts` need the `legal-land` add-on.
|
|
862
|
+
* `energy.*` need the `energy` add-on. `mining.*` need the `mining` add-on
|
|
863
|
+
* and identify via Land extra tables. Empty until the matching PostGIS table
|
|
864
|
+
* is loaded. No mining search grammar — claim numbers stay `energy.mineral-tenure`.
|
|
865
|
+
*/
|
|
866
|
+
identify(opts: IdentifyOptions): Promise<IdentifyResponse>;
|
|
867
|
+
/**
|
|
868
|
+
* Features of one catalog layer that intersect a bbox. Same PostGIS tables as
|
|
869
|
+
* `identify`. Tile-only catalog ids are a 501. Survey layers need `legal-land`;
|
|
870
|
+
* `energy.*` need `energy`; `survey.parcels` needs `agriculture`; mining
|
|
871
|
+
* layers need `mining`. Empty until the matching table is loaded.
|
|
872
|
+
*/
|
|
873
|
+
queryLayer(opts: QueryOptions): Promise<QueryResponse>;
|
|
874
|
+
private requestJson;
|
|
77
875
|
private query;
|
|
78
876
|
}
|
|
79
877
|
|
|
80
|
-
export { type GeocodeLayer, type GeocodeResult, Geocoder, GeocoderError, type GeocoderOptions, type NearbyOptions, type SearchOptions };
|
|
878
|
+
export { AMBIGUOUS_SCORE_RATIO, CIVIC_SEARCH_LAYERS, type CivicSearchLayer, CsvParser, type CsvProblem, type CsvRecord, DEFAULT_IDENTIFY_LAYERS, DEFAULT_QUERY_LIMIT, ENERGY_IDENTIFY_LAYER_IDS, ENERGY_SEARCH_LAYERS, type EnergyDetails, type EnergyQuery, type EnergyQueryKind, type EnergySearchLayer, type FocusParse, type GeocodeLayer, type GeocodeMatchReason, type GeocodeMatchType, type GeocodePrecision, type GeocodeResult, type GeocodeSource, Geocoder, GeocoderError, type GeocoderOptions, IDENTIFY_LAYER_IDS, type IdentifyLayerId, type IdentifyOptions, type IdentifyResponse, type IdentifyResult, LAND_IDENTIFY_LAYER_IDS, type LegalLandComponents, type LegalLandDetails, type LegalLandLayerSystem, type LegalLandParse, type LegalLandSystem, type LegalLandUnit, MAX_QUERY_LIMIT, MINING_IDENTIFY_LAYER_IDS, type NearbyOptions, OUTPUT_COLUMNS, OUTPUT_PREFIX, type OutputColumn, type ParcelQuery, type Place, type PlaceNearbyOptions, type PlaceSearchOptions, Places, PlacesError, type PlacesOptions, type PlacesResponse, QUERY_LAYER_IDS, type QueryBbox, type QueryLayerId, type QueryOptions, type QueryResponse, REGIONS, REGION_CODES, REGION_KEYS, type ReviewReason, type RowStatus, type RuralRouteKind, type RuralRouteQuery, STRUCTURED_FIELDS, type SearchLayersParse, type SearchOptions, type StructuredOptions, type StructuredParseResult, type StructuredQuery, alignRow, classify, encodeField, encodeRow, energySearchRequested, escapeForSpreadsheet, isEnergyIdentifyLayer, isEnergySearchSource, isIdentifyLayer, isLandIdentifyLayer, knownIdentifyLayers, knownQueryLayers, knownSearchLayers, looksLikeEnergyQuery, looksLikeFormula, looksLikeLegalLand, looksLikeParcel, looksLikeRuralRoute, outputValues, parseEnergyQuery, parseEnergySearchLayers, parseFocus, parseIdentifyAt, parseIdentifyLayers, parseLegalLand, parseParcel, parseQueryBbox, parseQueryLayer, parseQueryLimit, parseRegion, parseRuralRoute, parseSearchLayers, parseStructuredQuery, regionForms, resolveOutputHeader, toLegalLandDetails };
|