148 lines
4.1 KiB
TypeScript
148 lines
4.1 KiB
TypeScript
export type HydrogenHeatmapMetric = 'kg' | 'refuels' | 'vehicles';
|
|
export type HydrogenPayer = 'all' | 'lingniu' | 'customer';
|
|
|
|
export type HydrogenStationRecord = {
|
|
station_id: string;
|
|
station_name: string;
|
|
address: string;
|
|
longitude: string | number;
|
|
latitude: string | number;
|
|
kg: string | number;
|
|
refuel_count: string | number;
|
|
vehicle_count: string | number;
|
|
first_refuel: string;
|
|
last_refuel: string;
|
|
};
|
|
|
|
type HydrogenHeatmapQueryInput = {
|
|
startDate?: string;
|
|
endDate?: string;
|
|
query?: string;
|
|
payer?: string;
|
|
metric?: string;
|
|
};
|
|
|
|
type NearbyQueryInput = {
|
|
lng?: string;
|
|
lat?: string;
|
|
radiusKm?: string;
|
|
};
|
|
|
|
export const HYDROGEN_HEATMAP_DEFAULT_START = '2026-01-01';
|
|
export const HYDROGEN_HEATMAP_DEFAULT_END = '2026-07-13';
|
|
|
|
function isDate(value: string | undefined): value is string {
|
|
return Boolean(value && /^\d{4}-\d{2}-\d{2}$/.test(value));
|
|
}
|
|
|
|
function parseMetric(value: string | undefined): HydrogenHeatmapMetric {
|
|
if (value === 'refuels' || value === 'vehicles') return value;
|
|
return 'kg';
|
|
}
|
|
|
|
function parsePayer(value: string | undefined): HydrogenPayer {
|
|
if (value === 'lingniu' || value === 'customer') return value;
|
|
return 'all';
|
|
}
|
|
|
|
export function parseHydrogenHeatmapQuery(input: HydrogenHeatmapQueryInput) {
|
|
return {
|
|
startDate: isDate(input.startDate) ? input.startDate : HYDROGEN_HEATMAP_DEFAULT_START,
|
|
endDate: isDate(input.endDate) ? input.endDate : HYDROGEN_HEATMAP_DEFAULT_END,
|
|
query: (input.query || '').trim(),
|
|
payer: parsePayer(input.payer),
|
|
metric: parseMetric(input.metric),
|
|
};
|
|
}
|
|
|
|
export function parseNearbyQuery(input: NearbyQueryInput) {
|
|
const longitude = Number(input.lng);
|
|
const latitude = Number(input.lat);
|
|
return {
|
|
center: Number.isFinite(longitude) && Number.isFinite(latitude)
|
|
? { lng: longitude, lat: latitude }
|
|
: null,
|
|
radiusKm: Math.min(200, Math.max(5, Number(input.radiusKm) || 50)),
|
|
};
|
|
}
|
|
|
|
export function hydrogenMetricValue(
|
|
row: HydrogenStationRecord,
|
|
metric: HydrogenHeatmapMetric,
|
|
): number {
|
|
if (metric === 'refuels') return Number(row.refuel_count) || 0;
|
|
if (metric === 'vehicles') return Number(row.vehicle_count) || 0;
|
|
return Number(row.kg) || 0;
|
|
}
|
|
|
|
export function serializeHydrogenStation(
|
|
row: HydrogenStationRecord,
|
|
metric: HydrogenHeatmapMetric,
|
|
) {
|
|
return {
|
|
stationId: row.station_id,
|
|
stationName: row.station_name,
|
|
address: row.address,
|
|
lng: Number(row.longitude),
|
|
lat: Number(row.latitude),
|
|
kg: Number(row.kg) || 0,
|
|
refuelCount: Number(row.refuel_count) || 0,
|
|
vehicleCount: Number(row.vehicle_count) || 0,
|
|
firstRefuel: row.first_refuel,
|
|
lastRefuel: row.last_refuel,
|
|
metricValue: hydrogenMetricValue(row, metric),
|
|
};
|
|
}
|
|
|
|
export function rankHydrogenStations(
|
|
stations: HydrogenStationRecord[],
|
|
metric: HydrogenHeatmapMetric,
|
|
): HydrogenStationRecord[] {
|
|
return [...stations].sort((left, right) => (
|
|
hydrogenMetricValue(right, metric) - hydrogenMetricValue(left, metric)
|
|
));
|
|
}
|
|
|
|
export function buildHydrogenPoints(
|
|
stations: HydrogenStationRecord[],
|
|
metric: HydrogenHeatmapMetric,
|
|
) {
|
|
const points = stations.map((row) => ({
|
|
lng: Number(row.longitude),
|
|
lat: Number(row.latitude),
|
|
count: hydrogenMetricValue(row, metric),
|
|
}));
|
|
return {
|
|
points,
|
|
max: Math.max(1, ...points.map((point) => point.count)),
|
|
};
|
|
}
|
|
|
|
export function haversineKm(
|
|
leftLng: number,
|
|
leftLat: number,
|
|
rightLng: number,
|
|
rightLat: number,
|
|
): number {
|
|
const radians = (degrees: number) => degrees * Math.PI / 180;
|
|
const earthRadius = 6371;
|
|
const dLat = radians(rightLat - leftLat);
|
|
const dLng = radians(rightLng - leftLng);
|
|
const a = Math.sin(dLat / 2) ** 2
|
|
+ Math.cos(radians(leftLat)) * Math.cos(radians(rightLat)) * Math.sin(dLng / 2) ** 2;
|
|
return earthRadius * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
}
|
|
|
|
export function filterHydrogenStationsByRadius(
|
|
stations: HydrogenStationRecord[],
|
|
center: { lng: number; lat: number },
|
|
radiusKm: number,
|
|
): HydrogenStationRecord[] {
|
|
return stations.filter((row) => haversineKm(
|
|
center.lng,
|
|
center.lat,
|
|
Number(row.longitude),
|
|
Number(row.latitude),
|
|
) <= radiusKm);
|
|
}
|