PageSourceSearch

https://crashcountnyc.com/js/find_snapshot_url.d336b30f8cf2d656d6f…50062f2eecf5fd69f6dcb7a0001c93bde03e580c.js

js crashcountnyc.com collected 2026-10-01 00:31:57 UTC 23,170 bytes, 583 lines download raw bytes

1(function initFindSnapshotUrl(root, factory) {
2  const api = factory();
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() {
10  const SELECTION_SHARE_PARAM_KEYS = [
11    "q",
12    "label",
13    "layer",
14    "lat",
15    "lon",
16    "street_id",
17    "street_a",
18    "street_b",
19    "bike_lane_id",
20    "crash_id",
21    "collision_id",
22    "radius_m",
23    "radius",
24    "sel",
25    "auto"
26  ];
27
28  const STATE_SHARE_PARAM_KEYS = [
29    "compare",
30    "recent_start",
31    "recent_end",
32    "earlier_start",
33    "earlier_end",
34    "modes",
35    "mode"
36  ];
37
38  const SHARE_PARAM_KEYS = [
39    ...SELECTION_SHARE_PARAM_KEYS,
40    ...STATE_SHARE_PARAM_KEYS
41  ];
42
43  const SNAPSHOT_BOOTSTRAP_PARAM_KEYS = [
44    "version",
45    "window_start",
46    "window_end",
47    "exclude_highways"
48  ];
49
50  function decodeLabelValue(value) {
51    if (!value) return "";
52    try {
53      return decodeURIComponent(value);
54    } catch (err) {
55      return value;
56    }
57  }
58
59  function buildFindRedirectUrl(dataset, origin) {
60    const url = new URL("/find/", origin);
61    const label = decodeLabelValue(dataset.label || "");
62    if (label) url.searchParams.set("q", label);
63    if (label) url.searchParams.set("label", label);
64    url.searchParams.set("auto", "1");
65    if (dataset.layer) url.searchParams.set("layer", dataset.layer);
66    if (dataset.lat) url.searchParams.set("lat", dataset.lat);
67    if (dataset.lon) url.searchParams.set("lon", dataset.lon);
68    if (dataset.streetId) url.searchParams.set("street_id", dataset.streetId);
69    if (dataset.streetA) url.searchParams.set("street_a", dataset.streetA);
70    if (dataset.streetB) url.searchParams.set("street_b", dataset.streetB);
71    if (dataset.bikeLaneId) url.searchParams.set("bike_lane_id", dataset.bikeLaneId);
72    if (dataset.crashId) url.searchParams.set("crash_id", dataset.crashId);
73    const modes = decodeLabelValue(dataset.modes || "");
74    const mode = decodeLabelValue(dataset.mode || "");
75    if (modes) {
76      url.searchParams.set("modes", modes);
77    } else if (mode) {
78      url.searchParams.set("mode", mode);
79    } else if (dataset.layer === "bike_lane") {
80      url.searchParams.set("mode", "cyclist");
81    }
82    const source = String(dataset.source || "").trim();
83    if (source) url.searchParams.set("source", source);
84    return url.toString();
85  }
86
87  function parseSelectionParams(params) {
88    if (!params) return null;
89    const label = decodeLabelValue(params.get("label") || params.get("q") || "");
90    const layer = params.get("layer") || "";
91    const latRaw = params.get("lat");
92    const lonRaw = params.get("lon");
93    const lat = latRaw ? Number(latRaw) : NaN;
94    const lon = lonRaw ? Number(lonRaw) : NaN;
95    const radiusRaw = params.get("radius_m") || params.get("radius") || "";
96    const radiusMeters = radiusRaw ? Number(radiusRaw) : NaN;
97    const streetId = params.get("street_id") || "";
98    const streetA = params.get("street_a") || "";
99    const streetB = params.get("street_b") || "";
100    const bikeLaneId = params.get("bike_lane_id") || "";
101    const crashId = params.get("crash_id") || params.get("collision_id") || "";
102    const hasLatLon = Number.isFinite(lat) && Number.isFinite(lon);
103    if (!streetId && !bikeLaneId && !(streetA && streetB) && !hasLatLon && !crashId) return null;
104    return {
105      bikeLaneId: bikeLaneId,
106      crashId: crashId,
107      label: label,
108      lat: lat,
109      layer: layer,
110      lon: lon,
111      radiusMeters: Number.isFinite(radiusMeters) && radiusMeters > 0 ? radiusMeters : null,
112      streetA: streetA,
113      streetB: streetB,
114      streetId: streetId
115    };
116  }
117
118  function clearLegacyShareParams(params) {
119    if (!params) return;
120    SHARE_PARAM_KEYS.forEach(function deleteShareParam(key) {
121      params.delete(key);
122    });
123  }
124
125  function clearSelectionShareParams(params) {
126    if (!params) return;
127    SELECTION_SHARE_PARAM_KEYS.forEach(function deleteShareParam(key) {
128      params.delete(key);
129    });
130  }
131
132  function clearSnapshotBootstrapParams(params) {
133    if (!params) return;
134    SNAPSHOT_BOOTSTRAP_PARAM_KEYS.forEach(function deleteBootstrapParam(key) {
135      params.delete(key);
136    });
137  }
138
139  function finalizeSnapshotSidUrl(params, options) {
140    if (!params) return;
141    const preserveSelectionFallback = !!(options && options.preserveSelectionFallback);
142    const preserveBootstrapFallback = !!(options && options.preserveBootstrapFallback);
143    const syncVersionUrlParam = options && typeof options.syncVersionUrlParam === "function"
144      ? options.syncVersionUrlParam
145      : function noop() {};
146    if (!preserveSelectionFallback) {
147      clearLegacyShareParams(params);
148    }
149    syncVersionUrlParam(params);
150    if (!preserveBootstrapFallback) {
151      clearSnapshotBootstrapParams(params);
152    }
153  }
154
155  function formatShareCoord(value) {
156    if (!Number.isFinite(value)) return "";
157    return Number(value).toFixed(6);
158  }
159
160  function serializeShareSelection(selection) {
161    if (!selection) return null;
162    const label = (selection.label || "").trim();
163    const encodedLabel = label ? encodeURIComponent(label) : "";
164    const lat = formatShareCoord(selection.lat);
165    const lon = formatShareCoord(selection.lon);
166    const radius = Number.isFinite(selection.radiusMeters) && selection.radiusMeters > 0
167      ? String(Math.round(selection.radiusMeters))
168      : "";
169    if (selection.streetId) {
170      return ["street", encodeURIComponent(selection.streetId), lat, lon, encodedLabel].join("|");
171    }
172    if (selection.bikeLaneId) {
173      return ["bike_lane", encodeURIComponent(selection.bikeLaneId), lat, lon, encodedLabel].join("|");
174    }
175    if (selection.streetA && selection.streetB) {
176      return [
177        "intersection",
178        encodeURIComponent(selection.streetA),
179        encodeURIComponent(selection.streetB),
180        lat,
181        lon,
182        encodedLabel
183      ].join("|");
184    }
185    if (selection.crashId) {
186      const parts = ["crash", encodeURIComponent(String(selection.crashId)), lat, lon, encodedLabel];
187      if (radius) parts.push(radius);
188      return parts.join("|");
189    }
190    if (lat && lon) {
191      const parts = ["point", lat, lon, selection.layer || "address", encodedLabel];
192      if (radius) parts.push(radius);
193      return parts.join("|");
194    }
195    return null;
196  }
197
198  function parseShareCoord(value) {
199    if (!value) return NaN;
200    const num = Number(value);
201    return Number.isFinite(num) ? num : NaN;
202  }
203
204  function shareKeyForSelection(selection) {
205    if (!selection) return null;
206    if (selection.streetId) return `street:${selection.streetId}`;
207    if (selection.bikeLaneId) return `bike_lane:${selection.bikeLaneId}`;
208    if (selection.streetA && selection.streetB) return `intersection:${selection.streetA}|${selection.streetB}`;
209    if (selection.crashId) return `crash:${selection.crashId}`;
210    if (Number.isFinite(selection.lat) && Number.isFinite(selection.lon)) {
211      const layer = selection.layer || "address";
212      const radius = Number.isFinite(selection.radiusMeters) && selection.radiusMeters > 0
213        ? Math.round(selection.radiusMeters)
214        : null;
215      const radiusToken = radius ? `:r${radius}` : "";
216      return `point:${layer}:${formatShareCoord(selection.lat)},${formatShareCoord(selection.lon)}${radiusToken}`;
217    }
218    return null;
219  }
220
221  function parseShareSelection(value) {
222    if (!value) return null;
223    const parts = String(value).split("|");
224    const type = parts[0];
225    if (type === "street") {
226      const tailIndex = Math.max(1, parts.length - 3);
227      return {
228        streetId: decodeLabelValue(parts.slice(1, tailIndex).join("|") || parts[1] || ""),
229        layer: "street",
230        lat: parseShareCoord(parts[parts.length - 3]),
231        lon: parseShareCoord(parts[parts.length - 2]),
232        label: decodeLabelValue(parts[parts.length - 1] || "")
233      };
234    }
235    if (type === "bike_lane") {
236      const tailIndex = Math.max(1, parts.length - 3);
237      return {
238        bikeLaneId: decodeLabelValue(parts.slice(1, tailIndex).join("|") || parts[1] || ""),
239        layer: "bike_lane",
240        lat: parseShareCoord(parts[parts.length - 3]),
241        lon: parseShareCoord(parts[parts.length - 2]),
242        label: decodeLabelValue(parts[parts.length - 1] || "")
243      };
244    }
245    if (type === "intersection") {
246      const tailIndex = Math.max(1, parts.length - 3);
247      const streetParts = parts.slice(1, tailIndex);
248      let streetA = "";
249      let streetB = "";
250      if (streetParts.length === 2) {
251        streetA = decodeLabelValue(streetParts[0] || "");
252        streetB = decodeLabelValue(streetParts[1] || "");
253      } else if (streetParts.length > 2) {
254        let splitIndex = -1;
255        for (let i = 2; i < streetParts.length - 1; i += 1) {
256          const token = streetParts[i];
257          if (token === "all" || token === "0") {
258            splitIndex = i + 1;
259            break;
260          }
261        }
262        if (splitIndex === -1 && streetParts.length % 2 === 0) {
263          splitIndex = streetParts.length / 2;
264        }
265        if (splitIndex === -1) {
266          splitIndex = Math.min(3, streetParts.length - 1);
267        }
268        streetA = decodeLabelValue(streetParts.slice(0, splitIndex).join("|"));
269        streetB = decodeLabelValue(streetParts.slice(splitIndex).join("|"));
270      }
271      return {
272        streetA: streetA,
273        streetB: streetB,
274        layer: "intersection",
275        lat: parseShareCoord(parts[parts.length - 3]),
276        lon: parseShareCoord(parts[parts.length - 2]),
277        label: decodeLabelValue(parts[parts.length - 1] || "")
278      };
279    }
280    if (type === "crash") {
281      const radiusMeters = parseShareCoord(parts[5]);
282      return {
283        crashId: decodeLabelValue(parts[1] || ""),
284        layer: "crash",
285        lat: parseShareCoord(parts[2]),
286        lon: parseShareCoord(parts[3]),
287        label: decodeLabelValue(parts[4] || ""),
288        radiusMeters: Number.isFinite(radiusMeters) && radiusMeters > 0 ? radiusMeters : null
289      };
290    }
291    if (type === "point") {
292      const radiusMeters = parseShareCoord(parts[5]);
293      return {
294        layer: parts[3] || "address",
295        lat: parseShareCoord(parts[1]),
296        lon: parseShareCoord(parts[2]),
297        label: decodeLabelValue(parts[4] || ""),
298        radiusMeters: Number.isFinite(radiusMeters) && radiusMeters > 0 ? radiusMeters : null
299      };
300    }
301    return null;
302  }
303
304  function parseSelectionsParams(params) {
305    if (!params) return [];
306    return params.getAll("sel").map(parseShareSelection).filter(Boolean);
307  }
308
309  function parseSnapshotShareSelections(selection) {
310    const raw = selection && typeof selection === "object"
311      ? (selection.share_selections || selection.shareSelections || [])
312      : [];
313    if (!Array.isArray(raw) || !raw.length) return [];
314    return raw.map(parseShareSelection).filter(Boolean);
315  }
316
317  function snapshotSelectionLabels(selection, normalizeSelectionLabel) {
318    const raw = selection && typeof selection === "object"
319      ? (selection.labels_by_select_cell || selection.labelsBySelectCell || {})
320      : {};
321    if (!raw || typeof raw !== "object") return [];
322    const normalize = typeof normalizeSelectionLabel === "function"
323      ? normalizeSelectionLabel
324      : function passthrough(value) { return String(value || "").trim(); };
325    const seen = new Set();
326    const labels = [];
327    Object.values(raw).forEach(function collectLabel(value) {
328      const label = normalize(value);
329      if (!label || seen.has(label)) return;
330      seen.add(label);
331      labels.push(label);
332    });
333    return labels;
334  }
335
336  function hasSelectionDescriptor(selection) {
337    if (!selection) return false;
338    if (selection.streetId) return true;
339    if (selection.bikeLaneId) return true;
340    if (selection.streetA && selection.streetB) return true;
341    if (selection.crashId) return true;
342    return Number.isFinite(selection.lat) && Number.isFinite(selection.lon);
343  }
344
345  function shouldPreserveSnapshotFallbackParams(selections, pendingSelections) {
346    const resolved = Array.isArray(selections) ? selections.filter(Boolean) : [];
347    const pending = Array.isArray(pendingSelections) ? pendingSelections.filter(Boolean) : [];
348    const active = resolved.length ? resolved : pending;
349    if (active.length !== 1) return false;
350    return hasSelectionDescriptor(active[0]);
351  }
352
353  function buildSnapshotStateKey(state, options) {
354    const allModes = Array.isArray(options && options.allModes) ? options.allModes : [];
355    const timelineFilters = Array.isArray(options && options.timelineFilters) ? options.timelineFilters : [];
356    const ready = Boolean(state.series && state.compare && state.seriesSelectionVersion === state.selectionVersion);
357    const severity = state.severityFilter || "crashes";
358    const modes = state.modeFilters || new Set(allModes);
359    const modesKey = allModes.filter(function activeMode(mode) {
360      return modes.has(mode);
361    }).join(",");
362    const ageKey = state.ageFilter || "";
363    const hwKey = state.excludeHighways ? "1" : "0";
364    const timelineKey = timelineFilters
365      .map(function timelineKeyEntry(entry) { return entry.key; })
366      .filter(function enabledTimelineKey(key) {
367        return state.timelineFilters && state.timelineFilters.has(key);
368      })
369      .join(",");
370    const compareKey = state.compareMode ? "1" : "0";
371    const recent = state.compare && state.compare.recent ? state.compare.recent : null;
372    const earlier = state.compare && state.compare.earlier ? state.compare.earlier : null;
373    const recentKey = recent ? `${recent.start}-${recent.end}` : "";
374    const earlierKey = earlier ? `${earlier.start}-${earlier.end}` : "";
375    return [
376      ready ? "1" : "0",
377      String(state.selectionVersion || 0),
378      severity,
379      modesKey,
380      ageKey,
381      hwKey,
382      timelineKey,
383      compareKey,
384      recentKey,
385      earlierKey
386    ].join("|");
387  }
388
389  function buildSnapshotFromState(state, options) {
390    const allModes = Array.isArray(options && options.allModes) ? options.allModes : [];
391    const timelineFilters = Array.isArray(options && options.timelineFilters) ? options.timelineFilters : [];
392    const serialize = options && typeof options.serializeShareSelection === "function"
393      ? options.serializeShareSelection
394      : serializeShareSelection;
395    const resolvedDataVersion = options && typeof options.resolvedDataVersion === "function"
396      ? options.resolvedDataVersion
397      : function defaultResolvedDataVersion() { return ""; };
398    const isConcreteVersion = options && typeof options.isConcreteVersion === "function"
399      ? options.isConcreteVersion
400      : function defaultIsConcreteVersion(value) { return !!String(value || "").trim(); };
401    const selectRes = Number(options && options.selectRes);
402    const dataRes = Number(options && options.dataRes);
403
404    if (!state.selectedData || !state.selectedData.size) return null;
405    if (!state.series || !state.compare || state.seriesSelectionVersion !== state.selectionVersion) return null;
406    const months = state.series.months || [];
407    const recent = state.compare.recent;
408    if (!recent || !months[recent.start] || !months[recent.end]) return null;
409    const earlier = state.compare.earlier;
410    const earlierStart = earlier && months[earlier.start] ? months[earlier.start] : null;
411    const earlierEnd = earlier && months[earlier.end] ? months[earlier.end] : null;
412    const labels = {};
413    if (state.selectionLabelIndex && typeof state.selectionLabelIndex.forEach === "function") {
414      state.selectionLabelIndex.forEach(function assignLabel(label, cell) {
415        if (!label) return;
416        labels[cell] = label;
417      });
418    }
419    const modes = state.modeFilters || new Set(allModes);
420    const timeline = timelineFilters
421      .map(function timelineKeyEntry(entry) { return entry.key; })
422      .filter(function enabledTimelineKey(key) {
423        return state.timelineFilters && state.timelineFilters.has(key);
424      });
425    const shareSelections = Array.from((state.shareSelections && state.shareSelections.values()) || [])
426      .map(serialize)
427      .filter(Boolean);
428    const selection = {
429      data_cells: Array.from(state.selectedData),
430      labels_by_select_cell: labels
431    };
432    if (shareSelections.length) selection.share_selections = shareSelections;
433    const snapshot = {
434      v: 1,
435      app: "crashfinder",
436      select_res: Number.isFinite(selectRes) ? selectRes : undefined,
437      data_res: Number.isFinite(dataRes) ? dataRes : undefined,
438      selection: selection,
439      filters: {
440        severity: state.severityFilter || "crashes",
441        modes: allModes.filter(function activeMode(mode) {
442          return modes.has(mode);
443        }),
444        age: state.ageFilter || null,
445        exclude_highways: !!state.excludeHighways,
446        timeline: timeline
447      },
448      compare: {
449        enabled: !!state.compareMode,
450        recent_start: months[recent.start],
451        recent_end: months[recent.end],
452        earlier_start: earlierStart,
453        earlier_end: earlierEnd
454      }
455    };
456    if (!Number.isFinite(snapshot.select_res)) delete snapshot.select_res;
457    if (!Number.isFinite(snapshot.data_res)) delete snapshot.data_res;
458    const dataVersion = resolvedDataVersion();
459    if (!isConcreteVersion(dataVersion)) return null;
460    snapshot.data_version = dataVersion;
461    return snapshot;
462  }
463
464  function applyShareUrlState(params, options) {
465    if (!params) return;
466    const selections = Array.isArray(options && options.selections) ? options.selections.filter(Boolean) : [];
467    const pendingSelections = Array.isArray(options && options.pendingSelections)
468      ? options.pendingSelections.filter(Boolean)
469      : [];
470    const hasSid = !!(options && options.hasSid);
471    const preserveFallbackParamsWithSid = !!(options && options.preserveFallbackParamsWithSid);
472    const syncVersionUrlParam = options && typeof options.syncVersionUrlParam === "function"
473      ? options.syncVersionUrlParam
474      : function noop() {};
475    const preserveSelectionFallback = shouldPreserveSnapshotFallbackParams(selections, pendingSelections);
476    const allowSelectionFallbackWithSid = hasSid && preserveFallbackParamsWithSid;
477
478    if (!selections.length && pendingSelections.length && !allowSelectionFallbackWithSid) {
479      syncVersionUrlParam(params);
480      return;
481    }
482
483    clearSelectionShareParams(params);
484    if (selections.length && (!hasSid || preserveSelectionFallback || allowSelectionFallbackWithSid)) {
485      applyLegacyShareParams(params, selections);
486      params.set("auto", "1");
487    }
488
489    STATE_SHARE_PARAM_KEYS.forEach(function deleteShareParam(key) {
490      params.delete(key);
491    });
492
493    const series = options && options.series;
494    const compare = options && options.compare;
495    if (options && options.compareMode && series && compare && compare.recent) {
496      const recent = compare.recent;
497      const recentStart = series.months && series.months[recent.start];
498      const recentEnd = series.months && series.months[recent.end];
499      if (recentStart && recentEnd) {
500        params.set("compare", "1");
501        params.set("recent_start", recentStart);
502        params.set("recent_end", recentEnd);
503        const earlier = compare.earlier;
504        if (earlier) {
505          const earlierStart = series.months && series.months[earlier.start];
506          const earlierEnd = series.months && series.months[earlier.end];
507          if (earlierStart) params.set("earlier_start", earlierStart);
508          if (earlierEnd) params.set("earlier_end", earlierEnd);
509        }
510      }
511    } else if (options && options.pendingCompare && options.pendingCompare.compare) {
512      params.set("compare", "1");
513      if (options.pendingCompare.recentStart) params.set("recent_start", options.pendingCompare.recentStart);
514      if (options.pendingCompare.recentEnd) params.set("recent_end", options.pendingCompare.recentEnd);
515      if (options.pendingCompare.earlierStart) params.set("earlier_start", options.pendingCompare.earlierStart);
516      if (options.pendingCompare.earlierEnd) params.set("earlier_end", options.pendingCompare.earlierEnd);
517    }
518
519    const modeFilters = Array.isArray(options && options.modeFilters) ? options.modeFilters.filter(
519Boolean) : [];
520    if (modeFilters.length) {
521      params.set("modes", modeFilters.join(","));
522    }
523
524    syncVersionUrlParam(params);
525    if (hasSid && !preserveFallbackParamsWithSid) {
526      clearSnapshotBootstrapParams(params);
527    }
528  }
529
530  function applyLegacyShareParams(params, selections) {
531    if (!params) return;
532    const activeSelections = Array.isArray(selections) ? selections.filter(Boolean) : [];
533    if (!activeSelections.length) return;
534    if (activeSelections.length === 1) {
535      const selection = activeSelections[0];
536      const label = (selection.label || "").trim();
537      if (label) {
538        params.set("q", label);
539        params.set("label", label);
540      }
541      if (selection.layer) params.set("layer", selection.layer);
542      if (Number.isFinite(selection.lat)) params.set("lat", String(selection.lat));
543      if (Number.isFinite(selection.lon)) params.set("lon", String(selection.lon));
544      if (selection.streetId) params.set("street_id", selection.streetId);
545      if (selection.bikeLaneId) params.set("bike_lane_id", selection.bikeLaneId);
546      if (selection.streetA) params.set("street_a", selection.streetA);
547      if (selection.streetB) params.set("street_b", selection.streetB);
548      if (selection.crashId) params.set("crash_id", selection.crashId);
549      if (Number.isFinite(selection.radiusMeters) && selection.radiusMeters > 0) {
550        params.set("radius_m", String(Math.round(selection.radiusMeters)));
551      }
552      return;
553    }
554    activeSelections.forEach(function appendSelection(selection) {
555      const encoded = serializeShareSelection(selection);
556      if (encoded) params.append("sel", encoded);
557    });
558  }
559
560  return {
561    SHARE_PARAM_KEYS: SHARE_PARAM_KEYS,
562    applyLegacyShareParams: applyLegacyShareParams,
563    applyShareUrlState: applyShareUrlState,
564    buildFindRedirectUrl: buildFindRedirectUrl,
565    buildSnapshotFromState: buildSnapshotFromState,
566    buildSnapshotStateKey: buildSnapshotStateKey,
567    clearLegacyShareParams: clearLegacyShareParams,
568    clearSelectionShareParams: clearSelectionShareParams,
569    clearSnapshotBootstrapParams: clearSnapshotBootstrapParams,
570    decodeLabelValue: decodeLabelValue,
571    finalizeSnapshotSidUrl: finalizeSnapshotSidUrl,
572    formatShareCoord: formatShareCoord,
573    hasSelectionDescriptor: hasSelectionDescriptor,
574    parseSelectionParams: parseSelectionParams,
575    parseSnapshotShareSelections: parseSnapshotShareSelections,
576    parseSelectionsParams: parseSelectionsParams,
577    parseShareSelection: parseShareSelection,
578    serializeShareSelection: serializeShareSelection,
579    shareKeyForSelection: shareKeyForSelection,
580    snapshotSelectionLabels: snapshotSelectionLabels,
581    shouldPreserveSnapshotFallbackParams: shouldPreserveSnapshotFallbackParams
582  };
583});

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.