@unmap/geocoding 0.13.0 → 0.15.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { DEFAULT_GATEWAY as DEFAULT_GATEWAY2 } from "@unmap/core";
2
+ import { DEFAULT_GATEWAY as DEFAULT_GATEWAY2, UnmapError as UnmapError2, fetchWithRetry as fetchWithRetry2, parseRetryAfter as parseRetryAfter2 } from "@unmap/core";
3
3
 
4
4
  // src/legal-land.ts
5
5
  var SEP = String.raw`(?:\s*-\s*|\s+)`;
@@ -651,6 +651,317 @@ function parseFocus(raw) {
651
651
  return { focus: [lng, lat] };
652
652
  }
653
653
 
654
+ // src/treaties.ts
655
+ var INCLUDE_VALUES = ["treaties"];
656
+ var COMMUNITY_INCLUDE_VALUES = ["territories", "acknowledgements"];
657
+ function parseInclude(raw, options = {}) {
658
+ const out = { treaties: false, territories: false, acknowledgements: false };
659
+ const known = options.community ? [...INCLUDE_VALUES, ...COMMUNITY_INCLUDE_VALUES] : [...INCLUDE_VALUES];
660
+ for (const token of (raw ?? "").split(",")) {
661
+ const value = token.trim();
662
+ if (value === "") continue;
663
+ if (value === "treaties") {
664
+ out.treaties = true;
665
+ continue;
666
+ }
667
+ if (COMMUNITY_INCLUDE_VALUES.includes(value)) {
668
+ if (!options.community) {
669
+ return { error: `include "${value}" is not available: no community source has been admitted` };
670
+ }
671
+ out[value] = true;
672
+ continue;
673
+ }
674
+ return { error: `unknown include "${value}". known: ${known.join(", ")}` };
675
+ }
676
+ return out;
677
+ }
678
+ var VERSION_SHAPE = /^(\d{4})-(\d{2})-(\d{2})\.[0-9a-f]{12}$/;
679
+ function isTreatiesVersion(value) {
680
+ const m = VERSION_SHAPE.exec(value);
681
+ if (!m) return false;
682
+ const [year, month, day] = [Number(m[1]), Number(m[2]), Number(m[3])];
683
+ const date = new Date(Date.UTC(year, month - 1, day));
684
+ return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
685
+ }
686
+ function parseTreatiesVersion(raw, treatiesIncluded) {
687
+ if (raw === void 0) return { version: void 0 };
688
+ if (!treatiesIncluded) return { error: "treaties_version requires include=treaties" };
689
+ if (!isTreatiesVersion(raw)) {
690
+ return { error: "treaties_version must be YYYY-MM-DD. followed by 12 lowercase hex characters" };
691
+ }
692
+ return { version: raw };
693
+ }
694
+ function pickTreatyLabel(labels, lang) {
695
+ const preferred = lang.toLowerCase().split("-")[0] === "fr" ? "fr" : "en";
696
+ const other = preferred === "fr" ? "en" : "fr";
697
+ for (const l of [preferred, other]) {
698
+ const text = labels[l];
699
+ if (text !== void 0 && text.trim() !== "") return { text, lang: l };
700
+ }
701
+ return void 0;
702
+ }
703
+ function orderTreatyMatches(matches) {
704
+ const byId = /* @__PURE__ */ new Map();
705
+ for (const m of matches) if (!byId.has(m.id)) byId.set(m.id, m);
706
+ const rank = (c) => c === "historic" ? 0 : 1;
707
+ return [...byId.values()].sort(
708
+ (a, b) => rank(a.category) - rank(b.category) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)
709
+ );
710
+ }
711
+ var CATEGORIES = /* @__PURE__ */ new Set(["historic", "modern"]);
712
+ var BASES = /* @__PURE__ */ new Set(["result_point", "input_point"]);
713
+ var KINDS = /* @__PURE__ */ new Set([
714
+ "treaty_area",
715
+ "overall_treaty_area",
716
+ "settlement_land",
717
+ "rights_area",
718
+ "other",
719
+ "unspecified"
720
+ ]);
721
+ var REPRESENTATIONS = /* @__PURE__ */ new Set(["approximate", "illustrative", "unspecified"]);
722
+ function isRecord(value) {
723
+ return typeof value === "object" && value !== null && !Array.isArray(value);
724
+ }
725
+ function fail(message) {
726
+ throw new TypeError(`invalid treaty context: ${message}`);
727
+ }
728
+ function isHttpUrl(value) {
729
+ if (typeof value !== "string") return false;
730
+ try {
731
+ const url = new URL(value);
732
+ return url.protocol === "https:" || url.protocol === "http:";
733
+ } catch {
734
+ return false;
735
+ }
736
+ }
737
+ function checkLabels(value, field) {
738
+ if (!isRecord(value)) fail(`${field} must be an object`);
739
+ for (const [k, v] of Object.entries(value)) {
740
+ if (k !== "en" && k !== "fr" || typeof v !== "string") fail(`${field} may only carry en/fr strings`);
741
+ }
742
+ }
743
+ function checkMatch(value, i) {
744
+ const at = `matches[${i}]`;
745
+ if (!isRecord(value)) fail(`${at} must be an object`);
746
+ for (const key of ["id", "source_id", "source_record_id", "name"]) {
747
+ if (typeof value[key] !== "string" || value[key] === "") fail(`${at}.${key} must be a non-empty string`);
748
+ }
749
+ if (!CATEGORIES.has(value.category)) fail(`${at}.category`);
750
+ if (value.name_lang !== "en" && value.name_lang !== "fr") fail(`${at}.name_lang`);
751
+ checkLabels(value.names, `${at}.names`);
752
+ if (value.subtype !== void 0 && typeof value.subtype !== "string") fail(`${at}.subtype`);
753
+ if (value.subtypes !== void 0) checkLabels(value.subtypes, `${at}.subtypes`);
754
+ if (!KINDS.has(value.geography_kind)) fail(`${at}.geography_kind`);
755
+ if (!REPRESENTATIONS.has(value.representation)) fail(`${at}.representation`);
756
+ if (!isHttpUrl(value.source_url)) fail(`${at}.source_url must be an http(s) URL`);
757
+ }
758
+ function parseTreatyContext(value) {
759
+ if (!isRecord(value)) fail("must be an object");
760
+ if (value.schema_version !== 1) fail("schema_version must be 1");
761
+ if (value.status !== "ok" && value.status !== "unavailable") fail("status");
762
+ const version = value.dataset_version;
763
+ if (version !== null && (typeof version !== "string" || !isTreatiesVersion(version))) {
764
+ fail("dataset_version must be a version or null");
765
+ }
766
+ if (value.status === "ok" && version === null) fail("an ok context needs a dataset_version");
767
+ const point2 = value.point;
768
+ if (!isRecord(point2)) fail("point must be an object");
769
+ if (typeof point2.lng !== "number" || !Number.isFinite(point2.lng) || point2.lng < -180 || point2.lng > 180) {
770
+ fail("point.lng");
771
+ }
772
+ if (typeof point2.lat !== "number" || !Number.isFinite(point2.lat) || point2.lat < -90 || point2.lat > 90) {
773
+ fail("point.lat");
774
+ }
775
+ if (!BASES.has(point2.basis)) fail("point.basis");
776
+ const coverage = value.coverage;
777
+ if (!isRecord(coverage) || coverage.scope !== "Canada" || coverage.completeness !== "partial") fail("coverage");
778
+ if (value.notice_code !== "source_limitations_apply") fail("notice_code");
779
+ if (!Array.isArray(value.matches)) fail("matches must be an array");
780
+ if (value.status === "unavailable" && value.matches.length > 0) fail("an unavailable context carries no matches");
781
+ value.matches.forEach(checkMatch);
782
+ return value;
783
+ }
784
+ function unavailableTreatyContext(point2) {
785
+ return {
786
+ schema_version: 1,
787
+ dataset_version: null,
788
+ status: "unavailable",
789
+ point: { lng: point2.lng, lat: point2.lat, basis: point2.basis },
790
+ coverage: { scope: "Canada", completeness: "partial" },
791
+ matches: [],
792
+ notice_code: "source_limitations_apply"
793
+ };
794
+ }
795
+
796
+ // src/community.ts
797
+ function pickCommunityName(names, lang, defaultLang) {
798
+ const want = lang.toLowerCase().split("-")[0];
799
+ for (const [tag, text] of Object.entries(names)) {
800
+ if (tag.toLowerCase().split("-")[0] === want && text.trim() !== "") return { text, lang: tag };
801
+ }
802
+ const fallback = names[defaultLang];
803
+ return fallback !== void 0 && fallback.trim() !== "" ? { text: fallback, lang: defaultLang } : void 0;
804
+ }
805
+ function point(p) {
806
+ return { lng: p.lng, lat: p.lat, basis: p.basis };
807
+ }
808
+ function emptyTerritoryContext(p, status) {
809
+ return {
810
+ schema_version: 1,
811
+ dataset_version: null,
812
+ status,
813
+ point: point(p),
814
+ coverage: { scope: "source_defined", completeness: "partial" },
815
+ matches: [],
816
+ notice_code: "source_limitations_apply"
817
+ };
818
+ }
819
+ function emptyAcknowledgementContext(p, status) {
820
+ return {
821
+ schema_version: 1,
822
+ dataset_version: null,
823
+ status,
824
+ point: point(p),
825
+ resources: [],
826
+ notice_code: "source_limitations_apply"
827
+ };
828
+ }
829
+ function isRecord2(v) {
830
+ return typeof v === "object" && v !== null && !Array.isArray(v);
831
+ }
832
+ function fail2(message) {
833
+ throw new TypeError(`invalid community context: ${message}`);
834
+ }
835
+ function isHttpUrl2(v) {
836
+ if (typeof v !== "string") return false;
837
+ try {
838
+ const { protocol } = new URL(v);
839
+ return protocol === "https:" || protocol === "http:";
840
+ } catch {
841
+ return false;
842
+ }
843
+ }
844
+ function checkEnvelope(v, list) {
845
+ if (v.schema_version !== 1) fail2("schema_version must be 1");
846
+ if (v.status !== "ok" && v.status !== "not_enabled" && v.status !== "unavailable") fail2("status");
847
+ const version = v.dataset_version;
848
+ if (version !== null && (typeof version !== "string" || !isTreatiesVersion(version))) fail2("dataset_version");
849
+ if (v.status === "ok" && version === null) fail2("an ok context needs a dataset_version");
850
+ if (v.notice_code !== "source_limitations_apply") fail2("notice_code");
851
+ const p = v.point;
852
+ if (!isRecord2(p) || typeof p.lng !== "number" || typeof p.lat !== "number" || !Number.isFinite(p.lng) || !Number.isFinite(p.lat)) {
853
+ fail2("point");
854
+ }
855
+ const items = v[list];
856
+ if (!Array.isArray(items)) fail2(`${list} must be an array`);
857
+ if (v.status !== "ok" && items.length > 0) fail2(`a ${String(v.status)} context carries no ${list}`);
858
+ return items;
859
+ }
860
+ function parseTerritoryContext(value) {
861
+ if (!isRecord2(value)) fail2("must be an object");
862
+ const items = checkEnvelope(value, "matches");
863
+ items.forEach((m, i) => {
864
+ if (!isRecord2(m)) fail2(`matches[${i}]`);
865
+ for (const k of ["id", "source_id", "source_record_id", "name", "name_lang", "attribution"]) {
866
+ if (typeof m[k] !== "string" || m[k] === "") fail2(`matches[${i}].${k}`);
867
+ }
868
+ if (!Array.isArray(m.community_ids) || m.community_ids.some((c) => typeof c !== "string")) fail2(`matches[${i}].community_ids`);
869
+ if (!isHttpUrl2(m.source_url)) fail2(`matches[${i}].source_url must be an http(s) URL`);
870
+ });
871
+ return value;
872
+ }
873
+ function parseAcknowledgementContext(value) {
874
+ if (!isRecord2(value)) fail2("must be an object");
875
+ const items = checkEnvelope(value, "resources");
876
+ items.forEach((r, i) => {
877
+ if (!isRecord2(r)) fail2(`resources[${i}]`);
878
+ for (const k of ["id", "source_id", "title", "language", "attribution", "applicability", "reviewed_at", "content_version"]) {
879
+ if (typeof r[k] !== "string" || r[k] === "") fail2(`resources[${i}].${k}`);
880
+ }
881
+ if (!isHttpUrl2(r.url)) fail2(`resources[${i}].url must be an http(s) URL`);
882
+ if (!Array.isArray(r.applies_to) || r.applies_to.length === 0 || r.applies_to.some((t) => typeof t !== "string")) {
883
+ fail2(`resources[${i}].applies_to must name at least one territory`);
884
+ }
885
+ if (r.text !== void 0 && typeof r.text !== "string") fail2(`resources[${i}].text`);
886
+ });
887
+ return value;
888
+ }
889
+
890
+ // src/treaty-overlay.ts
891
+ function treatyLayerIds(category) {
892
+ return [`overlay_treaties_fill_${category}`, `overlay_treaties_line_${category}`];
893
+ }
894
+ var CATEGORIES2 = ["historic", "modern"];
895
+ function createTreatyOverlay(map, client, options = {}) {
896
+ const visibility = { historic: options.historic ?? true, modern: options.modern ?? true };
897
+ let lang = options.lang ?? "en";
898
+ let latest = 0;
899
+ let destroyed = false;
900
+ let drawnVersion;
901
+ const allLayers = () => CATEGORIES2.flatMap((c) => treatyLayerIds(c)).filter((id) => map.getLayer(id));
902
+ const apply = () => {
903
+ for (const category of CATEGORIES2) {
904
+ for (const id of treatyLayerIds(category)) {
905
+ if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", visibility[category] ? "visible" : "none");
906
+ }
907
+ }
908
+ };
909
+ const hidden = () => CATEGORIES2.filter((c) => !visibility[c]);
910
+ async function select(point2, version, drawn) {
911
+ const id = ++latest;
912
+ try {
913
+ const results = await client.reverse(point2[0], point2[1], {
914
+ include: ["treaties"],
915
+ lang,
916
+ limit: 1,
917
+ ...version ? { treatiesVersion: version } : {}
918
+ });
919
+ const context = results[0]?.treaties;
920
+ if (!context) throw new Error("the geocoder returned no treaty context");
921
+ const selection = { context, drawn, hidden: hidden() };
922
+ if (id === latest && !destroyed) options.onSelect?.(selection);
923
+ return selection;
924
+ } catch (err) {
925
+ if (id === latest && !destroyed) {
926
+ options.onError?.(err instanceof Error ? err : new Error(String(err)));
927
+ }
928
+ return void 0;
929
+ }
930
+ }
931
+ const onClick = (e) => {
932
+ const layers = allLayers();
933
+ const features = layers.length > 0 ? map.queryRenderedFeatures(e.point, { layers }) : [];
934
+ const drawn = [...new Set(features.map((f) => f.properties?.id).filter((v) => typeof v === "string"))].sort();
935
+ const version = features.map((f) => f.properties?.dataset_version).find((v) => typeof v === "string");
936
+ if (version) drawnVersion = version;
937
+ void select([e.lngLat.lng, e.lngLat.lat], version ?? drawnVersion, drawn);
938
+ };
939
+ map.on("styledata", apply);
940
+ if (options.interactive !== false) map.on("click", onClick);
941
+ apply();
942
+ return {
943
+ get visibility() {
944
+ return { ...visibility };
945
+ },
946
+ setVisibility(value) {
947
+ if (value.historic !== void 0) visibility.historic = value.historic;
948
+ if (value.modern !== void 0) visibility.modern = value.modern;
949
+ apply();
950
+ },
951
+ setLanguage(next) {
952
+ lang = next;
953
+ },
954
+ lookupAt(point2, datasetVersion) {
955
+ return select(point2, datasetVersion ?? drawnVersion, []);
956
+ },
957
+ destroy() {
958
+ destroyed = true;
959
+ map.off("styledata", apply);
960
+ map.off("click", onClick);
961
+ }
962
+ };
963
+ }
964
+
654
965
  // src/regions.ts
655
966
  var BASE = {
656
967
  ab: "AB",
@@ -822,7 +1133,7 @@ function parseQueryLimit(raw) {
822
1133
  }
823
1134
 
824
1135
  // src/rural-route.ts
825
- var KINDS = [
1136
+ var KINDS2 = [
826
1137
  { pattern: /\b(?:R\.?\s?R\.?|RURAL\s+ROUTE)\s*#?\s*(\d{1,3})\b/i, kind: "rural-route", numbered: true },
827
1138
  { pattern: /\b(?:S\.?\s?S\.?|SUBURBAN\s+SERVICE)\s*#?\s*(\d{1,3})\b/i, kind: "suburban-service", numbered: true },
828
1139
  { pattern: /\b(?:M\.?\s?R\.?|MOBILE\s+ROUTE)\s*#?\s*(\d{1,3})\b/i, kind: "mobile-route", numbered: true },
@@ -842,7 +1153,7 @@ function parseRuralRoute(q) {
842
1153
  if (typeof q !== "string") return null;
843
1154
  const raw = q.trim();
844
1155
  if (raw === "") return null;
845
- const matched = KINDS.map((k) => ({ ...k, match: k.pattern.exec(raw) })).find((k) => k.match !== null);
1156
+ const matched = KINDS2.map((k) => ({ ...k, match: k.pattern.exec(raw) })).find((k) => k.match !== null);
846
1157
  if (!matched?.match) return null;
847
1158
  let rest = raw.replace(matched.pattern, " ");
848
1159
  const parsed = { raw, kind: matched.kind, remainder: "" };
@@ -861,16 +1172,12 @@ function looksLikeRuralRoute(q) {
861
1172
  }
862
1173
 
863
1174
  // src/places.ts
864
- import { DEFAULT_GATEWAY } from "@unmap/core";
865
- var PlacesError = class extends Error {
866
- constructor(status, message, code) {
867
- super(message);
868
- this.status = status;
869
- this.code = code;
1175
+ import { DEFAULT_GATEWAY, UnmapError, fetchWithRetry, parseRetryAfter } from "@unmap/core";
1176
+ var PlacesError = class extends UnmapError {
1177
+ constructor(status, message, code, retryAfterSeconds) {
1178
+ super(status, message, code, retryAfterSeconds);
870
1179
  this.name = "PlacesError";
871
1180
  }
872
- status;
873
- code;
874
1181
  };
875
1182
  async function errorFrom(res, path) {
876
1183
  const fallback = `places ${res.status} on ${path}`;
@@ -882,13 +1189,16 @@ async function errorFrom(res, path) {
882
1189
  }
883
1190
  const reason = typeof body?.error === "string" ? body.error : void 0;
884
1191
  const code = typeof body?.code === "string" ? body.code : void 0;
885
- return new PlacesError(res.status, reason ? `${reason} (${fallback})` : fallback, code);
1192
+ const retryAfter = parseRetryAfter(res.headers?.get("retry-after") ?? null) ?? void 0;
1193
+ return new PlacesError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);
886
1194
  }
887
1195
  var Places = class {
888
1196
  key;
889
1197
  base;
890
1198
  fetchImpl;
1199
+ retry;
891
1200
  constructor(opts) {
1201
+ this.retry = opts.retry;
892
1202
  this.key = opts.key;
893
1203
  this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
894
1204
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
@@ -897,7 +1207,8 @@ var Places = class {
897
1207
  * Search places by name.
898
1208
  *
899
1209
  * Unlike `Geocoder.search()`, which returns whatever best matches and for "Calgary" is the city
900
- * itself, this returns places.
1210
+ * itself, this returns places. A missing query is 400 `bad_request` (`q is required`). A bbox
1211
+ * corner outside WGS84 is the same status and code. A box outside Canada stays a filter.
901
1212
  */
902
1213
  search(q, opts = {}) {
903
1214
  return this.request("/places/search", {
@@ -911,7 +1222,9 @@ var Places = class {
911
1222
  * Places of a category near a point.
912
1223
  *
913
1224
  * `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French
914
- * (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `"boulangerie"` works.
1225
+ * (`pharmacy`, `pharmacie`); the gateway resolves aliases, so `"boulangerie"` works. An anchor
1226
+ * outside Canada rejects with status 422 and code `outside_coverage`. A longitude or latitude
1227
+ * outside WGS84, including a bbox corner, rejects with status 400 and code `bad_request`.
915
1228
  */
916
1229
  nearby(category, opts = {}) {
917
1230
  return this.request("/places/nearby", {
@@ -936,25 +1249,25 @@ var Places = class {
936
1249
  async request(path, params, signal) {
937
1250
  const q = new URLSearchParams({ key: this.key });
938
1251
  for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
939
- const res = await this.fetchImpl(`${this.base}${path}?${q}`, {
940
- signal,
941
- headers: { accept: "application/json" }
942
- });
1252
+ const res = await fetchWithRetry(
1253
+ this.fetchImpl,
1254
+ `${this.base}${path}?${q}`,
1255
+ { signal, headers: { accept: "application/json" } },
1256
+ this.retry
1257
+ );
943
1258
  if (!res.ok) throw await errorFrom(res, path);
944
1259
  return await res.json();
945
1260
  }
946
1261
  };
947
1262
 
948
1263
  // src/index.ts
949
- var GeocoderError = class extends Error {
950
- constructor(status, message, code) {
951
- super(message);
952
- this.status = status;
953
- this.code = code;
1264
+ import { UnmapError as UnmapError3, parseRetryAfter as parseRetryAfter3 } from "@unmap/core";
1265
+ var GEOCODE_BATCH_MAX = 100;
1266
+ var GeocoderError = class extends UnmapError2 {
1267
+ constructor(status, message, code, retryAfterSeconds) {
1268
+ super(status, message, code, retryAfterSeconds);
954
1269
  this.name = "GeocoderError";
955
1270
  }
956
- status;
957
- code;
958
1271
  };
959
1272
  async function errorFrom2(res, path) {
960
1273
  const fallback = `geocode ${res.status} on ${path}`;
@@ -966,14 +1279,17 @@ async function errorFrom2(res, path) {
966
1279
  }
967
1280
  const reason = typeof body?.error === "string" ? body.error : void 0;
968
1281
  const code = typeof body?.code === "string" ? body.code : void 0;
969
- return new GeocoderError(res.status, reason ? `${reason} (${fallback})` : fallback, code);
1282
+ const retryAfter = parseRetryAfter2(res.headers?.get("retry-after") ?? null) ?? void 0;
1283
+ return new GeocoderError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);
970
1284
  }
971
1285
  var Geocoder = class {
972
1286
  key;
973
1287
  base;
974
1288
  fetchImpl;
1289
+ retry;
975
1290
  constructor(opts) {
976
1291
  this.key = opts.key;
1292
+ this.retry = opts.retry;
977
1293
  this.base = (opts.gateway ?? DEFAULT_GATEWAY2).replace(/\/+$/, "");
978
1294
  this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
979
1295
  }
@@ -981,6 +1297,28 @@ var Geocoder = class {
981
1297
  search(q, opts = {}) {
982
1298
  return this.query("/geocode/search", { q, ...toParams(opts) }, opts.signal);
983
1299
  }
1300
+ /**
1301
+ * Many searches in one or more `POST /geocode/batch` calls, chunked at
1302
+ * {@link GEOCODE_BATCH_MAX}. Results stay aligned to `queries`.
1303
+ */
1304
+ async batch(queries, opts = {}) {
1305
+ if (queries.length === 0) return [];
1306
+ const out = [];
1307
+ for (let i = 0; i < queries.length; i += GEOCODE_BATCH_MAX) {
1308
+ const chunk = queries.slice(i, i + GEOCODE_BATCH_MAX);
1309
+ const body = await this.postJson(
1310
+ "/geocode/batch",
1311
+ {
1312
+ queries: chunk,
1313
+ ...toBatchBody(opts)
1314
+ },
1315
+ opts.signal
1316
+ );
1317
+ if (!Array.isArray(body)) return out.concat(chunk.map(() => []));
1318
+ out.push(...body);
1319
+ }
1320
+ return out;
1321
+ }
984
1322
  /** Type-ahead suggestions for partial input. */
985
1323
  autocomplete(q, opts = {}) {
986
1324
  return this.query("/geocode/autocomplete", { q, ...toParams(opts) }, opts.signal);
@@ -1015,7 +1353,9 @@ var Geocoder = class {
1015
1353
  * Category search: POIs of a kind near a point, sorted by distance (metres, in each
1016
1354
  * result's `distance`). `category` takes canonical taxonomy ids ("health.pharmacy") or
1017
1355
  * plain words in English or French ("pharmacy", "pharmacie") — the gateway resolves
1018
- * aliases, so this client passes the value through untouched.
1356
+ * aliases, so this client passes the value through untouched. An anchor outside Canada rejects
1357
+ * with status 422 and code `outside_coverage`; the corpus is not queried. A longitude or
1358
+ * latitude outside WGS84, including a bbox corner, rejects with status 400 and code `bad_request`.
1019
1359
  */
1020
1360
  nearby(category, opts = {}) {
1021
1361
  return this.query(
@@ -1031,11 +1371,21 @@ var Geocoder = class {
1031
1371
  opts.signal
1032
1372
  );
1033
1373
  }
1034
- /** Reverse geocode a coordinate. */
1374
+ /**
1375
+ * Reverse geocode a coordinate. A point outside Canada rejects with status 422 and code
1376
+ * `outside_coverage`. A longitude or latitude outside WGS84 rejects with status 400 and code
1377
+ * `bad_request`.
1378
+ */
1035
1379
  reverse(lng, lat, opts = {}) {
1036
1380
  return this.query(
1037
1381
  "/geocode/reverse",
1038
- { lon: String(lng), lat: String(lat), lang: opts.lang },
1382
+ {
1383
+ lon: String(lng),
1384
+ lat: String(lat),
1385
+ lang: opts.lang,
1386
+ limit: opts.limit != null ? String(opts.limit) : void 0,
1387
+ ...treatyParams(opts)
1388
+ },
1039
1389
  opts.signal
1040
1390
  );
1041
1391
  }
@@ -1075,21 +1425,48 @@ var Geocoder = class {
1075
1425
  opts.signal
1076
1426
  );
1077
1427
  }
1078
- async requestJson(path, params, signal) {
1428
+ async postJson(path, body, signal) {
1079
1429
  const q = new URLSearchParams({ key: this.key });
1080
- for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
1081
1430
  const res = await this.fetchImpl(`${this.base}${path}?${q}`, {
1431
+ method: "POST",
1082
1432
  signal,
1083
- headers: { accept: "application/json" }
1433
+ headers: { accept: "application/json", "content-type": "application/json" },
1434
+ body: JSON.stringify(body)
1084
1435
  });
1085
1436
  if (!res.ok) throw await errorFrom2(res, path);
1086
1437
  return res.json();
1087
1438
  }
1439
+ async requestJson(path, params, signal) {
1440
+ const q = new URLSearchParams({ key: this.key });
1441
+ for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
1442
+ const res = await fetchWithRetry2(
1443
+ this.fetchImpl,
1444
+ `${this.base}${path}?${q}`,
1445
+ { signal, headers: { accept: "application/json" } },
1446
+ this.retry
1447
+ );
1448
+ if (!res.ok) throw await errorFrom2(res, path);
1449
+ return res.json();
1450
+ }
1088
1451
  async query(path, params, signal) {
1089
1452
  const body = await this.requestJson(path, params, signal);
1090
1453
  return Array.isArray(body) ? body : body.results ?? [];
1091
1454
  }
1092
1455
  };
1456
+ function toBatchBody(o) {
1457
+ return {
1458
+ ...o.limit != null ? { limit: o.limit } : {},
1459
+ ...o.lang ? { lang: o.lang } : {},
1460
+ ...o.bbox ? { bbox: o.bbox.join(",") } : {},
1461
+ ...o.focus ? { focus: o.focus.join(",") } : {},
1462
+ ...o.region ? { region: o.region } : {},
1463
+ ...o.layers ? { layers: o.layers } : {}
1464
+ };
1465
+ }
1466
+ function treatyParams(o) {
1467
+ const include = o.include && o.include.length > 0 ? [...new Set(o.include)].join(",") : void 0;
1468
+ return { include, treaties_version: o.treatiesVersion };
1469
+ }
1093
1470
  function toParams(o) {
1094
1471
  return {
1095
1472
  limit: o.limit != null ? String(o.limit) : void 0,
@@ -1098,20 +1475,24 @@ function toParams(o) {
1098
1475
  focus: o.focus ? o.focus.join(",") : void 0,
1099
1476
  region: o.region,
1100
1477
  geometry: o.geometry ? "true" : void 0,
1101
- layers: o.layers
1478
+ layers: o.layers,
1479
+ ...treatyParams(o)
1102
1480
  };
1103
1481
  }
1104
1482
  export {
1105
1483
  AMBIGUOUS_SCORE_RATIO,
1106
1484
  CIVIC_SEARCH_LAYERS,
1485
+ COMMUNITY_INCLUDE_VALUES,
1107
1486
  CsvParser,
1108
1487
  DEFAULT_IDENTIFY_LAYERS,
1109
1488
  DEFAULT_QUERY_LIMIT,
1110
1489
  ENERGY_IDENTIFY_LAYER_IDS,
1111
1490
  ENERGY_SEARCH_LAYERS,
1491
+ GEOCODE_BATCH_MAX,
1112
1492
  Geocoder,
1113
1493
  GeocoderError,
1114
1494
  IDENTIFY_LAYER_IDS,
1495
+ INCLUDE_VALUES,
1115
1496
  LAND_IDENTIFY_LAYER_IDS,
1116
1497
  MAX_QUERY_LIMIT,
1117
1498
  MINING_IDENTIFY_LAYER_IDS,
@@ -1124,8 +1505,12 @@ export {
1124
1505
  REGION_CODES,
1125
1506
  REGION_KEYS,
1126
1507
  STRUCTURED_FIELDS,
1508
+ UnmapError3 as UnmapError,
1127
1509
  alignRow,
1128
1510
  classify,
1511
+ createTreatyOverlay,
1512
+ emptyAcknowledgementContext,
1513
+ emptyTerritoryContext,
1129
1514
  encodeField,
1130
1515
  encodeRow,
1131
1516
  energySearchRequested,
@@ -1134,6 +1519,7 @@ export {
1134
1519
  isEnergySearchSource,
1135
1520
  isIdentifyLayer,
1136
1521
  isLandIdentifyLayer,
1522
+ isTreatiesVersion,
1137
1523
  knownIdentifyLayers,
1138
1524
  knownQueryLayers,
1139
1525
  knownSearchLayers,
@@ -1142,23 +1528,34 @@ export {
1142
1528
  looksLikeLegalLand,
1143
1529
  looksLikeParcel,
1144
1530
  looksLikeRuralRoute,
1531
+ orderTreatyMatches,
1145
1532
  outputValues,
1533
+ parseAcknowledgementContext,
1146
1534
  parseEnergyQuery,
1147
1535
  parseEnergySearchLayers,
1148
1536
  parseFocus,
1149
1537
  parseIdentifyAt,
1150
1538
  parseIdentifyLayers,
1539
+ parseInclude,
1151
1540
  parseLegalLand,
1152
1541
  parseParcel,
1153
1542
  parseQueryBbox,
1154
1543
  parseQueryLayer,
1155
1544
  parseQueryLimit,
1156
1545
  parseRegion,
1546
+ parseRetryAfter3 as parseRetryAfter,
1157
1547
  parseRuralRoute,
1158
1548
  parseSearchLayers,
1159
1549
  parseStructuredQuery,
1550
+ parseTerritoryContext,
1551
+ parseTreatiesVersion,
1552
+ parseTreatyContext,
1553
+ pickCommunityName,
1554
+ pickTreatyLabel,
1160
1555
  regionForms,
1161
1556
  resolveOutputHeader,
1162
- toLegalLandDetails
1557
+ toLegalLandDetails,
1558
+ treatyLayerIds,
1559
+ unavailableTreatyContext
1163
1560
  };
1164
1561
  //# sourceMappingURL=index.js.map