차량 목적지 경로 미리보기 추가

This commit is contained in:
2026-06-06 19:10:24 +09:00
parent 34bb805400
commit 1dc93c5c78
4 changed files with 564 additions and 7 deletions

View File

@@ -565,6 +565,14 @@ function publishSimulatorLocations() {
function createSimulatorLocationBody(vehicle, nowMs) {
const routeState = interpolateRoute(vehicle.route, nowMs, vehicle.loopMs, hashOffset(vehicle.driverId));
const speedWave = Math.sin((nowMs + hashOffset(vehicle.vehicleNo)) / 17000) * 8;
const speed = Math.max(18, Math.round(vehicle.speedBase + speedWave));
const routePath = createSimulatedRoutePath(
{ latitude: routeState.latitude, longitude: routeState.longitude },
routeState.destinationPoint,
vehicle.driverId
);
const remainingDistanceKm = roundNumber(calculatePathDistanceKm(routePath) * 1.08, 1);
const etaMinutes = Math.max(3, Math.round((remainingDistanceKm / Math.max(speed, 24)) * 60));
return {
driverId: vehicle.driverId,
@@ -573,13 +581,20 @@ function createSimulatorLocationBody(vehicle, nowMs) {
latitude: routeState.latitude,
longitude: routeState.longitude,
accuracy: 8,
speed: Math.max(18, Math.round(vehicle.speedBase + speedWave)),
speed,
heading: routeState.heading,
cargoName: vehicle.cargoName,
cargoQuantity: vehicle.cargoQuantity,
cargoWeight: vehicle.cargoWeight,
origin: routeState.origin,
destination: routeState.destination,
destinationLatitude: routeState.destinationPoint.latitude,
destinationLongitude: routeState.destinationPoint.longitude,
routePath,
routeSource: "simulated",
remainingDistanceKm,
etaMinutes,
estimatedArrivalAt: new Date(nowMs + etaMinutes * 60 * 1000).toISOString(),
provider: "simulator",
simulated: true,
recordedAt: new Date(nowMs).toISOString()
@@ -599,7 +614,8 @@ function interpolateRoute(route, nowMs, loopMs, offsetMs) {
longitude: interpolateNumber(from.longitude, to.longitude, localProgress),
heading: Math.round(calculateBearing(from, to)),
origin: from.name,
destination: to.name
destination: to.name,
destinationPoint: to
};
}
@@ -751,6 +767,13 @@ function createLocationRecord(body, driver) {
cargoWeight: optionalString(body.cargoWeight || body.weight, 40),
origin: optionalString(body.origin || body.departure, 80),
destination: optionalString(body.destination || body.arrival, 80),
destinationLatitude: optionalNumber(body.destinationLatitude || body.destinationLat || body.arrivalLatitude || body.arrivalLat),
destinationLongitude: optionalNumber(body.destinationLongitude || body.destinationLng || body.destinationLon || body.arrivalLongitude || body.arrivalLng || body.arrivalLon),
routePath: normalizeRoutePath(body.routePath || body.path || body.polyline),
routeSource: optionalString(body.routeSource || body.routingProvider, 40),
remainingDistanceKm: optionalNumber(body.remainingDistanceKm || body.distanceKm || body.routeDistanceKm),
etaMinutes: optionalNumber(body.etaMinutes || body.estimatedMinutes || body.remainingMinutes),
estimatedArrivalAt: normalizeOptionalDate(body.estimatedArrivalAt || body.etaAt || body.arrivalAt),
provider: body.provider ? String(body.provider) : "",
simulated: Boolean(body.simulated || driver.simulated),
recordedAt: normalizeDate(body.recordedAt),
@@ -767,6 +790,81 @@ function point(name, latitude, longitude) {
return { name, latitude, longitude };
}
function createSimulatedRoutePath(from, to, seed) {
const deltaLatitude = to.latitude - from.latitude;
const deltaLongitude = to.longitude - from.longitude;
const length = Math.sqrt(deltaLatitude ** 2 + deltaLongitude ** 2) || 1;
const offsetDirection = hashOffset(seed) % 2 === 0 ? 1 : -1;
const offsetSize = Math.min(0.32, Math.max(0.025, length * 0.14));
const perpendicularLatitude = (-deltaLongitude / length) * offsetSize * offsetDirection;
const perpendicularLongitude = (deltaLatitude / length) * offsetSize * offsetDirection;
return [
normalizeRoutePoint(from),
normalizeRoutePoint({
latitude: interpolateNumber(from.latitude, to.latitude, 0.35) + perpendicularLatitude,
longitude: interpolateNumber(from.longitude, to.longitude, 0.35) + perpendicularLongitude
}),
normalizeRoutePoint({
latitude: interpolateNumber(from.latitude, to.latitude, 0.72) - perpendicularLatitude * 0.35,
longitude: interpolateNumber(from.longitude, to.longitude, 0.72) - perpendicularLongitude * 0.35
}),
normalizeRoutePoint(to)
];
}
function normalizeRoutePath(value) {
if (!Array.isArray(value)) return [];
return value
.map((pointValue) => normalizeRoutePoint(pointValue))
.filter(Boolean)
.slice(0, 48);
}
function normalizeRoutePoint(value) {
if (!value) return null;
const latitude = Array.isArray(value)
? Number(value[0])
: Number(value.latitude ?? value.lat);
const longitude = Array.isArray(value)
? Number(value[1])
: Number(value.longitude ?? value.lng ?? value.lon);
if (!Number.isFinite(latitude) || latitude < -90 || latitude > 90) return null;
if (!Number.isFinite(longitude) || longitude < -180 || longitude > 180) return null;
return {
latitude: roundNumber(latitude, 6),
longitude: roundNumber(longitude, 6)
};
}
function calculatePathDistanceKm(routePath) {
let distance = 0;
for (let index = 1; index < routePath.length; index += 1) {
distance += calculateDistanceKm(routePath[index - 1], routePath[index]);
}
return distance;
}
function calculateDistanceKm(from, to) {
const earthRadiusKm = 6371;
const deltaLatitude = toRadians(to.latitude - from.latitude);
const deltaLongitude = toRadians(to.longitude - from.longitude);
const fromLatitude = toRadians(from.latitude);
const toLatitude = toRadians(to.latitude);
const a = Math.sin(deltaLatitude / 2) ** 2 +
Math.cos(fromLatitude) * Math.cos(toLatitude) * Math.sin(deltaLongitude / 2) ** 2;
return earthRadiusKm * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
function roundNumber(value, decimals) {
const multiplier = 10 ** decimals;
return Math.round(value * multiplier) / multiplier;
}
function smoothStep(value) {
return value * value * (3 - 2 * value);
}
@@ -833,6 +931,12 @@ function normalizeDate(value) {
return Number.isNaN(parsed.getTime()) ? new Date().toISOString() : parsed.toISOString();
}
function normalizeOptionalDate(value) {
if (!value) return null;
const parsed = new Date(value);
return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
}
function readHistory({ driverId, limit }) {
if (!fs.existsSync(HISTORY_FILE)) return [];