@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/README.md +5 -2
- package/dist/canada-service-area.d.ts +15 -0
- package/dist/canada-service-area.js +56 -0
- package/dist/canada-service-area.js.map +1 -0
- package/dist/index.d.ts +401 -22
- package/dist/index.js +431 -34
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
-
{
|
|
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
|
|
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
|