@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
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
/** The chart's color stops from a range scale; a level with no lower bound or color is skipped. */
|
|
2
|
+
export function levelColors(scale) {
|
|
3
|
+
return (scale?.levels ?? []).flatMap((level) => {
|
|
4
|
+
if (level.min === null || !level.color)
|
|
5
|
+
return [];
|
|
6
|
+
return [[level.min, level.color.replace(/^#/, "")]];
|
|
7
|
+
});
|
|
5
8
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The chart's zone: times and days are Pacific, whatever zone the device is in. */
|
|
2
|
+
export declare const PACIFIC_TIME_ZONE = "America/Los_Angeles";
|
|
3
|
+
/**
|
|
4
|
+
* The x-axis end: `now` when the data ends earlier on the current Pacific day (so the axis
|
|
5
|
+
* reaches the present), else the data's end. Both are Unix seconds.
|
|
6
|
+
*/
|
|
7
|
+
export declare function chartRangeMax(max: number, now?: Date): number;
|
|
8
|
+
/** A Unix time as Pacific wall-clock time, e.g. "Nov 4, 2026 8:05 PM". */
|
|
9
|
+
export declare function formatPacificTime(unixSeconds: number): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { startOfPacificDay } from "@sjvair/sdk/datetime/pacific";
|
|
2
|
+
/** The chart's zone: times and days are Pacific, whatever zone the device is in. */
|
|
3
|
+
export const PACIFIC_TIME_ZONE = "America/Los_Angeles";
|
|
4
|
+
/**
|
|
5
|
+
* The x-axis end: `now` when the data ends earlier on the current Pacific day (so the axis
|
|
6
|
+
* reaches the present), else the data's end. Both are Unix seconds.
|
|
7
|
+
*/
|
|
8
|
+
export function chartRangeMax(max, now = new Date()) {
|
|
9
|
+
const end = new Date(max * 1000);
|
|
10
|
+
const today = startOfPacificDay(end).getTime() === startOfPacificDay(now).getTime();
|
|
11
|
+
return today && now > end ? Math.floor(now.getTime() / 1000) : max;
|
|
12
|
+
}
|
|
13
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
14
|
+
timeZone: PACIFIC_TIME_ZONE,
|
|
15
|
+
month: "short",
|
|
16
|
+
day: "numeric",
|
|
17
|
+
year: "numeric",
|
|
18
|
+
hour: "numeric",
|
|
19
|
+
minute: "2-digit"
|
|
20
|
+
});
|
|
21
|
+
/** A Unix time as Pacific wall-clock time, e.g. "Nov 4, 2026 8:05 PM". */
|
|
22
|
+
export function formatPacificTime(unixSeconds) {
|
|
23
|
+
const part = Object.fromEntries(formatter.formatToParts(new Date(unixSeconds * 1000)).map((p) => [p.type, p.value]));
|
|
24
|
+
return `${part.month} ${part.day}, ${part.year} ${part.hour}:${part.minute} ${part.dayPeriod}`;
|
|
25
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatPacificTime } from "./pacific-time";
|
|
2
2
|
let cursLeft = -100;
|
|
3
3
|
let cursTop = -100;
|
|
4
4
|
export const uPlotCursorConfig = {
|
|
@@ -124,7 +124,7 @@ export function tooltipsPlugin() {
|
|
|
124
124
|
tt.style.display = "block";
|
|
125
125
|
let label = u.series[i].label;
|
|
126
126
|
label = label?.substring(label.indexOf("(") + 1, label.indexOf(")")) || label;
|
|
127
|
-
ttHeader.textContent =
|
|
127
|
+
ttHeader.textContent = formatPacificTime(xVal);
|
|
128
128
|
tt.textContent = `${label}: ${yVal}`;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
@@ -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,14 @@
|
|
|
1
|
+
import { type MapPlugin } from "@sjvair/map-sdk";
|
|
2
|
+
import type { DataSource } from "@sjvair/map-sdk/data";
|
|
3
|
+
import type { EvFeatureProperties, EvLevel } from "./ev-store.svelte.js";
|
|
4
|
+
export type EvPluginId = "ev-l2" | "ev-l3";
|
|
5
|
+
export declare const EV_PLUGIN_IDS: Record<EvLevel, EvPluginId>;
|
|
6
|
+
/**
|
|
7
|
+
* One level of EV chargers, as 4.x drew them: the level's charger icon and, clustered, bubbles in
|
|
8
|
+
* the level's color (radius 15/20/25 by count, with a white count) that zoom in on click. Stations
|
|
9
|
+
* are listed for the feature list; a click on one is a `featureclick` with a `feature` ref whose
|
|
10
|
+
* id is the station id (the layout opens the popup).
|
|
11
|
+
*/
|
|
12
|
+
export declare function evPlugin(level: EvLevel, source: DataSource<EvFeatureProperties>, options: {
|
|
13
|
+
clustered: boolean;
|
|
14
|
+
}): MapPlugin;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createRawSnippet } from "svelte";
|
|
2
|
+
import { pointsPlugin } from "@sjvair/map-sdk";
|
|
3
|
+
import { EV_STATION_LEVELS, evStationIcon } from "./ev-station-icons";
|
|
4
|
+
export const EV_PLUGIN_IDS = { lvl2: "ev-l2", lvl3: "ev-l3" };
|
|
5
|
+
const LEVEL_LABELS = { lvl2: "Level 2", lvl3: "Level 3" };
|
|
6
|
+
/** The marker size, as in 4.x. */
|
|
7
|
+
const ICON_SIZE = 32;
|
|
8
|
+
/** "rgb(r, g, b)" at 0.6 alpha, the cluster bubbles' fill in 4.x. */
|
|
9
|
+
function clusterFill(rgb) {
|
|
10
|
+
return rgb.replace("rgb(", "rgba(").replace(")", ", 0.6)");
|
|
11
|
+
}
|
|
12
|
+
/** 4.x's cluster tooltip: "Level 2 Chargers" over "N stations". */
|
|
13
|
+
function clusterTooltip(level, count) {
|
|
14
|
+
return createRawSnippet(() => ({
|
|
15
|
+
render: () => `<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>`
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One level of EV chargers, as 4.x drew them: the level's charger icon and, clustered, bubbles in
|
|
20
|
+
* the level's color (radius 15/20/25 by count, with a white count) that zoom in on click. Stations
|
|
21
|
+
* are listed for the feature list; a click on one is a `featureclick` with a `feature` ref whose
|
|
22
|
+
* id is the station id (the layout opens the popup).
|
|
23
|
+
*/
|
|
24
|
+
export function evPlugin(level, source, options) {
|
|
25
|
+
const { bg, border } = EV_STATION_LEVELS[level];
|
|
26
|
+
const icon = evStationIcon(bg, border);
|
|
27
|
+
return pointsPlugin({
|
|
28
|
+
id: EV_PLUGIN_IDS[level],
|
|
29
|
+
source,
|
|
30
|
+
layerLabel: `${LEVEL_LABELS[level]} Chargers`,
|
|
31
|
+
selectable: false,
|
|
32
|
+
icon: { category: () => level, draw: () => icon, size: ICON_SIZE },
|
|
33
|
+
detail: () => `${LEVEL_LABELS[level]} charger`,
|
|
34
|
+
...(options.clustered && {
|
|
35
|
+
cluster: {
|
|
36
|
+
radius: 40,
|
|
37
|
+
maxZoom: 9,
|
|
38
|
+
layers: ({ sourceId }) => [
|
|
39
|
+
{
|
|
40
|
+
local: "cluster-circle",
|
|
41
|
+
spec: {
|
|
42
|
+
type: "circle",
|
|
43
|
+
source: sourceId,
|
|
44
|
+
filter: ["has", "point_count"],
|
|
45
|
+
paint: {
|
|
46
|
+
"circle-color": clusterFill(bg),
|
|
47
|
+
"circle-radius": ["step", ["get", "point_count"], 15, 10, 20, 25, 25]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
local: "cluster-label",
|
|
53
|
+
spec: {
|
|
54
|
+
type: "symbol",
|
|
55
|
+
source: sourceId,
|
|
56
|
+
filter: ["has", "point_count"],
|
|
57
|
+
layout: {
|
|
58
|
+
"text-field": ["get", "point_count_abbreviated"],
|
|
59
|
+
"text-size": 12,
|
|
60
|
+
"text-allow-overlap": true,
|
|
61
|
+
"text-ignore-placement": true
|
|
62
|
+
},
|
|
63
|
+
paint: { "text-color": "#ffffff" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
tooltip: (cluster) => clusterTooltip(level, cluster.pointCount)
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { MapIconManager } from "../map/integrations/map-icon-manager";
|
|
2
1
|
export declare const EV_STATION_LEVELS: {
|
|
3
2
|
readonly lvl2: {
|
|
4
3
|
readonly bg: "rgb(62, 142, 208)";
|
|
@@ -10,7 +9,5 @@ export declare const EV_STATION_LEVELS: {
|
|
|
10
9
|
};
|
|
11
10
|
};
|
|
12
11
|
export type EvStationLevel = keyof typeof EV_STATION_LEVELS;
|
|
12
|
+
/** A charger marker: a circle in the level's colors with the white charger glyph, 32px. */
|
|
13
13
|
export declare function evStationIcon(bg: string, border: string): string;
|
|
14
|
-
export declare class EvStationIconManager extends MapIconManager {
|
|
15
|
-
constructor();
|
|
16
|
-
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { MapIconManager } from "../map/integrations/map-icon-manager";
|
|
2
|
-
import { asDataURI } from "../map/icons";
|
|
3
1
|
const EV_STATION_PATH = "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";
|
|
4
2
|
export const EV_STATION_LEVELS = {
|
|
5
3
|
lvl2: { bg: "rgb(62, 142, 208)", border: "rgb(42, 114, 174)" },
|
|
6
4
|
lvl3: { bg: "rgb(53, 73, 182)", border: "rgb(42, 58, 146)" }
|
|
7
5
|
};
|
|
6
|
+
/** A charger marker: a circle in the level's colors with the white charger glyph, 32px. */
|
|
8
7
|
export function evStationIcon(bg, border) {
|
|
9
8
|
return `<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
|
10
9
|
<circle cx="16" cy="16" r="14" fill="${bg}" stroke="${border}" stroke-width="2"/>
|
|
@@ -13,14 +12,3 @@ export function evStationIcon(bg, border) {
|
|
|
13
12
|
</svg>
|
|
14
13
|
</svg>`;
|
|
15
14
|
}
|
|
16
|
-
export class EvStationIconManager extends MapIconManager {
|
|
17
|
-
constructor() {
|
|
18
|
-
super();
|
|
19
|
-
for (const [level, colors] of Object.entries(EV_STATION_LEVELS)) {
|
|
20
|
-
const id = `ev-station-${level}`;
|
|
21
|
-
const icon = new Image(32, 32);
|
|
22
|
-
icon.src = asDataURI(evStationIcon(colors.bg, colors.border));
|
|
23
|
-
this.register(id, icon);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { EvStation } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Fetches the public electric chargers of one level in the Valley's ZIPs from NREL (key from
|
|
4
|
+
* `monitorMapConfig().nrelKey`), with codes turned into labels. Station 226154 is dropped: its
|
|
5
|
+
* coordinates are wrong.
|
|
6
|
+
*/
|
|
7
|
+
export declare function fetchEvStations(level: "lvl2" | "lvl3", signal?: AbortSignal): Promise<Array<EvStation>>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { accessDetailCodeTypes, creditCardTypes, evConnectorTypes, evNetworkTypes, facilityTypes } from "./data-maps";
|
|
2
|
+
import { monitorMapConfig } from "../config.svelte.js";
|
|
3
|
+
import { EV_ZIP_CODES } from "./zip-codes";
|
|
4
|
+
/** Which NREL charging level each map level asks for. */
|
|
5
|
+
const CHARGING_LEVEL = { lvl2: "2", lvl3: "dc_fast" };
|
|
6
|
+
/**
|
|
7
|
+
* Fetches the public electric chargers of one level in the Valley's ZIPs from NREL (key from
|
|
8
|
+
* `monitorMapConfig().nrelKey`), with codes turned into labels. Station 226154 is dropped: its
|
|
9
|
+
* coordinates are wrong.
|
|
10
|
+
*/
|
|
11
|
+
export async function fetchEvStations(level, signal) {
|
|
12
|
+
if (signal?.aborted)
|
|
13
|
+
throw signal.reason;
|
|
14
|
+
const url = new URL(`https://developer.nlr.gov/api/alt-fuel-stations/v1.json`);
|
|
15
|
+
url.searchParams.set("fuel_type", "ELEC");
|
|
16
|
+
url.searchParams.set("state", "CA");
|
|
17
|
+
url.searchParams.set("status", "E");
|
|
18
|
+
url.searchParams.set("access", "public");
|
|
19
|
+
url.searchParams.set("ev_charging_level", CHARGING_LEVEL[level]);
|
|
20
|
+
url.searchParams.set("zip", EV_ZIP_CODES.join(","));
|
|
21
|
+
url.searchParams.set("api_key", monitorMapConfig().nrelKey);
|
|
22
|
+
const res = await fetch(url, { signal });
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`EV chargers: NREL answered ${res.status}`);
|
|
25
|
+
const data = await res.json();
|
|
26
|
+
const fuelStations = data.fuel_stations;
|
|
27
|
+
if (!Array.isArray(fuelStations))
|
|
28
|
+
throw new Error("EV chargers: no fuel_stations in the answer");
|
|
29
|
+
return (fuelStations
|
|
30
|
+
.map((station) => {
|
|
31
|
+
if (station.access_detail_code) {
|
|
32
|
+
station.access_detail_code = accessDetailCodeTypes.get(station.access_detail_code);
|
|
33
|
+
}
|
|
34
|
+
if (station.cards_accepted) {
|
|
35
|
+
station.cards_accepted = station.cards_accepted
|
|
36
|
+
.split(" ")
|
|
37
|
+
.map((c) => creditCardTypes.get(c))
|
|
38
|
+
.join(", ");
|
|
39
|
+
}
|
|
40
|
+
if (station.ev_connector_types && station.ev_connector_types.length) {
|
|
41
|
+
station.ev_connector_types = station.ev_connector_types.map((t) => evConnectorTypes.get(t));
|
|
42
|
+
}
|
|
43
|
+
if (station.ev_network) {
|
|
44
|
+
station.ev_network = evNetworkTypes.get(station.ev_network);
|
|
45
|
+
}
|
|
46
|
+
if (station.facility_type) {
|
|
47
|
+
station.facility_type = facilityTypes.get(station.facility_type);
|
|
48
|
+
}
|
|
49
|
+
return station;
|
|
50
|
+
})
|
|
51
|
+
// remove stations with invalid coordinates
|
|
52
|
+
.filter((station) => station.id !== 226154));
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DataSource, type MapFeatureProperties } from "@sjvair/map-sdk/data";
|
|
2
|
+
import type { EvStation } from "./types";
|
|
3
|
+
export type EvLevel = "lvl2" | "lvl3";
|
|
4
|
+
/** A charger on the map: `name` is the station's name; chargers have no value. */
|
|
5
|
+
export interface EvFeatureProperties extends MapFeatureProperties {
|
|
6
|
+
level: EvLevel;
|
|
7
|
+
}
|
|
8
|
+
export type FetchEvStations = (level: EvLevel, signal?: AbortSignal) => Promise<EvStation[]>;
|
|
9
|
+
export interface EvStore {
|
|
10
|
+
/** One level's chargers for its plugin; `loading` until the first `load(level)`. */
|
|
11
|
+
dataSource(level: EvLevel): DataSource<EvFeatureProperties>;
|
|
12
|
+
/**
|
|
13
|
+
* Fetches a level once; overlapping calls share the run. A failure sets the status to `error`
|
|
14
|
+
* (its `retry()` fetches again) and rejects.
|
|
15
|
+
*/
|
|
16
|
+
load(level: EvLevel): Promise<void>;
|
|
17
|
+
/** A loaded station by id, from either level. */
|
|
18
|
+
station(id: string): EvStation | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** EV chargers by level, each fetched when first wanted. */
|
|
21
|
+
export declare function createEvStore(fetchStations?: FetchEvStations): EvStore;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { encodeRef } from "@sjvair/map-sdk/data";
|
|
2
|
+
import { fetchEvStations } from "./ev-stations";
|
|
3
|
+
/** NREL has this station in the wrong place, so it is left out (as in 4.x). */
|
|
4
|
+
const DROPPED_STATION = 226154;
|
|
5
|
+
function toFeatures(stations, level) {
|
|
6
|
+
return {
|
|
7
|
+
type: "FeatureCollection",
|
|
8
|
+
features: stations.map((station) => ({
|
|
9
|
+
type: "Feature",
|
|
10
|
+
properties: {
|
|
11
|
+
refKey: encodeRef({ kind: "feature", id: String(station.id) }),
|
|
12
|
+
name: station.station_name ?? "",
|
|
13
|
+
value: null,
|
|
14
|
+
level
|
|
15
|
+
},
|
|
16
|
+
geometry: { type: "Point", coordinates: [station.longitude, station.latitude] }
|
|
17
|
+
}))
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
class EvLevelSource {
|
|
21
|
+
level;
|
|
22
|
+
fetchStations;
|
|
23
|
+
status = $state.raw({ state: "loading" });
|
|
24
|
+
features = $state.raw(null);
|
|
25
|
+
scale = null;
|
|
26
|
+
stations = [];
|
|
27
|
+
#run = null;
|
|
28
|
+
#loaded = false;
|
|
29
|
+
constructor(level, fetchStations) {
|
|
30
|
+
this.level = level;
|
|
31
|
+
this.fetchStations = fetchStations;
|
|
32
|
+
}
|
|
33
|
+
load() {
|
|
34
|
+
if (this.#loaded)
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
this.#run ??= this.#fetch();
|
|
37
|
+
return this.#run;
|
|
38
|
+
}
|
|
39
|
+
async #fetch() {
|
|
40
|
+
if (this.status.state !== "loading")
|
|
41
|
+
this.status = { state: "loading" };
|
|
42
|
+
try {
|
|
43
|
+
const stations = (await this.fetchStations(this.level)).filter((station) => station.id !== DROPPED_STATION);
|
|
44
|
+
this.stations = stations;
|
|
45
|
+
this.features = toFeatures(stations, this.level);
|
|
46
|
+
this.status = { state: "ready" };
|
|
47
|
+
this.#loaded = true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.features = null;
|
|
51
|
+
this.status = {
|
|
52
|
+
state: "error",
|
|
53
|
+
error,
|
|
54
|
+
retry: () => {
|
|
55
|
+
this.load().catch(() => { });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
this.#run = null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** EV chargers by level, each fetched when first wanted. */
|
|
66
|
+
export function createEvStore(fetchStations = fetchEvStations) {
|
|
67
|
+
const levels = {
|
|
68
|
+
lvl2: new EvLevelSource("lvl2", fetchStations),
|
|
69
|
+
lvl3: new EvLevelSource("lvl3", fetchStations)
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
dataSource: (level) => levels[level],
|
|
73
|
+
load: (level) => levels[level].load(),
|
|
74
|
+
station(id) {
|
|
75
|
+
for (const source of Object.values(levels)) {
|
|
76
|
+
const found = source.stations.find((station) => String(station.id) === id);
|
|
77
|
+
if (found)
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type MapContext } from "@sjvair/map-sdk";
|
|
2
|
+
import type { EvStation } from "./types";
|
|
3
|
+
export interface StationPopups {
|
|
4
|
+
/** Opens `station`'s popup at `lngLat`, closing any other; it belongs to plugin `pluginId`. */
|
|
5
|
+
open(pluginId: string, station: EvStation, lngLat: [number, number]): void;
|
|
6
|
+
close(): void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The EV station popup, 4.x's (a MapTiler `Popup` with a close button that a map click leaves
|
|
10
|
+
* open). One is open at a time; it closes on a basemap switch, when its plugin leaves the map,
|
|
11
|
+
* and when the GL map is replaced. Call it during component setup: its effects live as long as
|
|
12
|
+
* the component.
|
|
13
|
+
*/
|
|
14
|
+
export declare function stationPopups(map: MapContext): StationPopups;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { mount, unmount, untrack } from "svelte";
|
|
2
|
+
import { Popup } from "@maptiler/sdk";
|
|
3
|
+
import { maptilerMap } from "@sjvair/map-sdk";
|
|
4
|
+
import EvPopup from "./EvPopup.svelte";
|
|
5
|
+
/**
|
|
6
|
+
* The EV station popup, 4.x's (a MapTiler `Popup` with a close button that a map click leaves
|
|
7
|
+
* open). One is open at a time; it closes on a basemap switch, when its plugin leaves the map,
|
|
8
|
+
* and when the GL map is replaced. Call it during component setup: its effects live as long as
|
|
9
|
+
* the component.
|
|
10
|
+
*/
|
|
11
|
+
export function stationPopups(map) {
|
|
12
|
+
let current = $state.raw(null);
|
|
13
|
+
function close() {
|
|
14
|
+
const open = current;
|
|
15
|
+
current = null;
|
|
16
|
+
open?.popup.remove();
|
|
17
|
+
}
|
|
18
|
+
// A basemap switch or a new GL map closes it.
|
|
19
|
+
$effect(() => {
|
|
20
|
+
void map.basemap.current;
|
|
21
|
+
void map.gl;
|
|
22
|
+
untrack(close);
|
|
23
|
+
});
|
|
24
|
+
// So does its plugin leaving the map (its level turned off). A Marker Clusters flip removes and
|
|
25
|
+
// re-adds the plugin in one pass, so the popup stays: the station is still there.
|
|
26
|
+
$effect(() => {
|
|
27
|
+
const ids = map.plugins.map((plugin) => plugin.id);
|
|
28
|
+
const open = current;
|
|
29
|
+
if (open && !ids.includes(open.pluginId))
|
|
30
|
+
untrack(close);
|
|
31
|
+
});
|
|
32
|
+
$effect(() => () => untrack(close));
|
|
33
|
+
return {
|
|
34
|
+
open(pluginId, station, lngLat) {
|
|
35
|
+
close();
|
|
36
|
+
const gl = maptilerMap(map);
|
|
37
|
+
if (!gl)
|
|
38
|
+
return;
|
|
39
|
+
const target = document.createElement("div");
|
|
40
|
+
const instance = mount(EvPopup, { target, props: { station } });
|
|
41
|
+
const popup = new Popup({ closeButton: true, closeOnClick: false, maxWidth: "none" })
|
|
42
|
+
.setLngLat(lngLat)
|
|
43
|
+
.setDOMContent(target);
|
|
44
|
+
popup.on("close", () => {
|
|
45
|
+
unmount(instance);
|
|
46
|
+
if (current?.popup === popup)
|
|
47
|
+
current = null;
|
|
48
|
+
});
|
|
49
|
+
current = { popup, pluginId };
|
|
50
|
+
popup.addTo(gl);
|
|
51
|
+
},
|
|
52
|
+
close
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { Feature, Geometry } from "geojson";
|
|
2
|
-
import type { MapGeoJSONFeature } from "@maptiler/sdk";
|
|
3
1
|
export interface EvStation {
|
|
4
2
|
access_code: string;
|
|
5
3
|
access_days_time: string;
|
|
@@ -24,30 +22,3 @@ export interface EvStation {
|
|
|
24
22
|
ev_network: string;
|
|
25
23
|
ev_pricing: string;
|
|
26
24
|
}
|
|
27
|
-
export interface EvStationMarkerProperties {
|
|
28
|
-
icon: string;
|
|
29
|
-
id: number;
|
|
30
|
-
level: "lvl2" | "lvl3";
|
|
31
|
-
station_name: string;
|
|
32
|
-
facility_type: string;
|
|
33
|
-
ev_network: string;
|
|
34
|
-
station_phone: string;
|
|
35
|
-
street_address: string;
|
|
36
|
-
city: string;
|
|
37
|
-
state: string;
|
|
38
|
-
zip: string;
|
|
39
|
-
access_days_time: string;
|
|
40
|
-
access_detail_code: string;
|
|
41
|
-
cards_accepted: string | null;
|
|
42
|
-
ev_pricing: string;
|
|
43
|
-
ev_connector_types: string;
|
|
44
|
-
}
|
|
45
|
-
export type EvStationMapFeature = Feature<Geometry, EvStationMarkerProperties>;
|
|
46
|
-
export interface EvStationClusterMapFeature extends MapGeoJSONFeature {
|
|
47
|
-
properties: {
|
|
48
|
-
cluster: true;
|
|
49
|
-
cluster_id: number;
|
|
50
|
-
point_count: number;
|
|
51
|
-
point_count_abbreviated: string | number;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
@@ -11,3 +11,14 @@ export declare const Tuolumne: string[];
|
|
|
11
11
|
export declare const Mono: string[];
|
|
12
12
|
export declare const Mariposa: string[];
|
|
13
13
|
export declare const Inyo: string[];
|
|
14
|
+
/**
|
|
15
|
+
* Every county's ZIPs once, sorted: the EV charger query's `zip` list. A ZIP that spans two
|
|
16
|
+
* counties is in both lists above; `scripts/check-ev-zips.mjs` checks the lists against the
|
|
17
|
+
* Census ZCTA-to-county file.
|
|
18
|
+
*
|
|
19
|
+
* The county lists began as a hand-scraped list. 5.0 only removes ZIPs with no land in their
|
|
20
|
+
* county; it adds none. Border ZIPs with some San Joaquin land (95632 Galt, 95641 Isleton,
|
|
21
|
+
* 94505 Discovery Bay) are left out (decided 2026-11-07): their chargers sit mostly outside
|
|
22
|
+
* the county. Revisit if users ask for chargers just over the county line.
|
|
23
|
+
*/
|
|
24
|
+
export declare const EV_ZIP_CODES: readonly string[];
|
|
@@ -47,7 +47,6 @@ export const SanJoaquin = [
|
|
|
47
47
|
export const Stanislaus = [
|
|
48
48
|
"95355",
|
|
49
49
|
"95350",
|
|
50
|
-
"94550",
|
|
51
50
|
"95351",
|
|
52
51
|
"95307",
|
|
53
52
|
"95380",
|
|
@@ -78,7 +77,6 @@ export const Stanislaus = [
|
|
|
78
77
|
"95397"
|
|
79
78
|
];
|
|
80
79
|
export const Merced = [
|
|
81
|
-
"95023",
|
|
82
80
|
"95380",
|
|
83
81
|
"93635",
|
|
84
82
|
"95301",
|
|
@@ -99,13 +97,11 @@ export const Merced = [
|
|
|
99
97
|
"95369",
|
|
100
98
|
"95303",
|
|
101
99
|
"95317",
|
|
102
|
-
"95325",
|
|
103
100
|
"93665",
|
|
104
101
|
"95342",
|
|
105
102
|
"95344",
|
|
106
103
|
"95343",
|
|
107
104
|
"93661",
|
|
108
|
-
"09534",
|
|
109
105
|
"95312"
|
|
110
106
|
];
|
|
111
107
|
export const Madera = [
|
|
@@ -137,7 +133,6 @@ export const Fresno = [
|
|
|
137
133
|
"93706",
|
|
138
134
|
"93711",
|
|
139
135
|
"93705",
|
|
140
|
-
"93245",
|
|
141
136
|
"93612",
|
|
142
137
|
"93657",
|
|
143
138
|
"93710",
|
|
@@ -185,7 +180,6 @@ export const Fresno = [
|
|
|
185
180
|
"93652",
|
|
186
181
|
"93627",
|
|
187
182
|
"93606",
|
|
188
|
-
"93665",
|
|
189
183
|
"93628",
|
|
190
184
|
"93605",
|
|
191
185
|
"93607",
|
|
@@ -231,8 +225,7 @@ export const Fresno = [
|
|
|
231
225
|
"93888",
|
|
232
226
|
"93844",
|
|
233
227
|
"93784",
|
|
234
|
-
"93780"
|
|
235
|
-
"93633"
|
|
228
|
+
"93780"
|
|
236
229
|
];
|
|
237
230
|
export const Kings = [
|
|
238
231
|
"93230",
|
|
@@ -242,7 +235,6 @@ export const Kings = [
|
|
|
242
235
|
"93204",
|
|
243
236
|
"93656",
|
|
244
237
|
"93242",
|
|
245
|
-
"93249",
|
|
246
238
|
"93266",
|
|
247
239
|
"93201",
|
|
248
240
|
"93202",
|
|
@@ -282,12 +274,9 @@ export const Tulare = [
|
|
|
282
274
|
"93238",
|
|
283
275
|
"93261",
|
|
284
276
|
"93641",
|
|
285
|
-
"93255",
|
|
286
277
|
"93603",
|
|
287
278
|
"93244",
|
|
288
279
|
"93262",
|
|
289
|
-
"93549",
|
|
290
|
-
"93628",
|
|
291
280
|
"93208",
|
|
292
281
|
"93227",
|
|
293
282
|
"93237",
|
|
@@ -403,7 +392,6 @@ export const Calaveras = [
|
|
|
403
392
|
export const Tuolumne = [
|
|
404
393
|
"95370",
|
|
405
394
|
"95327",
|
|
406
|
-
"95247",
|
|
407
395
|
"95383",
|
|
408
396
|
"95321",
|
|
409
397
|
"95379",
|
|
@@ -414,8 +402,6 @@ export const Tuolumne = [
|
|
|
414
402
|
"95310",
|
|
415
403
|
"95346",
|
|
416
404
|
"95364",
|
|
417
|
-
"95224",
|
|
418
|
-
"93541",
|
|
419
405
|
"95305",
|
|
420
406
|
"95335",
|
|
421
407
|
"95314",
|
|
@@ -427,7 +413,6 @@ export const Tuolumne = [
|
|
|
427
413
|
export const Mono = ["93514", "93546", "93517", "96107", "93529", "96133", "93541", "93512"];
|
|
428
414
|
export const Mariposa = [
|
|
429
415
|
"95338",
|
|
430
|
-
"95333",
|
|
431
416
|
"93601",
|
|
432
417
|
"95311",
|
|
433
418
|
"95329",
|
|
@@ -456,3 +441,30 @@ export const Inyo = [
|
|
|
456
441
|
"93522",
|
|
457
442
|
"93530"
|
|
458
443
|
];
|
|
444
|
+
/**
|
|
445
|
+
* Every county's ZIPs once, sorted: the EV charger query's `zip` list. A ZIP that spans two
|
|
446
|
+
* counties is in both lists above; `scripts/check-ev-zips.mjs` checks the lists against the
|
|
447
|
+
* Census ZCTA-to-county file.
|
|
448
|
+
*
|
|
449
|
+
* The county lists began as a hand-scraped list. 5.0 only removes ZIPs with no land in their
|
|
450
|
+
* county; it adds none. Border ZIPs with some San Joaquin land (95632 Galt, 95641 Isleton,
|
|
451
|
+
* 94505 Discovery Bay) are left out (decided 2026-11-07): their chargers sit mostly outside
|
|
452
|
+
* the county. Revisit if users ask for chargers just over the county line.
|
|
453
|
+
*/
|
|
454
|
+
export const EV_ZIP_CODES = [
|
|
455
|
+
...new Set([
|
|
456
|
+
...SanJoaquin,
|
|
457
|
+
...Stanislaus,
|
|
458
|
+
...Merced,
|
|
459
|
+
...Madera,
|
|
460
|
+
...Fresno,
|
|
461
|
+
...Kings,
|
|
462
|
+
...Tulare,
|
|
463
|
+
...Kern,
|
|
464
|
+
...Calaveras,
|
|
465
|
+
...Tuolumne,
|
|
466
|
+
...Mono,
|
|
467
|
+
...Mariposa,
|
|
468
|
+
...Inyo
|
|
469
|
+
])
|
|
470
|
+
].sort();
|