+ {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 (
+
+ );
+ })()}
+