PageSourceSearch

https://crashcountnyc.com/js/find_api_client.670aaa06dc0a060d9b8dd…cbba8f74fd6569a3de8f386b6dc4c99a23585069.js

js crashcountnyc.com collected 2026-10-01 00:31:49 UTC 10,365 bytes, 252 lines download raw bytes

1(function initFindApiClient(root, factory) {
2  const api = factory(root);
3  if (typeof module === "object" && module.exports) {
4    module.exports = api;
5    return;
6  }
7  const namespace = root.CrashCountFindCore = root.CrashCountFindCore || {};
8  Object.assign(namespace, api);
9})(typeof globalThis !== "undefined" ? globalThis : this, function factory(root) {
10  function createApiClient(deps) {
11    const config = deps.config || {};
12    const state = deps.state;
13    const fetchImpl = deps.fetchImpl || root.fetch;
14    const warn = deps.warn || function noop() {};
15
16    function cacheKey(prefix, id, version) {
17      return `${prefix}:${String(version || "latest")}:${String(id || "").trim()}`;
18    }
19
20    async function fetchHex(hexId) {
21      if (state.hexCache.has(hexId)) return state.hexCache.get(hexId);
22      const version = deps.resolvedDataVersion();
23      const intId = deps.h3ToIntString(hexId);
24      const url = `${config.apiBase}/hex/${deps.dataRes}/${intId}?version=${encodeURIComponent(version)}`;
25      const res = await fetchImpl(url);
26      if (res.status === 404) {
27        state.hexCache.set(hexId, null);
28        return null;
29      }
30      if (!res.ok) throw new Error(`hex ${res.status}`);
31      const data = await res.json();
32      const item = data.hex || null;
33      state.hexCache.set(hexId, item);
34      return item;
35    }
36
37    async function fetchHexBatch(hexIds, options) {
38      if (!hexIds.length) return [];
39      const version = deps.resolvedDataVersion();
40      const out = [];
41      const chunks = [];
42      const onChunk = options && typeof options.onChunk === "function" ? options.onChunk : null;
43      for (let i = 0; i < hexIds.length; i += deps.hexBatchChunkSize) {
44        chunks.push(hexIds.slice(i, i + deps.hexBatchChunkSize));
45      }
46
47      const fetchChunk = async function fetchChunk(chunk) {
48        const map = new Map();
49        const payload = {
50          res: deps.dataRes,
51          h3: chunk.map(function mapCell(cell) {
52            const intId = deps.h3ToIntString(cell);
53            map.set(intId, cell);
54            return intId;
55          }),
56          version: version
57        };
58        try {
59          const res = await fetchImpl(`${config.apiBase}/hex/batch?version=${encodeURIComponent(version)}`, {
60            method: "POST",
61            headers: { "content-type": "application/json" },
62            body: JSON.stringify(payload)
63          });
64          if (!res.ok) throw new Error(`hex batch ${res.status}`);
65          const data = await res.json();
66          const items = data.hexes || [];
67          items.forEach(function cacheItem(item) {
68            const key = map.get(String(item.h3));
69            if (key) state.hexCache.set(key, item);
70          });
71          const missing = Array.isArray(data.missing) ? data.missing : [];
72          missing.forEach(function cacheMissing(id) {
73            const key = map.get(String(id));
74            if (key && !state.hexCache.has(key)) state.hexCache.set(key, null);
75          });
76          if (onChunk) onChunk(items);
77          return items;
78        } catch (err) {
79          warn("[hex-find] hex batch", err);
80          return [];
81        }
82      };
83
84      let index = 0;
85      const workerCount = Math.min(deps.hexBatchConcurrency, chunks.length);
86      const workers = Array.from({ length: workerCount }, async function worker() {
87        while (index < chunks.length) {
88          const chunk = chunks[index];
89          index += 1;
90          const items = await fetchChunk(chunk);
91          if (items.length) out.push.apply(out, items);
92        }
93      });
94      await Promise.all(workers);
95      return out;
96    }
97
98    async function fetchStreetSuggestions(query, options) {
99      if (!config.apiBase) return [];
100      const versionOverride = options && options.version ? String(options.version).trim() : "";
101      const version = deps.resolvedDataVersion(versionOverride);
102      const url = `${config.apiBase}/search?q=${encodeURIComponent(query)}&version=${encodeURIComponent(version)}`;
103      const res = await fetchImpl(url);
104      if (!res.ok) throw new Error(`street search ${res.status}`);
105      const data = await res.json();
106      const streets = Array.isArray(data.streets) ? data.streets : [];
107      return streets.map(function mapStreet(street) {
108        return {
109          type: "Feature",
110          geometry: Array.isArray(street.center) ? { type: "Point", coordinates: street.center } : null,
111          properties: {
112            layer: "street",
113            label: street.label || street.name || "Street",
114            street_id: street.street_id,
115            borough: street.borough
116          }
117        };
118      });
119    }
120
121    async function fetchStreetMeta(streetId, options) {
122      const allowFallback = !options || options.allowFallback !== false;
123      const versionOverride = options && options.version ? String(options.version).trim() : "";
124      const version = deps.resolvedDataVersion(versionOverride);
125      const key = cacheKey("street", streetId, version);
126      if (state.streetCache.has(key)) return state.streetCache.get(key);
127      const url = `${config.apiBase}/street/${encodeURIComponent(streetId)}?version=${encodeURIComponent(version)}`;
128      const res = await fetchImpl(url);
129      if (res.ok) {
130        const data = await res.json();
131        const item = data.street || null;
132        state.streetCache.set(key, item);
133        return item;
134      }
135      if (allowFallback && res.status === 404 && streetId.endsWith("|all")) {
136        const fallbackId = streetId.replace(/\|all$/, "|0");
137        if (fallbackId !== streetId) {
138          const fallback = await fetchStreetMeta(fallbackId, { allowFallback: false, version: version });
139          state.streetCache.set(key, fallback);
140          return fallback;
141        }
142      }
143      throw new Error(`street ${res.status}`);
144    }
145
146    async function resolveStreetMeta(streetId, label, options) {
147      const resolvedOptions = options && typeof options === "object" ? options : {};
148      const item = await fetchStreetMeta(streetId, resolvedOptions);
149      return { streetId: streetId, item: item };
150    }
151
152    async function fetchBikeLaneSuggestions(query, options) {
153      if (!config.apiBase) return [];
154      const versionOverride = options && options.version ? String(options.version).trim() : "";
155      const version = deps.resolvedDataVersion(versionOverride);
156      const url = `${config.apiBase}/bike-lanes/search?q=${encodeURIComponent(query)}&version=${encodeURIComponent(version)}`;
157      const res = await fetchImpl(url);
158      if (!res.ok) throw new Error(`bike lane search ${res.status}`);
159      const data = await res.json();
160      const bikeLanes = Array.isArray(data.bike_lanes) ? data.bike_lanes : [];
161      return bikeLanes.map(function mapBikeLane(lane) {
162        return {
163          type: "Feature",
164          geometry: Array.isArray(lane.center) ? { type: "Point", coordinates: lane.center } : null,
165          properties: {
166            layer: "bike_lane",
167            label: lane.label || "Bike lane",
168            bike_lane_id: lane.bike_lane_id,
169            borough: lane.borough,
170            facility_group: lane.facility_group || ""
171          }
172        };
173      });
174    }
175
176    async function fetchBikeLaneMeta(bikeLaneId, options) {
177      const allowFallback = !options || options.allowFallback !== false;
178      const versionOverride = options && options.version ? String(options.version).trim() : "";
179      const version = deps.resolvedDataVersion(versionOverride);
180      const key = cacheKey("bike_lane", bikeLaneId, version);
181      if (state.bikeLaneCache.has(key)) return state.bikeLaneCache.get(key);
182      const url = `${config.apiBase}/bike-lane/${encodeURIComponent(bikeLaneId)}?version=${encodeURIComponent(version)}`;
183      const res = await fetchImpl(url);
184      if (res.ok) {
185        const data = await res.json();
186        const item = data.bike_lane || null;
187        state.bikeLaneCache.set(key, item);
188        return item;
189      }
190      if (allowFallback && res.status === 404 && bikeLaneId.endsWith("|all")) {
191        const fallbackId = bikeLaneId.replace(/\|all$/, "|0");
192        if (fallbackId !== bikeLaneId) {
193          const fallback = await fetchBikeLaneMeta(fallbackId, { allowFallback: false, version: version });
194          state.bikeLaneCache.set(key, fallback);
195          return fallback;
196        }
197      }
198      throw new Error(`bike lane ${res.status}`);
199    }
200
201    async function resolveBikeLaneMeta(bikeLaneId, label, options) {
202      const resolvedOptions = options && typeof options === "object" ? options : {};
203      const item = await fetchBikeLaneMeta(bikeLaneId, resolvedOptions);
204      return { bikeLaneId: bikeLaneId, item: item };
205    }
206
207    async function fetchCollision(collisionId) {
208      if (state.collisionCache.has(collisionId)) return state.collisionCache.get(collisionId);
209      const version = deps.resolvedDataVersion();
210      const url = `${config.apiBase}/crash/${collisionId}?version=${encodeURIComponent(version)}`;
211      const res = await fetchImpl(url);
212      if (res.status === 404) {
213        if (version) {
214          try {
215            const fallbackRes = await fetchImpl(`${config.apiBase}/crash/${collisionId}`);
216            if (fallbackRes.ok) {
217              const fallbackData = await fallbackRes.json();
218              const fallbackItem = fallbackData.collision || null;
219              state.collisionCache.set(collisionId, fallbackItem);
220              return fallbackItem;
221            }
222          } catch (err) {
223            warn("[hex-find] collision fallback failed", err);
224          }
225        }
226        state.collisionCache.set(collisionId, null);
227        return null;
228      }
229      if (!res.ok) throw new Error(`collision ${res.status}`);
230      const data = await res.json();
231      const item = data.collision || null;
232      state.collisionCache.set(collisionId, item);
233      return item;
234    }
235
236    return {
237      fetchBikeLaneMeta: fetchBikeLaneMeta,
238      fetchBikeLaneSuggestions: fetchBikeLaneSuggestions,
239      fetchCollision: fetchCollision,
240      fetchHex: fetchHex,
241      fetchHexBatch: fetchHexBatch,
242      fetchStreetMeta: fetchStreetMeta,
243      fetchStreetSuggestions: fetchStreetSuggestions,
244      resolveBikeLaneMeta: resolveBikeLaneMeta,
245      resolveStreetMeta: resolveStreetMeta
246    };
247  }
248
249  return {
250    createApiClient: createApiClient
251  };
252});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.