File size: 4,353 Bytes
e8a6c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b2f95f6
 
 
 
 
 
 
 
 
 
e8a6c67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * Fetch Riprap GeoJSON layers from FastAPI for a queried address.
 *
 * The legacy custom-element bundle (web/static/agent.js) wires the same
 * endpoints — keep these in sync. Failures are swallowed because the map
 * is supplementary; the briefing is the deliverable.
 */
import type { FeatureCollection } from 'geojson';

const EMPTY: FeatureCollection = { type: 'FeatureCollection', features: [] };

async function fetchFc(url: string): Promise<FeatureCollection> {
  try {
    const r = await fetch(url);
    if (!r.ok) return EMPTY;
    const j = (await r.json()) as FeatureCollection;
    if (!j || j.type !== 'FeatureCollection') return EMPTY;
    return j;
  } catch {
    return EMPTY;
  }
}

export async function fetchSandy(lat: number, lon: number, r = 1500): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/sandy?lat=${lat}&lon=${lon}&r=${r}`);
}

export async function fetchDep(lat: number, lon: number, r = 1500): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/dep_extreme_2080?lat=${lat}&lon=${lon}&r=${r}`);
}

/**
 * Prithvi water-mask polygons cover Hurricane Ida 2021 flood extents
 * across NYC. The polygons are *sparse* at street-block scale — most
 * single addresses fall in zero-feature neighborhoods. We use the same
 * 1.5 km radius as Sandy/DEP so the layer reflects what flooded *near*
 * the queried address. A wider radius pulled in features from across
 * the city (Manhattan polygons rendering on a Red Hook briefing) which
 * read as confabulation. If a neighborhood wasn't hit by Ida, silence
 * is the right answer — the legend drops the layer automatically.
 */
export async function fetchPrithviSynthetic(
  lat: number,
  lon: number,
  r = 1500
): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/prithvi_water?lat=${lat}&lon=${lon}&r=${r}`);
}

/**
 * Neighborhood / development_check intents emit `nta_resolve` instead of
 * `geocode`. The matching layer endpoints clip to the NTA polygon's bbox
 * — same FeatureCollection contract as the address-mode endpoints.
 */
export async function fetchSandyNta(code: string): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/sandy_clipped?code=${encodeURIComponent(code)}`);
}

export async function fetchDepNta(
  code: string,
  scenario: 'dep_extreme_2080' | 'dep_moderate_2050' | 'dep_moderate_current' = 'dep_extreme_2080'
): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/dep_clipped?code=${encodeURIComponent(code)}&scenario=${scenario}`);
}

export async function fetchNtaPolygon(code: string): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/nta?code=${encodeURIComponent(code)}`);
}

interface FloodNetSensor {
  type: 'Feature';
  geometry: { type: 'Point'; coordinates: [number, number] };
  properties: { n_events_3y?: number; peak_depth_mm?: number; deployment_id?: string; name?: string; [k: string]: unknown };
}

/**
 * USGS Hurricane Ida 2021 high-water marks within radius_m of the queried
 * address. Returns Points with site_description, elev_ft,
 * height_above_gnd_ft, hwm_quality, waterbody, distance_m properties.
 * Empirical tier — surveyed ground-truth water marks.
 */
export async function fetchIdaHwm(lat: number, lon: number, r = 1500): Promise<FeatureCollection> {
  return fetchFc(`/api/layers/ida_hwm?lat=${lat}&lon=${lon}&r=${r}`);
}

/**
 * FloodNet sensor points as a graduated-circle layer. The handoff puts 311
 * complaints on the proxy layer; we don't currently expose 311 as GeoJSON
 * from FastAPI, so floodnet sensor density (event count) drives the dot
 * radius for now. Swap to a real 311 endpoint when one lands.
 */
export async function fetchProxyDots(
  lat: number,
  lon: number,
  r = 1500
): Promise<FeatureCollection> {
  try {
    const res = await fetch(`/api/floodnet_near?lat=${lat}&lon=${lon}&r=${r}`);
    if (!res.ok) return EMPTY;
    const j = (await res.json()) as FeatureCollection;
    // Map n_events_3y → `count` so the proxy-dots radius interpolation hits.
    const features = j.features.map((f) => {
      const p = (f as FloodNetSensor).properties ?? {};
      return {
        ...f,
        properties: {
          ...p,
          count: typeof p.n_events_3y === 'number' ? p.n_events_3y : 1
        }
      };
    });
    return { type: 'FeatureCollection', features };
  } catch {
    return EMPTY;
  }
}