From 083fd87ea8fe976122db54bc1358e2a5a229dcf1 Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Wed, 7 Oct 2026 12:39:00 -0700 Subject: [PATCH] Switch to Esri basemap and geocoder with Nominatim fallback --- js/app.js | 80 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/js/app.js b/js/app.js index aadc04e3..f77cf591 100644 --- a/js/app.js +++ b/js/app.js @@ -87,11 +87,15 @@ function geomDistanceKm(geom, latlng) { function initMap() { const map = L.map("map", { preferCanvas: true }).setView([34.3, -111.7], 6); - L.tileLayer("https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png", { - maxZoom: 19, - subdomains: "abcd", - attribution: "© OpenStreetMap © CARTO" + const esri = "https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/"; + L.tileLayer(esri + "World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}", { + maxZoom: 16, + attribution: "Tiles © Esri, HERE, Garmin, © OpenStreetMap contributors" }).addTo(map); + const labels = map.createPane("labels"); + labels.style.zIndex = 450; + labels.style.pointerEvents = "none"; + L.tileLayer(esri + "World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}", { maxZoom: 16, pane: "labels" }).addTo(map); S.layers.fiber = L.layerGroup().addTo(map); S.layers.contracts = L.layerGroup().addTo(map); S.layers.focus = L.layerGroup().addTo(map); @@ -108,7 +112,35 @@ function initMap() { S.map = map; } -async function geocode(q) { +async function geocodeEsri(q) { + const url = "https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates?" + new URLSearchParams({ + SingleLine: q, + f: "json", + outFields: "Match_addr,RegionAbbr,Subregion", + sourceCountry: "USA", + maxLocations: "1", + outSR: "4326" + }); + try { + const r = await fetch(url); + if (!r.ok) return null; + const d = await r.json(); + const c = (d.candidates || [])[0]; + if (!c || c.score < 80) return null; + const a = c.attributes || {}; + return { + lat: c.location.y, + lng: c.location.x, + label: a.Match_addr || c.address, + state: a.RegionAbbr || null, + county: (a.Subregion || "").replace(/\s+County$/i, "").trim() + }; + } catch { + return null; + } +} + +async function geocodeOsm(q) { const url = "https://nominatim.openstreetmap.org/search?" + new URLSearchParams({ q, format: "jsonv2", @@ -116,20 +148,28 @@ async function geocode(q) { limit: "1", countrycodes: "us" }); - const r = await fetch(url, { headers: { "Accept-Language": "en" } }); - if (!r.ok) throw new Error("The address service did not respond. Try again in a minute."); - const list = await r.json(); - if (!list.length) return null; - const hit = list[0]; - const a = hit.address || {}; - const iso = a["ISO3166-2-lvl4"] || ""; - return { - lat: Number(hit.lat), - lng: Number(hit.lon), - label: hit.display_name.split(",").slice(0, 4).join(",").trim(), - state: iso.startsWith("US-") ? iso.slice(3) : null, - county: (a.county || "").replace(/\s+County$/i, "").trim() - }; + try { + const r = await fetch(url, { headers: { "Accept-Language": "en" } }); + if (!r.ok) return null; + const list = await r.json(); + if (!list.length) return null; + const hit = list[0]; + const a = hit.address || {}; + const iso = a["ISO3166-2-lvl4"] || ""; + return { + lat: Number(hit.lat), + lng: Number(hit.lon), + label: hit.display_name.split(",").slice(0, 4).join(",").trim(), + state: iso.startsWith("US-") ? iso.slice(3) : null, + county: (a.county || "").replace(/\s+County$/i, "").trim() + }; + } catch { + return null; + } +} + +async function geocode(q) { + return (await geocodeEsri(q)) || (await geocodeOsm(q)); } function shard(cache, dir, id) { @@ -412,7 +452,7 @@ function renderMeta() { a.rel = "noopener"; p1.append(a); f.append(p1); - f.append(el("p", null, "Address search by OpenStreetMap Nominatim.")); + f.append(el("p", null, "Address search by Esri, with OpenStreetMap Nominatim as backup.")); } async function check(q) {