{stationLabels.map((l, idx) => (
{l.name}
{l.sub ? (
{l.sub}
) : null}
))}
{sightData?.map((s: any, i: number) => {
const centerLat = routeData?.center_latitude;
const centerLon = routeData?.center_longitude;
if (centerLat === undefined || centerLon === undefined) return null;
const cos = Math.cos(rotationAngle);
const sin = Math.sin(rotationAngle);
const local = coordinatesToLocal(
s.latitude - centerLat,
s.longitude - centerLon
);
const x = local.x * UP_SCALE;
const y = local.y * UP_SCALE;
const rx = x * cos - y * sin;
const ry = x * sin + y * cos;
const dpr = Math.max(
1,
(typeof window !== "undefined" && window.devicePixelRatio) || 1
);
const sx = (rx * scale + position.x) / dpr;
const sy = (ry * scale + position.y) / dpr;
const size = 30;
const handleSightClick = () => {
const {
setSelectedSightId,
setIsManualSelection,
setIsRightWidgetSelectorOpen,
closeGovernorModal,
} = useGeolocationStore();
setSelectedSightId(String(s.id));
setIsManualSelection(true);
setIsRightWidgetSelectorOpen(false);
closeGovernorModal();
};
return (

);
})}
{(() => {
if (!routeData) return null;
const centerLat = routeData.center_latitude;
const centerLon = routeData.center_longitude;
if (centerLat === undefined || centerLon === undefined) return null;
const coords: any = apiStore?.context?.currentCoordinates;
if (!coords) return null;
const local = coordinatesToLocal(
coords.latitude - centerLat,
coords.longitude - centerLon
);
const wx = local.x * UP_SCALE;
const wy = local.y * UP_SCALE;
const cosR = Math.cos(rotationAngle);
const sinR = Math.sin(rotationAngle);
const rx = wx * cosR - wy * sinR;
const ry = wx * sinR + wy * cosR;
const dpr2 = Math.max(
1,
(typeof window !== "undefined" && window.devicePixelRatio) || 1
);
const screenX = (rx * scale + position.x) / dpr2;
const screenY = (ry * scale + position.y) / dpr2;
const pathPts: { x: number; y: number }[] = [];
for (let i = 0; i < routePath.length; i += 2)
pathPts.push({ x: routePath[i], y: routePath[i + 1] });
const stationsForAngle = (stationData || []).map((st: any) => {
const loc = coordinatesToLocal(
st.latitude - centerLat,
st.longitude - centerLon
);
const x = loc.x * UP_SCALE,
y = loc.y * UP_SCALE;
const rx2 = x * cosR - y * sinR,
ry2 = x * sinR + y * cosR;
return {
longitude: rx2,
latitude: ry2,
offset_x: st.offset_x,
offset_y: st.offset_y,
};
});
let tramSegIndex = -1;
if (routePath.length >= 4) {
let best = -1,
bestD = Infinity;
for (let i = 0; i < routePath.length - 2; i += 2) {
const p1x = routePath[i],
p1y = routePath[i + 1];
const p2x = routePath[i + 2],
p2y = routePath[i + 3];
const dx = p2x - p1x,
dy = p2y - p1y;
const len2 = dx * dx + dy * dy;
if (!len2) continue;
const t = ((rx - p1x) * dx + (ry - p1y) * dy) / len2;
const cl = Math.max(0, Math.min(1, t));
const px = p1x + cl * dx,
py = p1y + cl * dy;
const d = Math.hypot(rx - px, ry - py);
if (d < bestD) {
bestD = d;
best = i / 2;
}
}
tramSegIndex = best;
}
const optimalAngle = (() => {
const testRadiusInMap = 100 / scale;
const minPath = 60,
minPassed = 60,
minStation = 50;
let bestAng = 0,
bestScore = Infinity;
for (let i = 0; i < 12; i++) {
const ang = (i * Math.PI * 2) / 12;
const tx = rx + Math.cos(ang) * testRadiusInMap;
const ty = ry + Math.sin(ang) * testRadiusInMap;
const distPath = (function () {
if (pathPts.length < 2) return Infinity;
let md = Infinity;
for (let k = 0; k < pathPts.length - 1; k++) {
const p1 = pathPts[k],
p2 = pathPts[k + 1];
const L2 = (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2;
if (!L2) continue;
const tt =
((tx - p1.x) * (p2.x - p1.x) +
(ty - p1.y) * (p2.y - p1.y)) /
L2;
const cl = Math.max(0, Math.min(1, tt));
const px = p1.x + cl * (p2.x - p1.x),
py = p1.y + cl * (p2.y - p1.y);
const d = Math.hypot(tx - px, ty - py);
if (d < md) md = d;
}
return md * scale;
})();
const distPassed = (function () {
if (pathPts.length < 2 || tramSegIndex < 0) return Infinity;
let md = Infinity;
for (
let k = 0;
k <= Math.min(tramSegIndex, pathPts.length - 2);
k++
) {
const p1 = pathPts[k],
p2 = pathPts[k + 1];
const L2 = (p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2;
if (!L2) continue;
const tt =
((tx - p1.x) * (p2.x - p1.x) +
(ty - p1.y) * (p2.y - p1.y)) /
L2;
const cl = Math.max(0, Math.min(1, tt));
const px = p1.x + cl * (p2.x - p1.x),
py = p1.y + cl * (p2.y - p1.y);
const d = Math.hypot(tx - px, ty - py);
if (d < md) md = d;
}
return md * scale;
})();
const distStation = (function () {
if (!stationsForAngle.length) return Infinity;
const DEFAULT_LABEL_OFFSET_X = 25,
DEFAULT_LABEL_OFFSET_Y = 0;
let md = Infinity;
for (const st of stationsForAngle) {
const offsetX =
st.offset_x === 0 && st.offset_y === 0
? DEFAULT_LABEL_OFFSET_X
: st.offset_x || 0 * 3;
const offsetY =
st.offset_x === 0 && st.offset_y === 0
? DEFAULT_LABEL_OFFSET_Y
: st.offset_y || 0 * 3;
const lx = st.longitude + offsetX,
ly = st.latitude + offsetY;
const d = Math.hypot(tx - lx, ty - ly);
if (d < md) md = d;
}
return md * scale;
})();
let weight = 0;
if (distPath < minPath) weight += 100 * (1 - distPath / minPath);
if (distPassed < minPassed)
weight += 10 * (1 - distPassed / minPassed);
if (distStation < minStation)
weight += 1000 * (1 - distStation / minStation);
if (weight < bestScore) {
bestScore = weight;
bestAng = ang;
}
}
return bestAng;
})();
return (
);
})()}