@sjvair/monitor-map 4.3.0 → 5.0.0-beta.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/LICENSE +21 -0
- package/README.md +59 -138
- package/dist/lib/MonitorDetailPanel.svelte +23 -20
- package/dist/lib/MonitorMapLayout.svelte +147 -77
- package/dist/lib/components/DataBox.svelte +4 -1
- package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
- package/dist/lib/config.svelte.d.ts +7 -0
- package/dist/lib/config.svelte.js +10 -0
- package/dist/lib/core.d.ts +2 -2
- package/dist/lib/core.js +2 -1
- package/dist/lib/data-chart/DataChart.svelte +40 -25
- package/dist/lib/data-chart/DateRange.d.ts +13 -4
- package/dist/lib/data-chart/DateRange.js +50 -14
- package/dist/lib/data-chart/chart-utils.js +5 -7
- package/dist/lib/data-chart/level-colors.d.ts +3 -4
- package/dist/lib/data-chart/level-colors.js +7 -4
- package/dist/lib/data-chart/pacific-time.d.ts +9 -0
- package/dist/lib/data-chart/pacific-time.js +25 -0
- package/dist/lib/data-chart/tooltip.js +2 -2
- package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
- package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
- package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
- package/dist/lib/ev-stations/ev-station-icons.js +1 -13
- package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
- package/dist/lib/ev-stations/ev-stations.js +53 -0
- package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
- package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
- package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
- package/dist/lib/ev-stations/types.d.ts +0 -29
- package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
- package/dist/lib/ev-stations/zip-codes.js +28 -16
- package/dist/lib/index.d.ts +8 -56
- package/dist/lib/index.js +7 -46
- package/dist/lib/legend/Legend.svelte +18 -19
- package/dist/lib/legend/Legend.svelte.d.ts +2 -2
- package/dist/lib/levels.d.ts +7 -0
- package/dist/lib/levels.js +19 -0
- package/dist/lib/map/MapShell.svelte +17 -24
- package/dist/lib/map/MapShell.svelte.d.ts +4 -4
- package/dist/lib/map/basemaps.d.ts +25 -0
- package/dist/lib/map/basemaps.js +66 -0
- package/dist/lib/map/live-map.svelte.d.ts +3 -0
- package/dist/lib/map/live-map.svelte.js +22 -0
- package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
- package/dist/lib/map/sync-plugins.svelte.js +52 -0
- package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
- package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
- package/dist/lib/monitors/filtered-source.svelte.js +28 -0
- package/dist/lib/monitors/monitors.svelte.d.ts +25 -35
- package/dist/lib/monitors/monitors.svelte.js +35 -218
- package/dist/lib/monitors/retry-load.d.ts +8 -0
- package/dist/lib/monitors/retry-load.js +11 -0
- package/dist/lib/monitors/types.d.ts +1 -41
- package/dist/lib/options/map-option-defaults.d.ts +25 -36
- package/dist/lib/options/map-option-defaults.js +12 -30
- package/dist/lib/options/map-options.svelte.d.ts +13 -0
- package/dist/lib/options/map-options.svelte.js +26 -0
- package/dist/lib/options/menus/LayersMenu.svelte +8 -20
- package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
- package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
- package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
- package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
- package/dist/lib/options/plugin-order.d.ts +8 -0
- package/dist/lib/options/plugin-order.js +36 -0
- package/dist/lib/options/pollutant-param.d.ts +7 -5
- package/dist/lib/options/pollutant-param.js +9 -5
- package/dist/lib/options/row-icons.d.ts +8 -0
- package/dist/lib/options/row-icons.js +15 -0
- package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
- package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
- package/dist/lib/search/Search.svelte +20 -33
- package/dist/lib/search/Search.svelte.d.ts +5 -1
- package/dist/lib/search/search-pin.svelte.d.ts +11 -0
- package/dist/lib/search/search-pin.svelte.js +28 -0
- package/dist/lib/search/service.d.ts +7 -2
- package/dist/lib/search/service.js +17 -10
- package/dist/lib/stores.svelte.d.ts +8 -0
- package/dist/lib/stores.svelte.js +9 -0
- package/dist/lib/wind/wind-plugin.d.ts +11 -0
- package/dist/lib/wind/wind-plugin.js +25 -0
- package/package.json +12 -10
- package/src/lib/MonitorDetailPanel.svelte +23 -20
- package/src/lib/MonitorMapLayout.svelte +147 -77
- package/src/lib/components/DataBox.svelte +4 -1
- package/src/lib/config.svelte.ts +16 -0
- package/src/lib/core.ts +2 -2
- package/src/lib/data-chart/DataChart.svelte +40 -25
- package/src/lib/data-chart/DateRange.ts +58 -15
- package/src/lib/data-chart/chart-utils.ts +6 -7
- package/src/lib/data-chart/level-colors.ts +8 -9
- package/src/lib/data-chart/pacific-time.ts +31 -0
- package/src/lib/data-chart/tooltip.ts +2 -2
- package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
- package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
- package/src/lib/ev-stations/ev-station-icons.ts +1 -17
- package/src/lib/ev-stations/ev-stations.ts +72 -0
- package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
- package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
- package/src/lib/ev-stations/types.ts +0 -34
- package/src/lib/ev-stations/zip-codes.ts +29 -16
- package/src/lib/index.ts +8 -99
- package/src/lib/legend/Legend.svelte +18 -19
- package/src/lib/levels.ts +24 -0
- package/src/lib/map/MapShell.svelte +17 -24
- package/src/lib/map/basemaps.ts +76 -0
- package/src/lib/map/live-map.svelte.ts +24 -0
- package/src/lib/map/sync-plugins.svelte.ts +69 -0
- package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
- package/src/lib/monitors/filtered-source.svelte.ts +38 -0
- package/src/lib/monitors/monitors.svelte.ts +58 -261
- package/src/lib/monitors/retry-load.ts +14 -0
- package/src/lib/monitors/types.ts +1 -47
- package/src/lib/options/map-option-defaults.ts +30 -53
- package/src/lib/options/map-options.svelte.ts +30 -0
- package/src/lib/options/menus/LayersMenu.svelte +8 -20
- package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/src/lib/options/menus/PollutantMenu.svelte +4 -9
- package/src/lib/options/menus/SettingsMenu.svelte +32 -25
- package/src/lib/options/plugin-order.ts +38 -0
- package/src/lib/options/pollutant-param.ts +14 -9
- package/src/lib/options/row-icons.ts +17 -0
- package/src/lib/options/rows/MarkerIcon.svelte +5 -5
- package/src/lib/search/Search.svelte +20 -33
- package/src/lib/search/search-pin.svelte.ts +35 -0
- package/src/lib/search/service.ts +34 -15
- package/src/lib/stores.svelte.ts +15 -0
- package/src/lib/wind/wind-plugin.ts +33 -0
- package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
- package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -10
- package/dist/lib/collocation-sites/collocations.svelte.js +0 -21
- package/dist/lib/collocation-sites/types.d.ts +0 -8
- package/dist/lib/collocation-sites/types.js +0 -1
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
- package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -10
- package/dist/lib/ev-stations/ev-stations.svelte.js +0 -57
- package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
- package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
- package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
- package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
- package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
- package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
- package/dist/lib/hms/hms.svelte.d.ts +0 -20
- package/dist/lib/hms/hms.svelte.js +0 -237
- package/dist/lib/legend/LegendBar.svelte +0 -45
- package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
- package/dist/lib/legend/legend-data.d.ts +0 -40
- package/dist/lib/legend/legend-data.js +0 -65
- package/dist/lib/map/Map.svelte +0 -13
- package/dist/lib/map/Map.svelte.d.ts +0 -8
- package/dist/lib/map/basemap.d.ts +0 -14
- package/dist/lib/map/basemap.js +0 -10
- package/dist/lib/map/icons.d.ts +0 -7
- package/dist/lib/map/icons.js +0 -33
- package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
- package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
- package/dist/lib/map/integrations/click-manager.d.ts +0 -14
- package/dist/lib/map/integrations/click-manager.js +0 -52
- package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
- package/dist/lib/map/integrations/integrations-manager.js +0 -20
- package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
- package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
- package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
- package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
- package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
- package/dist/lib/map/integrations/map-icon-manager.js +0 -52
- package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
- package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
- package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
- package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
- package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
- package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
- package/dist/lib/map/integrations/types.d.ts +0 -14
- package/dist/lib/map/integrations/types.js +0 -1
- package/dist/lib/map/map-manager.svelte.d.ts +0 -20
- package/dist/lib/map/map-manager.svelte.js +0 -63
- package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
- package/dist/lib/map/map-style-state.svelte.js +0 -86
- package/dist/lib/map/utils.d.ts +0 -7
- package/dist/lib/map/utils.js +0 -36
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/monitor-utils.d.ts +0 -4
- package/dist/lib/monitors/monitor-utils.js +0 -41
- package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
- package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
- package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
- package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
- package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
- package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
- package/dist/lib/options/reset-map-options.d.ts +0 -2
- package/dist/lib/options/reset-map-options.js +0 -24
- package/dist/lib/reactivity.svelte.d.ts +0 -0
- package/dist/lib/reactivity.svelte.js +0 -83
- package/dist/lib/wind/wind.svelte.d.ts +0 -10
- package/dist/lib/wind/wind.svelte.js +0 -13
- package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
- package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
- package/src/lib/collocation-sites/collocations.svelte.ts +0 -27
- package/src/lib/collocation-sites/types.ts +0 -10
- package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
- package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
- package/src/lib/ev-stations/ev-stations.svelte.ts +0 -84
- package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
- package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
- package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
- package/src/lib/hms/hms.svelte.ts +0 -270
- package/src/lib/legend/LegendBar.svelte +0 -45
- package/src/lib/legend/legend-data.ts +0 -101
- package/src/lib/map/Map.svelte +0 -13
- package/src/lib/map/basemap.ts +0 -24
- package/src/lib/map/icons.ts +0 -42
- package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
- package/src/lib/map/integrations/click-manager.ts +0 -64
- package/src/lib/map/integrations/integrations-manager.ts +0 -25
- package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
- package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
- package/src/lib/map/integrations/map-icon-manager.ts +0 -67
- package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
- package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
- package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
- package/src/lib/map/integrations/types.ts +0 -24
- package/src/lib/map/map-manager.svelte.ts +0 -83
- package/src/lib/map/map-style-state.svelte.ts +0 -94
- package/src/lib/map/utils.ts +0 -53
- package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/src/lib/monitors/monitor-utils.ts +0 -57
- package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
- package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
- package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
- package/src/lib/options/reset-map-options.ts +0 -28
- package/src/lib/reactivity.svelte.ts +0 -82
- package/src/lib/wind/wind.svelte.ts +0 -20
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { startOfPacificDay } from "@sjvair/sdk/datetime/pacific";
|
|
2
|
+
|
|
3
|
+
/** The chart's zone: times and days are Pacific, whatever zone the device is in. */
|
|
4
|
+
export const PACIFIC_TIME_ZONE = "America/Los_Angeles";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The x-axis end: `now` when the data ends earlier on the current Pacific day (so the axis
|
|
8
|
+
* reaches the present), else the data's end. Both are Unix seconds.
|
|
9
|
+
*/
|
|
10
|
+
export function chartRangeMax(max: number, now: Date = new Date()): number {
|
|
11
|
+
const end = new Date(max * 1000);
|
|
12
|
+
const today = startOfPacificDay(end).getTime() === startOfPacificDay(now).getTime();
|
|
13
|
+
return today && now > end ? Math.floor(now.getTime() / 1000) : max;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
17
|
+
timeZone: PACIFIC_TIME_ZONE,
|
|
18
|
+
month: "short",
|
|
19
|
+
day: "numeric",
|
|
20
|
+
year: "numeric",
|
|
21
|
+
hour: "numeric",
|
|
22
|
+
minute: "2-digit"
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/** A Unix time as Pacific wall-clock time, e.g. "Nov 4, 2026 8:05 PM". */
|
|
26
|
+
export function formatPacificTime(unixSeconds: number): string {
|
|
27
|
+
const part = Object.fromEntries(
|
|
28
|
+
formatter.formatToParts(new Date(unixSeconds * 1000)).map((p) => [p.type, p.value])
|
|
29
|
+
);
|
|
30
|
+
return `${part.month} ${part.day}, ${part.year} ${part.hour}:${part.minute} ${part.dayPeriod}`;
|
|
31
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatPacificTime } from "./pacific-time";
|
|
2
2
|
import type { LevelColors } from "./level-colors";
|
|
3
3
|
|
|
4
4
|
let cursLeft = -100;
|
|
@@ -153,7 +153,7 @@ export function tooltipsPlugin() {
|
|
|
153
153
|
let label = u.series[i].label as string;
|
|
154
154
|
label = label?.substring(label.indexOf("(") + 1, label.indexOf(")")) || label;
|
|
155
155
|
|
|
156
|
-
ttHeader.textContent =
|
|
156
|
+
ttHeader.textContent = formatPacificTime(xVal);
|
|
157
157
|
tt!.textContent = `${label}: ${yVal}`;
|
|
158
158
|
}
|
|
159
159
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import CreditCard from "@lucide/svelte/icons/credit-card";
|
|
3
3
|
import Zap from "@lucide/svelte/icons/zap";
|
|
4
|
-
import type {
|
|
4
|
+
import type { EvStation } from "./types";
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
|
-
|
|
7
|
+
station: EvStation;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const {
|
|
10
|
+
const { station }: Props = $props();
|
|
11
11
|
|
|
12
|
+
// 4.x's station popup, from the station NREL returned (codes already turned into labels).
|
|
12
13
|
const computed = $derived.by(() => {
|
|
13
|
-
const p =
|
|
14
|
+
const p = station;
|
|
14
15
|
|
|
15
|
-
const connectors: string[] =
|
|
16
|
+
const connectors: string[] = (p.ev_connector_types ?? []).filter(Boolean);
|
|
16
17
|
|
|
17
18
|
const cards: string[] = p.cards_accepted
|
|
18
19
|
? p.cards_accepted.split(",").map((c) => c.trim())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createRawSnippet } from "svelte";
|
|
2
|
+
import { pointsPlugin, type MapPlugin } from "@sjvair/map-sdk";
|
|
3
|
+
import type { DataSource } from "@sjvair/map-sdk/data";
|
|
4
|
+
import { EV_STATION_LEVELS, evStationIcon } from "./ev-station-icons";
|
|
5
|
+
import type { EvFeatureProperties, EvLevel } from "./ev-store.svelte.js";
|
|
6
|
+
|
|
7
|
+
export type EvPluginId = "ev-l2" | "ev-l3";
|
|
8
|
+
|
|
9
|
+
export const EV_PLUGIN_IDS: Record<EvLevel, EvPluginId> = { lvl2: "ev-l2", lvl3: "ev-l3" };
|
|
10
|
+
|
|
11
|
+
const LEVEL_LABELS: Record<EvLevel, string> = { lvl2: "Level 2", lvl3: "Level 3" };
|
|
12
|
+
|
|
13
|
+
/** The marker size, as in 4.x. */
|
|
14
|
+
const ICON_SIZE = 32;
|
|
15
|
+
|
|
16
|
+
/** "rgb(r, g, b)" at 0.6 alpha, the cluster bubbles' fill in 4.x. */
|
|
17
|
+
function clusterFill(rgb: string): string {
|
|
18
|
+
return rgb.replace("rgb(", "rgba(").replace(")", ", 0.6)");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 4.x's cluster tooltip: "Level 2 Chargers" over "N stations". */
|
|
22
|
+
function clusterTooltip(level: EvLevel, count: number) {
|
|
23
|
+
return createRawSnippet(() => ({
|
|
24
|
+
render: () =>
|
|
25
|
+
`<div class="flex flex-col gap-1 p-1"><p class="font-semibold">${LEVEL_LABELS[level]} Chargers</p><p class="text-sm">${count} stations</p></div>`
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One level of EV chargers, as 4.x drew them: the level's charger icon and, clustered, bubbles in
|
|
31
|
+
* the level's color (radius 15/20/25 by count, with a white count) that zoom in on click. Stations
|
|
32
|
+
* are listed for the feature list; a click on one is a `featureclick` with a `feature` ref whose
|
|
33
|
+
* id is the station id (the layout opens the popup).
|
|
34
|
+
*/
|
|
35
|
+
export function evPlugin(
|
|
36
|
+
level: EvLevel,
|
|
37
|
+
source: DataSource<EvFeatureProperties>,
|
|
38
|
+
options: { clustered: boolean }
|
|
39
|
+
): MapPlugin {
|
|
40
|
+
const { bg, border } = EV_STATION_LEVELS[level];
|
|
41
|
+
const icon = evStationIcon(bg, border);
|
|
42
|
+
return pointsPlugin<EvFeatureProperties>({
|
|
43
|
+
id: EV_PLUGIN_IDS[level],
|
|
44
|
+
source,
|
|
45
|
+
layerLabel: `${LEVEL_LABELS[level]} Chargers`,
|
|
46
|
+
selectable: false,
|
|
47
|
+
icon: { category: () => level, draw: () => icon, size: ICON_SIZE },
|
|
48
|
+
detail: () => `${LEVEL_LABELS[level]} charger`,
|
|
49
|
+
...(options.clustered && {
|
|
50
|
+
cluster: {
|
|
51
|
+
radius: 40,
|
|
52
|
+
maxZoom: 9,
|
|
53
|
+
layers: ({ sourceId }) => [
|
|
54
|
+
{
|
|
55
|
+
local: "cluster-circle",
|
|
56
|
+
spec: {
|
|
57
|
+
type: "circle",
|
|
58
|
+
source: sourceId,
|
|
59
|
+
filter: ["has", "point_count"],
|
|
60
|
+
paint: {
|
|
61
|
+
"circle-color": clusterFill(bg),
|
|
62
|
+
"circle-radius": ["step", ["get", "point_count"], 15, 10, 20, 25, 25]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
local: "cluster-label",
|
|
68
|
+
spec: {
|
|
69
|
+
type: "symbol",
|
|
70
|
+
source: sourceId,
|
|
71
|
+
filter: ["has", "point_count"],
|
|
72
|
+
layout: {
|
|
73
|
+
"text-field": ["get", "point_count_abbreviated"],
|
|
74
|
+
"text-size": 12,
|
|
75
|
+
"text-allow-overlap": true,
|
|
76
|
+
"text-ignore-placement": true
|
|
77
|
+
},
|
|
78
|
+
paint: { "text-color": "#ffffff" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
tooltip: (cluster) => clusterTooltip(level, cluster.pointCount)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { MapIconManager } from "$lib/map/integrations/map-icon-manager";
|
|
2
|
-
import { asDataURI } from "$lib/map/icons";
|
|
3
|
-
|
|
4
1
|
const EV_STATION_PATH =
|
|
5
2
|
"m340-200 100-160h-60v-120L280-320h60v120ZM240-560h240v-200H240v200Zm0 360h240v-280H240v280Zm-80 80v-640q0-33 23.5-56.5T240-840h240q33 0 56.5 23.5T560-760v280h50q29 0 49.5 20.5T680-410v185q0 17 14 31t31 14q18 0 31.5-14t13.5-31v-375h-10q-17 0-28.5-11.5T720-640v-80h20v-60h40v60h40v-60h40v60h20v80q0 17-11.5 28.5T840-600h-10v375q0 42-30.5 73.5T725-120q-43 0-74-31.5T620-225v-185q0-5-2.5-7.5T610-420h-50v300H160Zm320-80H240h240Z";
|
|
6
3
|
|
|
@@ -11,6 +8,7 @@ export const EV_STATION_LEVELS = {
|
|
|
11
8
|
|
|
12
9
|
export type EvStationLevel = keyof typeof EV_STATION_LEVELS;
|
|
13
10
|
|
|
11
|
+
/** A charger marker: a circle in the level's colors with the white charger glyph, 32px. */
|
|
14
12
|
export function evStationIcon(bg: string, border: string): string {
|
|
15
13
|
return `<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
16
14
|
<circle cx="16" cy="16" r="14" fill="${bg}" stroke="${border}" stroke-width="2"/>
|
|
@@ -19,17 +17,3 @@ export function evStationIcon(bg: string, border: string): string {
|
|
|
19
17
|
</svg>
|
|
20
18
|
</svg>`;
|
|
21
19
|
}
|
|
22
|
-
|
|
23
|
-
export class EvStationIconManager extends MapIconManager {
|
|
24
|
-
constructor() {
|
|
25
|
-
super();
|
|
26
|
-
for (const [level, colors] of Object.entries(EV_STATION_LEVELS) as Array<
|
|
27
|
-
[EvStationLevel, (typeof EV_STATION_LEVELS)[EvStationLevel]]
|
|
28
|
-
>) {
|
|
29
|
-
const id = `ev-station-${level}`;
|
|
30
|
-
const icon = new Image(32, 32);
|
|
31
|
-
icon.src = asDataURI(evStationIcon(colors.bg, colors.border));
|
|
32
|
-
this.register(id, icon);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import {
|
|
2
|
+
accessDetailCodeTypes,
|
|
3
|
+
creditCardTypes,
|
|
4
|
+
evConnectorTypes,
|
|
5
|
+
evNetworkTypes,
|
|
6
|
+
facilityTypes
|
|
7
|
+
} from "./data-maps";
|
|
8
|
+
import { monitorMapConfig } from "../config.svelte.js";
|
|
9
|
+
import type { EvStation } from "./types";
|
|
10
|
+
import { EV_ZIP_CODES } from "./zip-codes";
|
|
11
|
+
|
|
12
|
+
/** Which NREL charging level each map level asks for. */
|
|
13
|
+
const CHARGING_LEVEL = { lvl2: "2", lvl3: "dc_fast" } as const;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fetches the public electric chargers of one level in the Valley's ZIPs from NREL (key from
|
|
17
|
+
* `monitorMapConfig().nrelKey`), with codes turned into labels. Station 226154 is dropped: its
|
|
18
|
+
* coordinates are wrong.
|
|
19
|
+
*/
|
|
20
|
+
export async function fetchEvStations(
|
|
21
|
+
level: "lvl2" | "lvl3",
|
|
22
|
+
signal?: AbortSignal
|
|
23
|
+
): Promise<Array<EvStation>> {
|
|
24
|
+
if (signal?.aborted) throw signal.reason;
|
|
25
|
+
const url = new URL(`https://developer.nlr.gov/api/alt-fuel-stations/v1.json`);
|
|
26
|
+
url.searchParams.set("fuel_type", "ELEC");
|
|
27
|
+
url.searchParams.set("state", "CA");
|
|
28
|
+
url.searchParams.set("status", "E");
|
|
29
|
+
url.searchParams.set("access", "public");
|
|
30
|
+
url.searchParams.set("ev_charging_level", CHARGING_LEVEL[level]);
|
|
31
|
+
url.searchParams.set("zip", EV_ZIP_CODES.join(","));
|
|
32
|
+
url.searchParams.set("api_key", monitorMapConfig().nrelKey);
|
|
33
|
+
|
|
34
|
+
const res = await fetch(url, { signal });
|
|
35
|
+
if (!res.ok) throw new Error(`EV chargers: NREL answered ${res.status}`);
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
const fuelStations: Array<EvStation> = data.fuel_stations;
|
|
38
|
+
if (!Array.isArray(fuelStations)) throw new Error("EV chargers: no fuel_stations in the answer");
|
|
39
|
+
return (
|
|
40
|
+
fuelStations
|
|
41
|
+
.map((station) => {
|
|
42
|
+
if (station.access_detail_code) {
|
|
43
|
+
station.access_detail_code = accessDetailCodeTypes.get(station.access_detail_code);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (station.cards_accepted) {
|
|
47
|
+
station.cards_accepted = station.cards_accepted
|
|
48
|
+
.split(" ")
|
|
49
|
+
.map((c: string) => creditCardTypes.get(c))
|
|
50
|
+
.join(", ");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (station.ev_connector_types && station.ev_connector_types.length) {
|
|
54
|
+
station.ev_connector_types = station.ev_connector_types.map((t: string) =>
|
|
55
|
+
evConnectorTypes.get(t)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (station.ev_network) {
|
|
60
|
+
station.ev_network = evNetworkTypes.get(station.ev_network);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (station.facility_type) {
|
|
64
|
+
station.facility_type = facilityTypes.get(station.facility_type);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return station;
|
|
68
|
+
})
|
|
69
|
+
// remove stations with invalid coordinates
|
|
70
|
+
.filter((station) => station.id !== 226154)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { FeatureCollection, Point } from "geojson";
|
|
2
|
+
import {
|
|
3
|
+
encodeRef,
|
|
4
|
+
type DataSource,
|
|
5
|
+
type DataStatus,
|
|
6
|
+
type MapFeatureProperties
|
|
7
|
+
} from "@sjvair/map-sdk/data";
|
|
8
|
+
import { fetchEvStations } from "./ev-stations";
|
|
9
|
+
import type { EvStation } from "./types";
|
|
10
|
+
|
|
11
|
+
export type EvLevel = "lvl2" | "lvl3";
|
|
12
|
+
|
|
13
|
+
/** A charger on the map: `name` is the station's name; chargers have no value. */
|
|
14
|
+
export interface EvFeatureProperties extends MapFeatureProperties {
|
|
15
|
+
level: EvLevel;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type FetchEvStations = (level: EvLevel, signal?: AbortSignal) => Promise<EvStation[]>;
|
|
19
|
+
|
|
20
|
+
export interface EvStore {
|
|
21
|
+
/** One level's chargers for its plugin; `loading` until the first `load(level)`. */
|
|
22
|
+
dataSource(level: EvLevel): DataSource<EvFeatureProperties>;
|
|
23
|
+
/**
|
|
24
|
+
* Fetches a level once; overlapping calls share the run. A failure sets the status to `error`
|
|
25
|
+
* (its `retry()` fetches again) and rejects.
|
|
26
|
+
*/
|
|
27
|
+
load(level: EvLevel): Promise<void>;
|
|
28
|
+
/** A loaded station by id, from either level. */
|
|
29
|
+
station(id: string): EvStation | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** NREL has this station in the wrong place, so it is left out (as in 4.x). */
|
|
33
|
+
const DROPPED_STATION = 226154;
|
|
34
|
+
|
|
35
|
+
function toFeatures(
|
|
36
|
+
stations: EvStation[],
|
|
37
|
+
level: EvLevel
|
|
38
|
+
): FeatureCollection<Point, EvFeatureProperties> {
|
|
39
|
+
return {
|
|
40
|
+
type: "FeatureCollection",
|
|
41
|
+
features: stations.map((station) => ({
|
|
42
|
+
type: "Feature",
|
|
43
|
+
properties: {
|
|
44
|
+
refKey: encodeRef({ kind: "feature", id: String(station.id) }),
|
|
45
|
+
name: station.station_name ?? "",
|
|
46
|
+
value: null,
|
|
47
|
+
level
|
|
48
|
+
},
|
|
49
|
+
geometry: { type: "Point", coordinates: [station.longitude, station.latitude] }
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
class EvLevelSource implements DataSource<EvFeatureProperties> {
|
|
55
|
+
status: DataStatus = $state.raw({ state: "loading" });
|
|
56
|
+
features: FeatureCollection<Point, EvFeatureProperties> | null = $state.raw(null);
|
|
57
|
+
readonly scale = null;
|
|
58
|
+
stations: EvStation[] = [];
|
|
59
|
+
|
|
60
|
+
#run: Promise<void> | null = null;
|
|
61
|
+
#loaded = false;
|
|
62
|
+
|
|
63
|
+
constructor(
|
|
64
|
+
private level: EvLevel,
|
|
65
|
+
private fetchStations: FetchEvStations
|
|
66
|
+
) {}
|
|
67
|
+
|
|
68
|
+
load(): Promise<void> {
|
|
69
|
+
if (this.#loaded) return Promise.resolve();
|
|
70
|
+
this.#run ??= this.#fetch();
|
|
71
|
+
return this.#run;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async #fetch(): Promise<void> {
|
|
75
|
+
if (this.status.state !== "loading") this.status = { state: "loading" };
|
|
76
|
+
try {
|
|
77
|
+
const stations = (await this.fetchStations(this.level)).filter(
|
|
78
|
+
(station) => station.id !== DROPPED_STATION
|
|
79
|
+
);
|
|
80
|
+
this.stations = stations;
|
|
81
|
+
this.features = toFeatures(stations, this.level);
|
|
82
|
+
this.status = { state: "ready" };
|
|
83
|
+
this.#loaded = true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
this.features = null;
|
|
86
|
+
this.status = {
|
|
87
|
+
state: "error",
|
|
88
|
+
error,
|
|
89
|
+
retry: () => {
|
|
90
|
+
this.load().catch(() => {});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
throw error;
|
|
94
|
+
} finally {
|
|
95
|
+
this.#run = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** EV chargers by level, each fetched when first wanted. */
|
|
101
|
+
export function createEvStore(fetchStations: FetchEvStations = fetchEvStations): EvStore {
|
|
102
|
+
const levels = {
|
|
103
|
+
lvl2: new EvLevelSource("lvl2", fetchStations),
|
|
104
|
+
lvl3: new EvLevelSource("lvl3", fetchStations)
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
dataSource: (level) => levels[level],
|
|
108
|
+
load: (level) => levels[level].load(),
|
|
109
|
+
station(id) {
|
|
110
|
+
for (const source of Object.values(levels)) {
|
|
111
|
+
const found = source.stations.find((station) => String(station.id) === id);
|
|
112
|
+
if (found) return found;
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { mount, unmount, untrack } from "svelte";
|
|
2
|
+
import { Popup } from "@maptiler/sdk";
|
|
3
|
+
import { maptilerMap, type MapContext } from "@sjvair/map-sdk";
|
|
4
|
+
import EvPopup from "./EvPopup.svelte";
|
|
5
|
+
import type { EvStation } from "./types";
|
|
6
|
+
|
|
7
|
+
export interface StationPopups {
|
|
8
|
+
/** Opens `station`'s popup at `lngLat`, closing any other; it belongs to plugin `pluginId`. */
|
|
9
|
+
open(pluginId: string, station: EvStation, lngLat: [number, number]): void;
|
|
10
|
+
close(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The EV station popup, 4.x's (a MapTiler `Popup` with a close button that a map click leaves
|
|
15
|
+
* open). One is open at a time; it closes on a basemap switch, when its plugin leaves the map,
|
|
16
|
+
* and when the GL map is replaced. Call it during component setup: its effects live as long as
|
|
17
|
+
* the component.
|
|
18
|
+
*/
|
|
19
|
+
export function stationPopups(map: MapContext): StationPopups {
|
|
20
|
+
let current: { popup: Popup; pluginId: string } | null = $state.raw(null);
|
|
21
|
+
|
|
22
|
+
function close() {
|
|
23
|
+
const open = current;
|
|
24
|
+
current = null;
|
|
25
|
+
open?.popup.remove();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// A basemap switch or a new GL map closes it.
|
|
29
|
+
$effect(() => {
|
|
30
|
+
void map.basemap.current;
|
|
31
|
+
void map.gl;
|
|
32
|
+
untrack(close);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// So does its plugin leaving the map (its level turned off). A Marker Clusters flip removes and
|
|
36
|
+
// re-adds the plugin in one pass, so the popup stays: the station is still there.
|
|
37
|
+
$effect(() => {
|
|
38
|
+
const ids = map.plugins.map((plugin) => plugin.id);
|
|
39
|
+
const open = current;
|
|
40
|
+
if (open && !ids.includes(open.pluginId)) untrack(close);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
$effect(() => () => untrack(close));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
open(pluginId, station, lngLat) {
|
|
47
|
+
close();
|
|
48
|
+
const gl = maptilerMap(map);
|
|
49
|
+
if (!gl) return;
|
|
50
|
+
const target = document.createElement("div");
|
|
51
|
+
const instance = mount(EvPopup, { target, props: { station } });
|
|
52
|
+
const popup = new Popup({ closeButton: true, closeOnClick: false, maxWidth: "none" })
|
|
53
|
+
.setLngLat(lngLat)
|
|
54
|
+
.setDOMContent(target);
|
|
55
|
+
popup.on("close", () => {
|
|
56
|
+
unmount(instance);
|
|
57
|
+
if (current?.popup === popup) current = null;
|
|
58
|
+
});
|
|
59
|
+
current = { popup, pluginId };
|
|
60
|
+
popup.addTo(gl);
|
|
61
|
+
},
|
|
62
|
+
close
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { Feature, Geometry } from "geojson";
|
|
2
|
-
import type { MapGeoJSONFeature } from "@maptiler/sdk";
|
|
3
|
-
|
|
4
1
|
export interface EvStation {
|
|
5
2
|
access_code: string;
|
|
6
3
|
access_days_time: string;
|
|
@@ -25,34 +22,3 @@ export interface EvStation {
|
|
|
25
22
|
ev_network: string;
|
|
26
23
|
ev_pricing: string;
|
|
27
24
|
}
|
|
28
|
-
|
|
29
|
-
export interface EvStationMarkerProperties {
|
|
30
|
-
icon: string;
|
|
31
|
-
id: number;
|
|
32
|
-
level: "lvl2" | "lvl3";
|
|
33
|
-
station_name: string;
|
|
34
|
-
facility_type: string;
|
|
35
|
-
ev_network: string;
|
|
36
|
-
station_phone: string;
|
|
37
|
-
street_address: string;
|
|
38
|
-
city: string;
|
|
39
|
-
state: string;
|
|
40
|
-
zip: string;
|
|
41
|
-
access_days_time: string;
|
|
42
|
-
access_detail_code: string;
|
|
43
|
-
cards_accepted: string | null;
|
|
44
|
-
ev_pricing: string;
|
|
45
|
-
ev_connector_types: string; // JSON.stringify(string[])
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type EvStationMapFeature = Feature<Geometry, EvStationMarkerProperties>;
|
|
49
|
-
|
|
50
|
-
export interface EvStationClusterMapFeature extends MapGeoJSONFeature {
|
|
51
|
-
properties: {
|
|
52
|
-
cluster: true;
|
|
53
|
-
cluster_id: number;
|
|
54
|
-
point_count: number;
|
|
55
|
-
point_count_abbreviated: string | number;
|
|
56
|
-
};
|
|
57
|
-
// level is derived from feature.source: "ev-stations-lvl2" | "ev-stations-lvl3"
|
|
58
|
-
}
|
|
@@ -48,7 +48,6 @@ export const SanJoaquin = [
|
|
|
48
48
|
export const Stanislaus = [
|
|
49
49
|
"95355",
|
|
50
50
|
"95350",
|
|
51
|
-
"94550",
|
|
52
51
|
"95351",
|
|
53
52
|
"95307",
|
|
54
53
|
"95380",
|
|
@@ -80,7 +79,6 @@ export const Stanislaus = [
|
|
|
80
79
|
];
|
|
81
80
|
|
|
82
81
|
export const Merced = [
|
|
83
|
-
"95023",
|
|
84
82
|
"95380",
|
|
85
83
|
"93635",
|
|
86
84
|
"95301",
|
|
@@ -101,13 +99,11 @@ export const Merced = [
|
|
|
101
99
|
"95369",
|
|
102
100
|
"95303",
|
|
103
101
|
"95317",
|
|
104
|
-
"95325",
|
|
105
102
|
"93665",
|
|
106
103
|
"95342",
|
|
107
104
|
"95344",
|
|
108
105
|
"95343",
|
|
109
106
|
"93661",
|
|
110
|
-
"09534",
|
|
111
107
|
"95312"
|
|
112
108
|
];
|
|
113
109
|
|
|
@@ -141,7 +137,6 @@ export const Fresno = [
|
|
|
141
137
|
"93706",
|
|
142
138
|
"93711",
|
|
143
139
|
"93705",
|
|
144
|
-
"93245",
|
|
145
140
|
"93612",
|
|
146
141
|
"93657",
|
|
147
142
|
"93710",
|
|
@@ -189,7 +184,6 @@ export const Fresno = [
|
|
|
189
184
|
"93652",
|
|
190
185
|
"93627",
|
|
191
186
|
"93606",
|
|
192
|
-
"93665",
|
|
193
187
|
"93628",
|
|
194
188
|
"93605",
|
|
195
189
|
"93607",
|
|
@@ -235,8 +229,7 @@ export const Fresno = [
|
|
|
235
229
|
"93888",
|
|
236
230
|
"93844",
|
|
237
231
|
"93784",
|
|
238
|
-
"93780"
|
|
239
|
-
"93633"
|
|
232
|
+
"93780"
|
|
240
233
|
];
|
|
241
234
|
|
|
242
235
|
export const Kings = [
|
|
@@ -247,7 +240,6 @@ export const Kings = [
|
|
|
247
240
|
"93204",
|
|
248
241
|
"93656",
|
|
249
242
|
"93242",
|
|
250
|
-
"93249",
|
|
251
243
|
"93266",
|
|
252
244
|
"93201",
|
|
253
245
|
"93202",
|
|
@@ -288,12 +280,9 @@ export const Tulare = [
|
|
|
288
280
|
"93238",
|
|
289
281
|
"93261",
|
|
290
282
|
"93641",
|
|
291
|
-
"93255",
|
|
292
283
|
"93603",
|
|
293
284
|
"93244",
|
|
294
285
|
"93262",
|
|
295
|
-
"93549",
|
|
296
|
-
"93628",
|
|
297
286
|
"93208",
|
|
298
287
|
"93227",
|
|
299
288
|
"93237",
|
|
@@ -412,7 +401,6 @@ export const Calaveras = [
|
|
|
412
401
|
export const Tuolumne = [
|
|
413
402
|
"95370",
|
|
414
403
|
"95327",
|
|
415
|
-
"95247",
|
|
416
404
|
"95383",
|
|
417
405
|
"95321",
|
|
418
406
|
"95379",
|
|
@@ -423,8 +411,6 @@ export const Tuolumne = [
|
|
|
423
411
|
"95310",
|
|
424
412
|
"95346",
|
|
425
413
|
"95364",
|
|
426
|
-
"95224",
|
|
427
|
-
"93541",
|
|
428
414
|
"95305",
|
|
429
415
|
"95335",
|
|
430
416
|
"95314",
|
|
@@ -438,7 +424,6 @@ export const Mono = ["93514", "93546", "93517", "96107", "93529", "96133", "9354
|
|
|
438
424
|
|
|
439
425
|
export const Mariposa = [
|
|
440
426
|
"95338",
|
|
441
|
-
"95333",
|
|
442
427
|
"93601",
|
|
443
428
|
"95311",
|
|
444
429
|
"95329",
|
|
@@ -468,3 +453,31 @@ export const Inyo = [
|
|
|
468
453
|
"93522",
|
|
469
454
|
"93530"
|
|
470
455
|
];
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Every county's ZIPs once, sorted: the EV charger query's `zip` list. A ZIP that spans two
|
|
459
|
+
* counties is in both lists above; `scripts/check-ev-zips.mjs` checks the lists against the
|
|
460
|
+
* Census ZCTA-to-county file.
|
|
461
|
+
*
|
|
462
|
+
* The county lists began as a hand-scraped list. 5.0 only removes ZIPs with no land in their
|
|
463
|
+
* county; it adds none. Border ZIPs with some San Joaquin land (95632 Galt, 95641 Isleton,
|
|
464
|
+
* 94505 Discovery Bay) are left out (decided 2026-11-07): their chargers sit mostly outside
|
|
465
|
+
* the county. Revisit if users ask for chargers just over the county line.
|
|
466
|
+
*/
|
|
467
|
+
export const EV_ZIP_CODES: readonly string[] = [
|
|
468
|
+
...new Set([
|
|
469
|
+
...SanJoaquin,
|
|
470
|
+
...Stanislaus,
|
|
471
|
+
...Merced,
|
|
472
|
+
...Madera,
|
|
473
|
+
...Fresno,
|
|
474
|
+
...Kings,
|
|
475
|
+
...Tulare,
|
|
476
|
+
...Kern,
|
|
477
|
+
...Calaveras,
|
|
478
|
+
...Tuolumne,
|
|
479
|
+
...Mono,
|
|
480
|
+
...Mariposa,
|
|
481
|
+
...Inyo
|
|
482
|
+
])
|
|
483
|
+
].sort();
|