@unmap/geocoding 0.12.0 → 0.14.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 +524 -16
- package/dist/index.js +506 -22
- 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 } 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: "" };
|
|
@@ -860,18 +1171,105 @@ function looksLikeRuralRoute(q) {
|
|
|
860
1171
|
return parseRuralRoute(q) != null;
|
|
861
1172
|
}
|
|
862
1173
|
|
|
1174
|
+
// src/places.ts
|
|
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);
|
|
1179
|
+
this.name = "PlacesError";
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
async function errorFrom(res, path) {
|
|
1183
|
+
const fallback = `places ${res.status} on ${path}`;
|
|
1184
|
+
let body;
|
|
1185
|
+
try {
|
|
1186
|
+
body = await res.json();
|
|
1187
|
+
} catch {
|
|
1188
|
+
body = void 0;
|
|
1189
|
+
}
|
|
1190
|
+
const reason = typeof body?.error === "string" ? body.error : void 0;
|
|
1191
|
+
const code = typeof body?.code === "string" ? body.code : void 0;
|
|
1192
|
+
const retryAfter = parseRetryAfter(res.headers?.get("retry-after") ?? null) ?? void 0;
|
|
1193
|
+
return new PlacesError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);
|
|
1194
|
+
}
|
|
1195
|
+
var Places = class {
|
|
1196
|
+
key;
|
|
1197
|
+
base;
|
|
1198
|
+
fetchImpl;
|
|
1199
|
+
retry;
|
|
1200
|
+
constructor(opts) {
|
|
1201
|
+
this.retry = opts.retry;
|
|
1202
|
+
this.key = opts.key;
|
|
1203
|
+
this.base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
|
|
1204
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Search places by name.
|
|
1208
|
+
*
|
|
1209
|
+
* Unlike `Geocoder.search()`, which returns whatever best matches and for "Calgary" is the city
|
|
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.
|
|
1212
|
+
*/
|
|
1213
|
+
search(q, opts = {}) {
|
|
1214
|
+
return this.request("/places/search", {
|
|
1215
|
+
q,
|
|
1216
|
+
limit: opts.limit != null ? String(opts.limit) : void 0,
|
|
1217
|
+
lang: opts.lang,
|
|
1218
|
+
bbox: opts.bbox?.join(",")
|
|
1219
|
+
}, opts.signal);
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Places of a category near a point.
|
|
1223
|
+
*
|
|
1224
|
+
* `category` takes a taxonomy id (`health.pharmacy`) or a word in English or French
|
|
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`.
|
|
1228
|
+
*/
|
|
1229
|
+
nearby(category, opts = {}) {
|
|
1230
|
+
return this.request("/places/nearby", {
|
|
1231
|
+
category: Array.isArray(category) ? category.join(",") : String(category),
|
|
1232
|
+
near: opts.near?.join(","),
|
|
1233
|
+
bbox: opts.bbox?.join(","),
|
|
1234
|
+
radius: opts.radius != null ? String(opts.radius) : void 0,
|
|
1235
|
+
limit: opts.limit != null ? String(opts.limit) : void 0,
|
|
1236
|
+
lang: opts.lang
|
|
1237
|
+
}, opts.signal);
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* One place by its unmap id.
|
|
1241
|
+
*
|
|
1242
|
+
* Throws `PlacesError` with status 404 when it does not exist. A malformed id is also a 404,
|
|
1243
|
+
* because from a caller's side "does not exist" and "is not a well-formed id" are the same
|
|
1244
|
+
* answer; the gateway's message distinguishes them as far as it honestly can.
|
|
1245
|
+
*/
|
|
1246
|
+
get(id, opts = {}) {
|
|
1247
|
+
return this.request(`/places/${encodeURIComponent(id)}`, { lang: opts.lang }, opts.signal);
|
|
1248
|
+
}
|
|
1249
|
+
async request(path, params, signal) {
|
|
1250
|
+
const q = new URLSearchParams({ key: this.key });
|
|
1251
|
+
for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
|
|
1252
|
+
const res = await fetchWithRetry(
|
|
1253
|
+
this.fetchImpl,
|
|
1254
|
+
`${this.base}${path}?${q}`,
|
|
1255
|
+
{ signal, headers: { accept: "application/json" } },
|
|
1256
|
+
this.retry
|
|
1257
|
+
);
|
|
1258
|
+
if (!res.ok) throw await errorFrom(res, path);
|
|
1259
|
+
return await res.json();
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
|
|
863
1263
|
// src/index.ts
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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);
|
|
869
1269
|
this.name = "GeocoderError";
|
|
870
1270
|
}
|
|
871
|
-
status;
|
|
872
|
-
code;
|
|
873
1271
|
};
|
|
874
|
-
async function
|
|
1272
|
+
async function errorFrom2(res, path) {
|
|
875
1273
|
const fallback = `geocode ${res.status} on ${path}`;
|
|
876
1274
|
let body;
|
|
877
1275
|
try {
|
|
@@ -881,21 +1279,46 @@ async function errorFrom(res, path) {
|
|
|
881
1279
|
}
|
|
882
1280
|
const reason = typeof body?.error === "string" ? body.error : void 0;
|
|
883
1281
|
const code = typeof body?.code === "string" ? body.code : void 0;
|
|
884
|
-
|
|
1282
|
+
const retryAfter = parseRetryAfter2(res.headers?.get("retry-after") ?? null) ?? void 0;
|
|
1283
|
+
return new GeocoderError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);
|
|
885
1284
|
}
|
|
886
1285
|
var Geocoder = class {
|
|
887
1286
|
key;
|
|
888
1287
|
base;
|
|
889
1288
|
fetchImpl;
|
|
1289
|
+
retry;
|
|
890
1290
|
constructor(opts) {
|
|
891
1291
|
this.key = opts.key;
|
|
892
|
-
this.
|
|
1292
|
+
this.retry = opts.retry;
|
|
1293
|
+
this.base = (opts.gateway ?? DEFAULT_GATEWAY2).replace(/\/+$/, "");
|
|
893
1294
|
this.fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
894
1295
|
}
|
|
895
1296
|
/** Full-text search. */
|
|
896
1297
|
search(q, opts = {}) {
|
|
897
1298
|
return this.query("/geocode/search", { q, ...toParams(opts) }, opts.signal);
|
|
898
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
|
+
}
|
|
899
1322
|
/** Type-ahead suggestions for partial input. */
|
|
900
1323
|
autocomplete(q, opts = {}) {
|
|
901
1324
|
return this.query("/geocode/autocomplete", { q, ...toParams(opts) }, opts.signal);
|
|
@@ -930,7 +1353,9 @@ var Geocoder = class {
|
|
|
930
1353
|
* Category search: POIs of a kind near a point, sorted by distance (metres, in each
|
|
931
1354
|
* result's `distance`). `category` takes canonical taxonomy ids ("health.pharmacy") or
|
|
932
1355
|
* plain words in English or French ("pharmacy", "pharmacie") — the gateway resolves
|
|
933
|
-
* 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`.
|
|
934
1359
|
*/
|
|
935
1360
|
nearby(category, opts = {}) {
|
|
936
1361
|
return this.query(
|
|
@@ -946,11 +1371,21 @@ var Geocoder = class {
|
|
|
946
1371
|
opts.signal
|
|
947
1372
|
);
|
|
948
1373
|
}
|
|
949
|
-
/**
|
|
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
|
+
*/
|
|
950
1379
|
reverse(lng, lat, opts = {}) {
|
|
951
1380
|
return this.query(
|
|
952
1381
|
"/geocode/reverse",
|
|
953
|
-
{
|
|
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
|
+
},
|
|
954
1389
|
opts.signal
|
|
955
1390
|
);
|
|
956
1391
|
}
|
|
@@ -990,14 +1425,27 @@ var Geocoder = class {
|
|
|
990
1425
|
opts.signal
|
|
991
1426
|
);
|
|
992
1427
|
}
|
|
993
|
-
async
|
|
1428
|
+
async postJson(path, body, signal) {
|
|
994
1429
|
const q = new URLSearchParams({ key: this.key });
|
|
995
|
-
for (const [k, v] of Object.entries(params)) if (v != null && v !== "") q.set(k, v);
|
|
996
1430
|
const res = await this.fetchImpl(`${this.base}${path}?${q}`, {
|
|
1431
|
+
method: "POST",
|
|
997
1432
|
signal,
|
|
998
|
-
headers: { accept: "application/json" }
|
|
1433
|
+
headers: { accept: "application/json", "content-type": "application/json" },
|
|
1434
|
+
body: JSON.stringify(body)
|
|
999
1435
|
});
|
|
1000
|
-
if (!res.ok) throw await
|
|
1436
|
+
if (!res.ok) throw await errorFrom2(res, path);
|
|
1437
|
+
return res.json();
|
|
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);
|
|
1001
1449
|
return res.json();
|
|
1002
1450
|
}
|
|
1003
1451
|
async query(path, params, signal) {
|
|
@@ -1005,6 +1453,20 @@ var Geocoder = class {
|
|
|
1005
1453
|
return Array.isArray(body) ? body : body.results ?? [];
|
|
1006
1454
|
}
|
|
1007
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
|
+
}
|
|
1008
1470
|
function toParams(o) {
|
|
1009
1471
|
return {
|
|
1010
1472
|
limit: o.limit != null ? String(o.limit) : void 0,
|
|
@@ -1013,32 +1475,42 @@ function toParams(o) {
|
|
|
1013
1475
|
focus: o.focus ? o.focus.join(",") : void 0,
|
|
1014
1476
|
region: o.region,
|
|
1015
1477
|
geometry: o.geometry ? "true" : void 0,
|
|
1016
|
-
layers: o.layers
|
|
1478
|
+
layers: o.layers,
|
|
1479
|
+
...treatyParams(o)
|
|
1017
1480
|
};
|
|
1018
1481
|
}
|
|
1019
1482
|
export {
|
|
1020
1483
|
AMBIGUOUS_SCORE_RATIO,
|
|
1021
1484
|
CIVIC_SEARCH_LAYERS,
|
|
1485
|
+
COMMUNITY_INCLUDE_VALUES,
|
|
1022
1486
|
CsvParser,
|
|
1023
1487
|
DEFAULT_IDENTIFY_LAYERS,
|
|
1024
1488
|
DEFAULT_QUERY_LIMIT,
|
|
1025
1489
|
ENERGY_IDENTIFY_LAYER_IDS,
|
|
1026
1490
|
ENERGY_SEARCH_LAYERS,
|
|
1491
|
+
GEOCODE_BATCH_MAX,
|
|
1027
1492
|
Geocoder,
|
|
1028
1493
|
GeocoderError,
|
|
1029
1494
|
IDENTIFY_LAYER_IDS,
|
|
1495
|
+
INCLUDE_VALUES,
|
|
1030
1496
|
LAND_IDENTIFY_LAYER_IDS,
|
|
1031
1497
|
MAX_QUERY_LIMIT,
|
|
1032
1498
|
MINING_IDENTIFY_LAYER_IDS,
|
|
1033
1499
|
OUTPUT_COLUMNS,
|
|
1034
1500
|
OUTPUT_PREFIX,
|
|
1501
|
+
Places,
|
|
1502
|
+
PlacesError,
|
|
1035
1503
|
QUERY_LAYER_IDS,
|
|
1036
1504
|
REGIONS,
|
|
1037
1505
|
REGION_CODES,
|
|
1038
1506
|
REGION_KEYS,
|
|
1039
1507
|
STRUCTURED_FIELDS,
|
|
1508
|
+
UnmapError3 as UnmapError,
|
|
1040
1509
|
alignRow,
|
|
1041
1510
|
classify,
|
|
1511
|
+
createTreatyOverlay,
|
|
1512
|
+
emptyAcknowledgementContext,
|
|
1513
|
+
emptyTerritoryContext,
|
|
1042
1514
|
encodeField,
|
|
1043
1515
|
encodeRow,
|
|
1044
1516
|
energySearchRequested,
|
|
@@ -1047,6 +1519,7 @@ export {
|
|
|
1047
1519
|
isEnergySearchSource,
|
|
1048
1520
|
isIdentifyLayer,
|
|
1049
1521
|
isLandIdentifyLayer,
|
|
1522
|
+
isTreatiesVersion,
|
|
1050
1523
|
knownIdentifyLayers,
|
|
1051
1524
|
knownQueryLayers,
|
|
1052
1525
|
knownSearchLayers,
|
|
@@ -1055,23 +1528,34 @@ export {
|
|
|
1055
1528
|
looksLikeLegalLand,
|
|
1056
1529
|
looksLikeParcel,
|
|
1057
1530
|
looksLikeRuralRoute,
|
|
1531
|
+
orderTreatyMatches,
|
|
1058
1532
|
outputValues,
|
|
1533
|
+
parseAcknowledgementContext,
|
|
1059
1534
|
parseEnergyQuery,
|
|
1060
1535
|
parseEnergySearchLayers,
|
|
1061
1536
|
parseFocus,
|
|
1062
1537
|
parseIdentifyAt,
|
|
1063
1538
|
parseIdentifyLayers,
|
|
1539
|
+
parseInclude,
|
|
1064
1540
|
parseLegalLand,
|
|
1065
1541
|
parseParcel,
|
|
1066
1542
|
parseQueryBbox,
|
|
1067
1543
|
parseQueryLayer,
|
|
1068
1544
|
parseQueryLimit,
|
|
1069
1545
|
parseRegion,
|
|
1546
|
+
parseRetryAfter3 as parseRetryAfter,
|
|
1070
1547
|
parseRuralRoute,
|
|
1071
1548
|
parseSearchLayers,
|
|
1072
1549
|
parseStructuredQuery,
|
|
1550
|
+
parseTerritoryContext,
|
|
1551
|
+
parseTreatiesVersion,
|
|
1552
|
+
parseTreatyContext,
|
|
1553
|
+
pickCommunityName,
|
|
1554
|
+
pickTreatyLabel,
|
|
1073
1555
|
regionForms,
|
|
1074
1556
|
resolveOutputHeader,
|
|
1075
|
-
toLegalLandDetails
|
|
1557
|
+
toLegalLandDetails,
|
|
1558
|
+
treatyLayerIds,
|
|
1559
|
+
unavailableTreatyContext
|
|
1076
1560
|
};
|
|
1077
1561
|
//# sourceMappingURL=index.js.map
|