Files
WhiteNightsAdminPanel/src/pages/Route/route-preview/web-gl/web-gl-version.tsx
fisenko 139d9e74ea #19 Фикс ошибки билда + название для маршрутов (#20)
Reviewed-on: #20
Reviewed-by: Микаэл Оганесян <15lu.akari@unprism.ru>
Co-authored-by: fisenko <kkzemeow@gmail.com>
Co-committed-by: fisenko <kkzemeow@gmail.com>
2025-11-07 07:59:11 +00:00

1726 lines
61 KiB
TypeScript

// function initWebGLContext(
// canvas: HTMLCanvasElement
// ): WebGLRenderingContext | null {
// const gl =
// (canvas.getContext("webgl") as WebGLRenderingContext | null) ||
// (canvas.getContext("experimental-webgl") as WebGLRenderingContext | null);
// return gl;
// }
// function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement): boolean {
// const dpr = Math.max(1, window.devicePixelRatio || 1);
// const displayWidth = Math.floor(canvas.clientWidth * dpr);
// const displayHeight = Math.floor(canvas.clientHeight * dpr);
// if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
// canvas.width = displayWidth;
// canvas.height = displayHeight;
// return true;
// }
// return false;
// }
// export const WebGLMap = observer(() => {
// const canvasRef = useRef<HTMLCanvasElement | null>(null);
// const glRef = useRef<WebGLRenderingContext | null>(null);
// const programRef = useRef<WebGLProgram | null>(null);
// const bufferRef = useRef<WebGLBuffer | null>(null);
// const pointProgramRef = useRef<WebGLProgram | null>(null);
// const pointBufferRef = useRef<WebGLBuffer | null>(null);
// const screenLineProgramRef = useRef<WebGLProgram | null>(null);
// const screenLineBufferRef = useRef<WebGLBuffer | null>(null);
// const attribsRef = useRef<{ a_pos: number } | null>(null);
// const uniformsRef = useRef<{
// u_cameraPos: WebGLUniformLocation | null;
// u_scale: WebGLUniformLocation | null;
// u_resolution: WebGLUniformLocation | null;
// u_color: WebGLUniformLocation | null;
// } | null>(null);
// const { routeData, stationData, stationDataEn, stationDataZh, sightData } =
// useMapData() as any;
// const {
// position,
// scale,
// setPosition,
// setScale,
// isAutoMode,
// setIsAutoMode,
// screenCenter,
// setScreenCenter,
// userActivityTimestamp,
// updateUserActivity,
// } = useTransform();
// const cameraAnimationStore = useCameraAnimationStore();
// const scaleLimitsRef = useRef({
// min: null as number | null,
// max: null as number | null,
// });
// useEffect(() => {
// if (
// routeData?.scale_min !== undefined &&
// routeData?.scale_max !== undefined
// ) {
// scaleLimitsRef.current = {
// min: routeData.scale_min / 10,
// max: routeData.scale_max / 10,
// };
// }
// }, [routeData?.scale_min, routeData?.scale_max]);
// const clampScale = useCallback((value: number) => {
// const { min, max } = scaleLimitsRef.current;
// if (min === null || max === null) {
// return value;
// }
// const clampedValue = Math.max(min, Math.min(max, value));
// return clampedValue;
// }, []);
// const { selectedLanguage } = useGeolocationStore();
// const positionRef = useRef(position);
// const scaleRef = useRef(scale);
// const setPositionRef = useRef(setPosition);
// const setScaleRef = useRef(setScale);
// useEffect(() => {
// setPositionRef.current = setPosition;
// }, [setPosition]);
// useEffect(() => {
// setScaleRef.current = setScale;
// }, [setScale]);
// useEffect(() => {
// if (routeData) {
// }
// }, [routeData]);
// useEffect(() => {
// positionRef.current = position;
// }, [position]);
// useEffect(() => {
// scaleRef.current = scale;
// }, [scale]);
// const rotationAngle = useMemo(() => {
// const deg = (routeData as any)?.rotate ?? 0;
// return (deg * Math.PI) / 180;
// }, [routeData]);
// const {
// position: animatedYellowDotPosition,
// animateTo: animateYellowDotTo,
// setPositionImmediate: setYellowDotPositionImmediate,
// } = useAnimatedPolarPosition(0, 0, 800);
// const routePath = useMemo(() => {
// if (!routeData?.path || routeData?.path.length === 0)
// return new Float32Array();
// const centerLat = routeData.center_latitude;
// const centerLon = routeData.center_longitude;
// if (centerLat === undefined || centerLon === undefined)
// return new Float32Array();
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const verts: number[] = [];
// for (const [lat, lon] of routeData.path) {
// const local = coordinatesToLocal(lat - centerLat, lon - 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;
// verts.push(rx, ry);
// }
// return new Float32Array(verts);
// }, [
// routeData?.path,
// routeData?.center_latitude,
// routeData?.center_longitude,
// rotationAngle,
// ]);
// const transformedTramCoords = useMemo(() => {
// 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 x = local.x * UP_SCALE;
// const y = local.y * UP_SCALE;
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const rx = x * cos - y * sin;
// const ry = x * sin + y * cos;
// return { x: rx, y: ry };
// }, [
// routeData?.center_latitude,
// routeData?.center_longitude,
// apiStore?.context?.currentCoordinates,
// rotationAngle,
// ]);
// useEffect(() => {
// const callback = (newPos: { x: number; y: number }, newZoom: number) => {
// setPosition(newPos);
// setScale(newZoom);
// };
// cameraAnimationStore.setUpdateCallback(callback);
// cameraAnimationStore.syncState(position, scale);
// return () => {
// cameraAnimationStore.setUpdateCallback(null);
// };
// }, []);
// useEffect(() => {
// if (
// routeData?.scale_min !== undefined &&
// routeData?.scale_max !== undefined
// ) {
// cameraAnimationStore.setMaxZoom(routeData.scale_max / SCALE_FACTOR);
// cameraAnimationStore.setMinZoom(routeData.scale_min / SCALE_FACTOR);
// }
// }, [routeData?.scale_min, routeData?.scale_max, cameraAnimationStore]);
// useEffect(() => {
// const interval = setInterval(() => {
// const timeSinceActivity = Date.now() - userActivityTimestamp;
// if (timeSinceActivity >= 5000 && !isAutoMode) {
// setIsAutoMode(true);
// }
// }, 1000);
// return () => clearInterval(interval);
// }, [userActivityTimestamp, isAutoMode, setIsAutoMode]);
// useEffect(() => {
// if (cameraAnimationStore.isActivelyAnimating) {
// return;
// }
// if (isAutoMode && transformedTramCoords && screenCenter) {
// const transformedStations = stationData
// ? stationData
// .map((station: any) => {
// const centerLat = routeData?.center_latitude;
// const centerLon = routeData?.center_longitude;
// if (centerLat === undefined || centerLon === undefined)
// return null;
// const local = coordinatesToLocal(
// station.latitude - centerLat,
// station.longitude - centerLon
// );
// const x = local.x * UP_SCALE;
// const y = local.y * UP_SCALE;
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const rx = x * cos - y * sin;
// const ry = x * sin + y * cos;
// return {
// longitude: rx,
// latitude: ry,
// id: station.id,
// };
// })
// .filter(Boolean)
// : [];
// if (
// transformedTramCoords &&
// screenCenter &&
// transformedStations &&
// scaleLimitsRef.current !== null &&
// scaleLimitsRef.current.max !== null &&
// scaleLimitsRef.current.min &&
// scaleLimitsRef.current.min !== null
// ) {
// cameraAnimationStore.setMaxZoom(scaleLimitsRef.current!.max);
// cameraAnimationStore.setMinZoom(scaleLimitsRef.current!.min);
// cameraAnimationStore.syncState(positionRef.current, scaleRef.current);
// cameraAnimationStore.followTram(
// transformedTramCoords,
// screenCenter,
// transformedStations
// );
// }
// } else if (!isAutoMode) {
// cameraAnimationStore.stopAnimation();
// }
// }, [
// isAutoMode,
// transformedTramCoords,
// screenCenter,
// cameraAnimationStore,
// stationData,
// routeData,
// rotationAngle,
// ]);
// const stationLabels = useMemo(() => {
// if (!stationData || !routeData)
// return [] as Array<{ x: number; y: number; name: string; sub?: string }>;
// const centerLat = routeData.center_latitude;
// const centerLon = routeData.center_longitude;
// if (centerLat === undefined || centerLon === undefined) return [];
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const result: Array<{ x: number; y: number; name: string; sub?: string }> =
// [];
// for (let i = 0; i < stationData.length; i++) {
// const st = stationData[i];
// const local = coordinatesToLocal(
// st.latitude - centerLat,
// st.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 DEFAULT_LABEL_OFFSET_X = 25;
// const DEFAULT_LABEL_OFFSET_Y = 0;
// const labelOffsetX =
// st.offset_x === 0 && st.offset_y === 0
// ? DEFAULT_LABEL_OFFSET_X
// : st.offset_x;
// const labelOffsetY =
// st.offset_x === 0 && st.offset_y === 0
// ? DEFAULT_LABEL_OFFSET_Y
// : st.offset_y;
// const textBlockPositionX = rx + labelOffsetX;
// const textBlockPositionY = ry + labelOffsetY;
// const dpr = Math.max(
// 1,
// (typeof window !== "undefined" && window.devicePixelRatio) || 1
// );
// const sx = (textBlockPositionX * scale + position.x) / dpr;
// const sy = (textBlockPositionY * scale + position.y) / dpr;
// let sub: string | undefined;
// if ((selectedLanguage as any) === "zh")
// sub = (stationDataZh as any)?.[i]?.name;
// else if (
// (selectedLanguage as any) === "en" ||
// (selectedLanguage as any) === "ru"
// )
// sub = (stationDataEn as any)?.[i]?.name;
// result.push({ x: sx, y: sy, name: st.name, sub });
// }
// return result;
// }, [
// stationData,
// stationDataEn as any,
// stationDataZh as any,
// position.x,
// position.y,
// scale,
// routeData?.center_latitude,
// routeData?.center_longitude,
// rotationAngle,
// selectedLanguage as any,
// ]);
// const stationPoints = useMemo(() => {
// if (!stationData || !routeData) return new Float32Array();
// const centerLat = routeData.center_latitude;
// const centerLon = routeData.center_longitude;
// if (centerLat === undefined || centerLon === undefined)
// return new Float32Array();
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const verts: number[] = [];
// for (const s of stationData) {
// 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;
// verts.push(rx, ry);
// }
// return new Float32Array(verts);
// }, [
// stationData,
// routeData?.center_latitude,
// routeData?.center_longitude,
// rotationAngle,
// ]);
// const sightPoints = useMemo(() => {
// if (!sightData || !routeData) return new Float32Array();
// const centerLat = routeData.center_latitude;
// const centerLon = routeData.center_longitude;
// if (centerLat === undefined || centerLon === undefined)
// return new Float32Array();
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const verts: number[] = [];
// for (const s of sightData) {
// 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;
// verts.push(rx, ry);
// }
// return new Float32Array(verts);
// }, [
// sightData,
// routeData?.center_latitude,
// routeData?.center_longitude,
// rotationAngle,
// ]);
// useEffect(() => {
// const canvas = canvasRef.current;
// if (!canvas) return;
// const gl = initWebGLContext(canvas);
// glRef.current = gl;
// if (!gl) return;
// const vertSrc = `
// attribute vec2 a_pos;
// uniform vec2 u_cameraPos;
// uniform float u_scale;
// uniform vec2 u_resolution;
// void main() {
// vec2 screen = a_pos * u_scale + u_cameraPos;
// vec2 zeroToOne = screen / u_resolution;
// vec2 zeroToTwo = zeroToOne * 2.0;
// vec2 clip = zeroToTwo - 1.0;
// gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
// }
// `;
// const fragSrc = `
// precision mediump float;
// uniform vec4 u_color;
// void main() {
// gl_FragColor = u_color;
// }
// `;
// const compile = (type: number, src: string) => {
// const s = gl.createShader(type)!;
// gl.shaderSource(s, src);
// gl.compileShader(s);
// return s;
// };
// const vs = compile(gl.VERTEX_SHADER, vertSrc);
// const fs = compile(gl.FRAGMENT_SHADER, fragSrc);
// const prog = gl.createProgram()!;
// gl.attachShader(prog, vs);
// gl.attachShader(prog, fs);
// gl.linkProgram(prog);
// programRef.current = prog;
// gl.useProgram(prog);
// const a_pos = gl.getAttribLocation(prog, "a_pos");
// const u_cameraPos = gl.getUniformLocation(prog, "u_cameraPos");
// const u_scale = gl.getUniformLocation(prog, "u_scale");
// const u_resolution = gl.getUniformLocation(prog, "u_resolution");
// const u_color = gl.getUniformLocation(prog, "u_color");
// attribsRef.current = { a_pos };
// uniformsRef.current = { u_cameraPos, u_scale, u_resolution, u_color };
// const buffer = gl.createBuffer();
// bufferRef.current = buffer;
// const pointVert = `
// attribute vec2 a_pos;
// uniform vec2 u_cameraPos;
// uniform float u_scale;
// uniform vec2 u_resolution;
// uniform float u_pointSize;
// void main() {
// vec2 screen = a_pos * u_scale + u_cameraPos;
// vec2 zeroToOne = screen / u_resolution;
// vec2 zeroToTwo = zeroToOne * 2.0;
// vec2 clip = zeroToTwo - 1.0;
// gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
// gl_PointSize = u_pointSize;
// }
// `;
// const pointFrag = `
// precision mediump float;
// uniform vec4 u_color;
// void main() {
// vec2 c = gl_PointCoord * 2.0 - 1.0;
// float d = dot(c, c);
// if (d > 1.0) discard;
// gl_FragColor = u_color;
// }
// `;
// const vs2 = compile(gl.VERTEX_SHADER, pointVert);
// const fs2 = compile(gl.FRAGMENT_SHADER, pointFrag);
// const pprog = gl.createProgram()!;
// gl.attachShader(pprog, vs2);
// gl.attachShader(pprog, fs2);
// gl.linkProgram(pprog);
// pointProgramRef.current = pprog;
// pointBufferRef.current = gl.createBuffer();
// const lineVert = `
// attribute vec2 a_screen;
// uniform vec2 u_resolution;
// void main(){
// vec2 zeroToOne = a_screen / u_resolution;
// vec2 clip = zeroToOne * 2.0 - 1.0;
// gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
// }
// `;
// const lineFrag = `
// precision mediump float;
// uniform vec4 u_color;
// void main(){ gl_FragColor = u_color; }
// `;
// const lv = compile(gl.VERTEX_SHADER, lineVert);
// const lf = compile(gl.FRAGMENT_SHADER, lineFrag);
// const lprog = gl.createProgram()!;
// gl.attachShader(lprog, lv);
// gl.attachShader(lprog, lf);
// gl.linkProgram(lprog);
// screenLineProgramRef.current = lprog;
// screenLineBufferRef.current = gl.createBuffer();
// const handleResize = () => {
// const changed = resizeCanvasToDisplaySize(canvas);
// if (!gl) return;
// setScreenCenter({
// x: canvas.width / 2,
// y: canvas.height / 2,
// });
// if (changed) {
// gl.viewport(0, 0, canvas.width, canvas.height);
// }
// gl.clearColor(0, 0, 0, 1);
// gl.clear(gl.COLOR_BUFFER_BIT);
// };
// handleResize();
// window.addEventListener("resize", handleResize);
// return () => {
// window.removeEventListener("resize", handleResize);
// };
// }, []);
// useEffect(() => {
// const centerLat = routeData?.center_latitude;
// const centerLon = routeData?.center_longitude;
// if (centerLat !== undefined && centerLon !== undefined) {
// const coords: any = apiStore?.context?.currentCoordinates;
// if (coords) {
// const local = coordinatesToLocal(
// coords.latitude - centerLat,
// coords.longitude - centerLon
// );
// const x = local.x * UP_SCALE;
// const y = local.y * UP_SCALE;
// const cos = Math.cos(rotationAngle),
// sin = Math.sin(rotationAngle);
// const rx = x * cos - y * sin;
// const ry = x * sin + y * cos;
// if (isAutoMode) {
// animateYellowDotTo(rx, ry);
// } else {
// setYellowDotPositionImmediate(rx, ry);
// }
// }
// }
// }, [
// routeData?.center_latitude,
// routeData?.center_longitude,
// rotationAngle,
// apiStore?.context?.currentCoordinates?.latitude,
// apiStore?.context?.currentCoordinates?.longitude,
// isAutoMode,
// animateYellowDotTo,
// setYellowDotPositionImmediate,
// ]);
// useEffect(() => {
// const gl = glRef.current;
// const canvas = canvasRef.current;
// const prog = programRef.current;
// const buffer = bufferRef.current;
// const attribs = attribsRef.current;
// const uniforms = uniformsRef.current;
// const pprog = pointProgramRef.current;
// const pbuffer = pointBufferRef.current;
// if (
// !gl ||
// !canvas ||
// !prog ||
// !buffer ||
// !attribs ||
// !uniforms ||
// !pprog ||
// !pbuffer
// )
// return;
// gl.viewport(0, 0, canvas.width, canvas.height);
// gl.clearColor(0, 0, 0, 1);
// gl.clear(gl.COLOR_BUFFER_BIT);
// gl.useProgram(prog);
// gl.uniform2f(uniforms.u_cameraPos, position.x, position.y);
// gl.uniform1f(uniforms.u_scale, scale);
// gl.uniform2f(uniforms.u_resolution, canvas.width, canvas.height);
// gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
// gl.bufferData(gl.ARRAY_BUFFER, routePath, gl.STATIC_DRAW);
// gl.enableVertexAttribArray(attribs.a_pos);
// gl.vertexAttribPointer(attribs.a_pos, 2, gl.FLOAT, false, 0, 0);
// const vcount = routePath.length / 2;
// let tramSegIndex = -1;
// {
// const centerLatTmp = routeData?.center_latitude;
// const centerLonTmp = routeData?.center_longitude;
// if (centerLatTmp !== undefined && centerLonTmp !== undefined) {
// const coordsAny: any = apiStore?.context?.currentCoordinates;
// if (coordsAny) {
// const loc = coordinatesToLocal(
// coordsAny.latitude - centerLatTmp,
// coordsAny.longitude - centerLonTmp
// );
// const wx = loc.x * UP_SCALE;
// const wy = loc.y * UP_SCALE;
// const cosR = Math.cos(rotationAngle),
// sinR = Math.sin(rotationAngle);
// const tX = wx * cosR - wy * sinR;
// const tY = wx * sinR + wy * cosR;
// let best = -1,
// bestD = Infinity;
// for (let i = 0; i < vcount - 1; i++) {
// const p1x = routePath[i * 2],
// p1y = routePath[i * 2 + 1];
// const p2x = routePath[(i + 1) * 2],
// p2y = routePath[(i + 1) * 2 + 1];
// const dx = p2x - p1x,
// dy = p2y - p1y;
// const len2 = dx * dx + dy * dy;
// if (!len2) continue;
// const t = ((tX - p1x) * dx + (tY - 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(tX - px, tY - py);
// if (d < bestD) {
// bestD = d;
// best = i;
// }
// }
// tramSegIndex = best;
// }
// }
// }
// const vertexCount = routePath.length / 2;
// if (vertexCount > 1) {
// const generateThickLine = (points: Float32Array, width: number) => {
// const vertices: number[] = [];
// const halfWidth = width / 2;
// if (points.length < 4) return new Float32Array();
// for (let i = 0; i < points.length - 2; i += 2) {
// const x1 = points[i];
// const y1 = points[i + 1];
// const x2 = points[i + 2];
// const y2 = points[i + 3];
// const dx = x2 - x1;
// const dy = y2 - y1;
// const length = Math.sqrt(dx * dx + dy * dy);
// if (length === 0) continue;
// const perpX = (-dy / length) * halfWidth;
// const perpY = (dx / length) * halfWidth;
// vertices.push(x1 + perpX, y1 + perpY);
// vertices.push(x1 - perpX, y1 - perpY);
// vertices.push(x2 + perpX, y2 + perpY);
// vertices.push(x1 - perpX, y1 - perpY);
// vertices.push(x2 - perpX, y2 - perpY);
// vertices.push(x2 + perpX, y2 + perpY);
// if (i < points.length - 4) {
// const x3 = points[i + 4];
// const y3 = points[i + 5];
// const dx2 = x3 - x2;
// const dy2 = y3 - y2;
// const length2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
// if (length2 > 0) {
// const perpX2 = (-dy2 / length2) * halfWidth;
// const perpY2 = (dx2 / length2) * halfWidth;
// vertices.push(x2 + perpX, y2 + perpY);
// vertices.push(x2 - perpX, y2 - perpY);
// vertices.push(x2 + perpX2, y2 + perpY2);
// vertices.push(x2 - perpX, y2 - perpY);
// vertices.push(x2 - perpX2, y2 - perpY2);
// vertices.push(x2 + perpX2, y2 + perpY2);
// }
// }
// }
// return new Float32Array(vertices);
// };
// const lineWidth = Math.min(6);
// const r1 = ((PATH_COLOR >> 16) & 0xff) / 255;
// const g1 = ((PATH_COLOR >> 8) & 0xff) / 255;
// const b1 = (PATH_COLOR & 0xff) / 255;
// gl.uniform4f(uniforms.u_color, r1, g1, b1, 1);
// if (tramSegIndex >= 0) {
// const animatedPos = animatedYellowDotPosition;
// if (
// animatedPos &&
// animatedPos.x !== undefined &&
// animatedPos.y !== undefined
// ) {
// const passedPoints: number[] = [];
// for (let i = 0; i <= tramSegIndex; i++) {
// passedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
// }
// passedPoints.push(animatedPos.x, animatedPos.y);
// if (passedPoints.length >= 4) {
// const thickLineVertices = generateThickLine(
// new Float32Array(passedPoints),
// lineWidth
// );
// gl.bufferData(gl.ARRAY_BUFFER, thickLineVertices, gl.STATIC_DRAW);
// gl.drawArrays(gl.TRIANGLES, 0, thickLineVertices.length / 2);
// }
// }
// }
// const r2 = ((UNPASSED_STATION_COLOR >> 16) & 0xff) / 255;
// const g2 = ((UNPASSED_STATION_COLOR >> 8) & 0xff) / 255;
// const b2 = (UNPASSED_STATION_COLOR & 0xff) / 255;
// gl.uniform4f(uniforms.u_color, r2, g2, b2, 1);
// const animatedPos = animatedYellowDotPosition;
// if (
// animatedPos &&
// animatedPos.x !== undefined &&
// animatedPos.y !== undefined
// ) {
// const unpassedPoints: number[] = [];
// unpassedPoints.push(animatedPos.x, animatedPos.y);
// for (let i = tramSegIndex + 1; i < vertexCount; i++) {
// unpassedPoints.push(routePath[i * 2], routePath[i * 2 + 1]);
// }
// if (unpassedPoints.length >= 4) {
// const thickLineVertices = generateThickLine(
// new Float32Array(unpassedPoints),
// lineWidth
// );
// gl.bufferData(gl.ARRAY_BUFFER, thickLineVertices, gl.STATIC_DRAW);
// gl.drawArrays(gl.TRIANGLES, 0, thickLineVertices.length / 2);
// }
// }
// }
// if (stationPoints.length > 0) {
// gl.useProgram(pprog);
// const a_pos_pts = gl.getAttribLocation(pprog, "a_pos");
// const u_cameraPos_pts = gl.getUniformLocation(pprog, "u_cameraPos");
// const u_scale_pts = gl.getUniformLocation(pprog, "u_scale");
// const u_resolution_pts = gl.getUniformLocation(pprog, "u_resolution");
// const u_pointSize = gl.getUniformLocation(pprog, "u_pointSize");
// const u_color_pts = gl.getUniformLocation(pprog, "u_color");
// gl.uniform2f(u_cameraPos_pts, position.x, position.y);
// gl.uniform1f(u_scale_pts, scale);
// gl.uniform2f(u_resolution_pts, canvas.width, canvas.height);
// gl.bindBuffer(gl.ARRAY_BUFFER, pbuffer);
// gl.bufferData(gl.ARRAY_BUFFER, stationPoints, gl.STATIC_DRAW);
// gl.enableVertexAttribArray(a_pos_pts);
// gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// gl.uniform1f(u_pointSize, 10 * scale * 1.5);
// const r_outline = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
// const g_outline = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
// const b_outline = (BACKGROUND_COLOR & 0xff) / 255;
// gl.uniform4f(u_color_pts, r_outline, g_outline, b_outline, 1);
// gl.drawArrays(gl.POINTS, 0, stationPoints.length / 2);
// gl.uniform1f(u_pointSize, 8.0 * scale * 1.5);
// if (tramSegIndex >= 0) {
// const passedStations = [];
// for (let i = 0; i < stationData.length; i++) {
// if (i <= tramSegIndex) {
// passedStations.push(stationPoints[i * 2], stationPoints[i * 2 + 1]);
// }
// }
// if (passedStations.length > 0) {
// const r_passed = ((PATH_COLOR >> 16) & 0xff) / 255;
// const g_passed = ((PATH_COLOR >> 8) & 0xff) / 255;
// const b_passed = (PATH_COLOR & 0xff) / 255;
// gl.uniform4f(u_color_pts, r_passed, g_passed, b_passed, 1);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// new Float32Array(passedStations),
// gl.STATIC_DRAW
// );
// gl.drawArrays(gl.POINTS, 0, passedStations.length / 2);
// }
// }
// if (tramSegIndex >= 0) {
// const unpassedStations = [];
// for (let i = 0; i < stationData.length; i++) {
// if (i > tramSegIndex) {
// unpassedStations.push(
// stationPoints[i * 2],
// stationPoints[i * 2 + 1]
// );
// }
// }
// if (unpassedStations.length > 0) {
// const r_unpassed = ((UNPASSED_STATION_COLOR >> 16) & 0xff) / 255;
// const g_unpassed = ((UNPASSED_STATION_COLOR >> 8) & 0xff) / 255;
// const b_unpassed = (UNPASSED_STATION_COLOR & 0xff) / 255;
// gl.uniform4f(u_color_pts, r_unpassed, g_unpassed, b_unpassed, 1);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// new Float32Array(unpassedStations),
// gl.STATIC_DRAW
// );
// gl.drawArrays(gl.POINTS, 0, unpassedStations.length / 2);
// }
// } else {
// const r_unpassed = ((UNPASSED_STATION_COLOR >> 16) & 0xff) / 255;
// const g_unpassed = ((UNPASSED_STATION_COLOR >> 8) & 0xff) / 255;
// const b_unpassed = (UNPASSED_STATION_COLOR & 0xff) / 255;
// gl.uniform4f(u_color_pts, r_unpassed, g_unpassed, b_unpassed, 1);
// gl.bufferData(gl.ARRAY_BUFFER, stationPoints, gl.STATIC_DRAW);
// gl.drawArrays(gl.POINTS, 0, stationPoints.length / 2);
// }
// }
// const a_pos_pts = gl.getAttribLocation(pprog, "a_pos");
// const u_cameraPos_pts = gl.getUniformLocation(pprog, "u_cameraPos");
// const u_scale_pts = gl.getUniformLocation(pprog, "u_scale");
// const u_resolution_pts = gl.getUniformLocation(pprog, "u_resolution");
// const u_pointSize = gl.getUniformLocation(pprog, "u_pointSize");
// const u_color_pts = gl.getUniformLocation(pprog, "u_color");
// gl.uniform2f(u_cameraPos_pts, position.x, position.y);
// gl.uniform1f(u_scale_pts, scale);
// gl.uniform2f(u_resolution_pts, canvas.width, canvas.height);
// const toPointsArray = (arr: number[]) => new Float32Array(arr);
// 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 getSeg = (px: number, py: number) => {
// if (pathPts.length < 2) return -1;
// let best = -1,
// bestD = Infinity;
// for (let i = 0; i < pathPts.length - 1; i++) {
// const p1 = pathPts[i],
// p2 = pathPts[i + 1];
// const dx = p2.x - p1.x,
// dy = p2.y - p1.y;
// const len2 = dx * dx + dy * dy;
// if (!len2) continue;
// const t = ((px - p1.x) * dx + (py - p1.y) * dy) / len2;
// const tt = Math.max(0, Math.min(1, t));
// const cx = p1.x + tt * dx,
// cy = p1.y + tt * dy;
// const d = Math.hypot(px - cx, py - cy);
// if (d < bestD) {
// bestD = d;
// best = i;
// }
// }
// return best;
// };
// let tramSegForStations = -1;
// {
// const cLat = routeData?.center_latitude,
// cLon = routeData?.center_longitude;
// const tram = apiStore?.context?.currentCoordinates as any;
// if (tram && cLat !== undefined && cLon !== undefined) {
// const loc = coordinatesToLocal(
// tram.latitude - cLat,
// tram.longitude - cLon
// );
// const wx = loc.x * UP_SCALE,
// wy = loc.y * UP_SCALE;
// const cosR = Math.cos(rotationAngle),
// sinR = Math.sin(rotationAngle);
// const tx = wx * cosR - wy * sinR,
// ty = wx * sinR + wy * cosR;
// tramSegForStations = getSeg(tx, ty);
// }
// }
// const passedStations: number[] = [];
// const unpassedStations: number[] = [];
// for (let i = 0; i < stationPoints.length; i += 2) {
// const sx = stationPoints[i],
// sy = stationPoints[i + 1];
// const seg = getSeg(sx, sy);
// if (tramSegForStations !== -1 && seg !== -1 && seg < tramSegForStations)
// passedStations.push(sx, sy);
// else unpassedStations.push(sx, sy);
// }
// const outlineSize = 10.0 * scale * 2,
// coreSize = 8.0 * scale * 2;
// gl.bindBuffer(gl.ARRAY_BUFFER, pbuffer);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// toPointsArray(unpassedStations),
// gl.STREAM_DRAW
// );
// gl.enableVertexAttribArray(a_pos_pts);
// gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// gl.uniform1f(u_pointSize, outlineSize);
// gl.uniform4f(
// u_color_pts,
// ((BACKGROUND_COLOR >> 16) & 255) / 255,
// ((BACKGROUND_COLOR >> 8) & 255) / 255,
// (BACKGROUND_COLOR & 255) / 255,
// 1
// );
// if (unpassedStations.length)
// gl.drawArrays(gl.POINTS, 0, unpassedStations.length / 2);
// gl.uniform1f(u_pointSize, coreSize);
// gl.uniform4f(
// u_color_pts,
// ((UNPASSED_STATION_COLOR >> 16) & 255) / 255,
// ((UNPASSED_STATION_COLOR >> 8) & 255) / 255,
// (UNPASSED_STATION_COLOR & 255) / 255,
// 1
// );
// if (unpassedStations.length)
// gl.drawArrays(gl.POINTS, 0, unpassedStations.length / 2);
// gl.bindBuffer(gl.ARRAY_BUFFER, pbuffer);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// toPointsArray(passedStations),
// gl.STREAM_DRAW
// );
// gl.enableVertexAttribArray(a_pos_pts);
// gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// gl.uniform1f(u_pointSize, outlineSize);
// gl.uniform4f(
// u_color_pts,
// ((BACKGROUND_COLOR >> 16) & 255) / 255,
// ((BACKGROUND_COLOR >> 8) & 255) / 255,
// (BACKGROUND_COLOR & 255) / 255,
// 1
// );
// if (passedStations.length)
// gl.drawArrays(gl.POINTS, 0, passedStations.length / 2);
// gl.uniform1f(u_pointSize, coreSize);
// gl.uniform4f(
// u_color_pts,
// ((PATH_COLOR >> 16) & 255) / 255,
// ((PATH_COLOR >> 8) & 255) / 255,
// (PATH_COLOR & 255) / 255,
// 1
// );
// if (passedStations.length)
// gl.drawArrays(gl.POINTS, 0, passedStations.length / 2);
// if (
// stationData &&
// stationData.length > 0 &&
// routeData &&
// apiStore?.context
// ) {
// const centerLat = routeData.center_latitude;
// const centerLon = routeData.center_longitude;
// if (centerLat !== undefined && centerLon !== undefined) {
// const cos = Math.cos(rotationAngle);
// const sin = Math.sin(rotationAngle);
// const startStationData = stationData.find(
// (station) => station.id.toString() === apiStore.context?.startStopId
// );
// const endStationData = stationData.find(
// (station) => station.id.toString() === apiStore.context?.endStopId
// );
// const terminalStations: number[] = [];
// if (startStationData) {
// const startLocal = coordinatesToLocal(
// startStationData.latitude - centerLat,
// startStationData.longitude - centerLon
// );
// const startX = startLocal.x * UP_SCALE;
// const startY = startLocal.y * UP_SCALE;
// const startRx = startX * cos - startY * sin;
// const startRy = startX * sin + startY * cos;
// terminalStations.push(startRx, startRy);
// }
// if (endStationData) {
// const endLocal = coordinatesToLocal(
// endStationData.latitude - centerLat,
// endStationData.longitude - centerLon
// );
// const endX = endLocal.x * UP_SCALE;
// const endY = endLocal.y * UP_SCALE;
// const endRx = endX * cos - endY * sin;
// const endRy = endX * sin + endY * cos;
// terminalStations.push(endRx, endRy);
// }
// if (terminalStations.length > 0) {
// const terminalStationData: any[] = [];
// if (startStationData) terminalStationData.push(startStationData);
// if (endStationData) terminalStationData.push(endStationData);
// let tramSegIndex = -1;
// const coords: any = apiStore?.context?.currentCoordinates;
// if (coords && centerLat !== undefined && centerLon !== undefined) {
// 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 tx = wx * cosR - wy * sinR;
// const ty = wx * sinR + wy * cosR;
// let best = -1;
// let bestD = Infinity;
// for (let i = 0; i < routePath.length - 2; i += 2) {
// const p1x = routePath[i];
// const p1y = routePath[i + 1];
// const p2x = routePath[i + 2];
// const p2y = routePath[i + 3];
// const dx = p2x - p1x;
// const dy = p2y - p1y;
// const len2 = dx * dx + dy * dy;
// if (!len2) continue;
// const t = ((tx - p1x) * dx + (ty - p1y) * dy) / len2;
// const cl = Math.max(0, Math.min(1, t));
// const px = p1x + cl * dx;
// const py = p1y + cl * dy;
// const d = Math.hypot(tx - px, ty - py);
// if (d < bestD) {
// bestD = d;
// best = i / 2;
// }
// }
// tramSegIndex = best;
// }
// const isStartPassed = startStationData
// ? (() => {
// const sx = terminalStations[0];
// const sy = terminalStations[1];
// const seg = (() => {
// if (routePath.length < 4) return -1;
// let best = -1;
// let bestD = Infinity;
// for (let i = 0; i < routePath.length - 2; i += 2) {
// const p1x = routePath[i];
// const p1y = routePath[i + 1];
// const p2x = routePath[i + 2];
// const p2y = routePath[i + 3];
// const dx = p2x - p1x;
// const dy = p2y - p1y;
// const len2 = dx * dx + dy * dy;
// if (!len2) continue;
// const t = ((sx - p1x) * dx + (sy - p1y) * dy) / len2;
// const cl = Math.max(0, Math.min(1, t));
// const px = p1x + cl * dx;
// const py = p1y + cl * dy;
// const d = Math.hypot(sx - px, sy - py);
// if (d < bestD) {
// bestD = d;
// best = i / 2;
// }
// }
// return best;
// })();
// return tramSegIndex !== -1 && seg !== -1 && seg < tramSegIndex;
// })()
// : false;
// const isEndPassed = endStationData
// ? (() => {
// const ex = terminalStations[terminalStations.length - 2];
// const ey = terminalStations[terminalStations.length - 1];
// const seg = (() => {
// if (routePath.length < 4) return -1;
// let best = -1;
// let bestD = Infinity;
// for (let i = 0; i < routePath.length - 2; i += 2) {
// const p1x = routePath[i];
// const p1y = routePath[i + 1];
// const p2x = routePath[i + 2];
// const p2y = routePath[i + 3];
// const dx = p2x - p1x;
// const dy = p2y - p1y;
// const len2 = dx * dx + dy * dy;
// if (!len2) continue;
// const t = ((ex - p1x) * dx + (ey - p1y) * dy) / len2;
// const cl = Math.max(0, Math.min(1, t));
// const px = p1x + cl * dx;
// const py = p1y + cl * dy;
// const d = Math.hypot(ex - px, ey - py);
// if (d < bestD) {
// bestD = d;
// best = i / 2;
// }
// }
// return best;
// })();
// return tramSegIndex !== -1 && seg !== -1 && seg < tramSegIndex;
// })()
// : false;
// gl.bindBuffer(gl.ARRAY_BUFFER, pbuffer);
// gl.bufferData(
// gl.ARRAY_BUFFER,
// new Float32Array(terminalStations),
// gl.STREAM_DRAW
// );
// gl.enableVertexAttribArray(a_pos_pts);
// gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// gl.uniform1f(u_pointSize, 18.0 * scale);
// if (startStationData && endStationData) {
// if (isStartPassed) {
// gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
// } else {
// gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
// }
// gl.drawArrays(gl.POINTS, 0, 1);
// if (isEndPassed) {
// gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
// } else {
// gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
// }
// gl.drawArrays(gl.POINTS, 1, 1);
// } else {
// const isPassed = startStationData ? isStartPassed : isEndPassed;
// if (isPassed) {
// gl.uniform4f(u_color_pts, 1.0, 0.4, 0.4, 1.0);
// } else {
// gl.uniform4f(u_color_pts, 0.7, 0.7, 0.7, 1.0);
// }
// gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
// }
// gl.uniform1f(u_pointSize, 11.0 * scale);
// const r_center = ((BACKGROUND_COLOR >> 16) & 0xff) / 255;
// const g_center = ((BACKGROUND_COLOR >> 8) & 0xff) / 255;
// const b_center = (BACKGROUND_COLOR & 0xff) / 255;
// gl.uniform4f(u_color_pts, r_center, g_center, b_center, 1.0);
// gl.drawArrays(gl.POINTS, 0, terminalStations.length / 2);
// }
// }
// }
// if (animatedYellowDotPosition) {
// const rx = animatedYellowDotPosition.x;
// const ry = animatedYellowDotPosition.y;
// gl.uniform1f(u_pointSize, 13.3333 * scale);
// gl.uniform4f(u_color_pts, 1.0, 1.0, 0.0, 1.0);
// const tmp = new Float32Array([rx, ry]);
// gl.bindBuffer(gl.ARRAY_BUFFER, pbuffer);
// gl.bufferData(gl.ARRAY_BUFFER, tmp, gl.STREAM_DRAW);
// gl.enableVertexAttribArray(a_pos_pts);
// gl.vertexAttribPointer(a_pos_pts, 2, gl.FLOAT, false, 0, 0);
// gl.drawArrays(gl.POINTS, 0, 1);
// }
// }, [
// routePath,
// stationPoints,
// sightPoints,
// position.x,
// position.y,
// scale,
// animatedYellowDotPosition?.x,
// animatedYellowDotPosition?.y,
// ]);
// useEffect(() => {
// const canvas = canvasRef.current;
// if (!canvas) return;
// if (!routePath || routePath.length < 4) return;
// let minX = Infinity,
// minY = Infinity,
// maxX = -Infinity,
// maxY = -Infinity;
// for (let i = 0; i < routePath.length; i += 2) {
// const x = routePath[i];
// const y = routePath[i + 1];
// if (x < minX) minX = x;
// if (y < minY) minY = y;
// if (x > maxX) maxX = x;
// if (y > maxY) maxY = y;
// }
// if (
// !isFinite(minX) ||
// !isFinite(minY) ||
// !isFinite(maxX) ||
// !isFinite(maxY)
// )
// return;
// const worldWidth = Math.max(1, maxX - minX);
// const worldHeight = Math.max(1, maxY - minY);
// const margin = 0.1;
// const targetScale = Math.min(
// (canvas.width * (1 - margin)) / worldWidth,
// (canvas.height * (1 - margin)) / worldHeight
// );
// const centerX = (minX + maxX) / 2;
// const centerY = (minY + maxY) / 2;
// const clampedScale = clampScale(targetScale);
// setScaleRef.current(clampedScale);
// setPositionRef.current({
// x: canvas.width / 2 - centerX * clampedScale,
// y: canvas.height / 2 - centerY * clampedScale,
// });
// }, [routePath]);
// useEffect(() => {
// const canvas = canvasRef.current;
// if (!canvas) return;
// let isDragging = false;
// let startMouse = { x: 0, y: 0 };
// let startPos = { x: 0, y: 0 };
// const activePointers = new Map<number, { x: number; y: number }>();
// let isPinching = false;
// let pinchStart: {
// distance: number;
// midpoint: { x: number; y: number };
// scale: number;
// position: { x: number; y: number };
// } | null = null;
// const getDistance = (
// p1: { x: number; y: number },
// p2: { x: number; y: number }
// ) => Math.hypot(p2.x - p1.x, p2.y - p1.y);
// const getMidpoint = (
// p1: { x: number; y: number },
// p2: { x: number; y: number }
// ) => ({
// x: (p1.x + p2.x) / 2,
// y: (p1.y + p2.y) / 2,
// });
// const onPointerDown = (e: PointerEvent) => {
// updateUserActivity();
// if (isAutoMode) {
// setIsAutoMode(false);
// }
// cameraAnimationStore.stopAnimation();
// canvas.setPointerCapture(e.pointerId);
// const rect = canvas.getBoundingClientRect();
// activePointers.set(e.pointerId, {
// x: e.clientX - rect.left,
// y: e.clientY - rect.top,
// });
// if (activePointers.size === 1) {
// isDragging = true;
// startMouse = { x: e.clientX - rect.left, y: e.clientY - rect.top };
// startPos = { x: positionRef.current.x, y: positionRef.current.y };
// } else if (activePointers.size === 2) {
// isDragging = false;
// const [p1, p2] = Array.from(activePointers.values());
// pinchStart = {
// distance: getDistance(p1, p2),
// midpoint: getMidpoint(p1, p2),
// scale: scaleRef.current,
// position: { x: positionRef.current.x, y: positionRef.current.y },
// };
// isPinching = true;
// }
// };
// const onPointerMove = (e: PointerEvent) => {
// if (!activePointers.has(e.pointerId)) return;
// updateUserActivity();
// const rect = canvas.getBoundingClientRect();
// activePointers.set(e.pointerId, {
// x: e.clientX - rect.left,
// y: e.clientY - rect.top,
// });
// if (activePointers.size === 2) {
// isDragging = false;
// const pointersArray = Array.from(activePointers.values());
// if (pointersArray.length === 2) {
// const [p1, p2] = pointersArray;
// if (!isPinching || pinchStart === null) {
// isPinching = true;
// pinchStart = {
// distance: getDistance(p1, p2),
// midpoint: getMidpoint(p1, p2),
// scale: scaleRef.current,
// position: { x: positionRef.current.x, y: positionRef.current.y },
// };
// }
// if (pinchStart) {
// const currentDistance = getDistance(p1, p2);
// const zoomFactor = currentDistance / pinchStart.distance;
// const unclampedScale = pinchStart.scale * zoomFactor;
// const newScale = clampScale(Math.max(0.1, unclampedScale));
// const k = newScale / pinchStart.scale;
// const newPosition = {
// x: pinchStart.position.x * k + pinchStart.midpoint.x * (1 - k),
// y: pinchStart.position.y * k + pinchStart.midpoint.y * (1 - k),
// };
// setPositionRef.current(newPosition);
// setScaleRef.current(newScale);
// }
// }
// } else if (isDragging && activePointers.size === 1) {
// const p = Array.from(activePointers.values())[0];
// if (
// !startMouse ||
// !startPos ||
// typeof startMouse.x !== "number" ||
// typeof startMouse.y !== "number" ||
// typeof startPos.x !== "number" ||
// typeof startPos.y !== "number"
// ) {
// console.warn(
// "WebGLMap: Некорректные значения startMouse или startPos:",
// {
// startMouse,
// startPos,
// p,
// }
// );
// return;
// }
// const dx = p.x - startMouse.x;
// const dy = p.y - startMouse.y;
// setPositionRef.current({ x: startPos.x + dx, y: startPos.y + dy });
// }
// };
// const onPointerUp = (e: PointerEvent) => {
// updateUserActivity();
// canvas.releasePointerCapture(e.pointerId);
// activePointers.delete(e.pointerId);
// if (activePointers.size < 2) {
// isPinching = false;
// pinchStart = null;
// }
// if (activePointers.size === 0) {
// isDragging = false;
// } else if (activePointers.size === 1) {
// const p = Array.from(activePointers.values())[0];
// startPos = { x: positionRef.current.x, y: positionRef.current.y };
// startMouse = { x: p.x, y: p.y };
// isDragging = true;
// }
// };
// const onPointerCancel = (e: PointerEvent) => {
// updateUserActivity();
// canvas.releasePointerCapture(e.pointerId);
// activePointers.delete(e.pointerId);
// isPinching = false;
// pinchStart = null;
// if (activePointers.size === 0) {
// isDragging = false;
// }
// };
// const onWheel = (e: WheelEvent) => {
// e.preventDefault();
// updateUserActivity();
// if (isAutoMode) {
// setIsAutoMode(false);
// }
// cameraAnimationStore.stopAnimation();
// const rect = canvas.getBoundingClientRect();
// const mouseX =
// (e.clientX - rect.left) * (canvas.width / canvas.clientWidth);
// const mouseY =
// (e.clientY - rect.top) * (canvas.height / canvas.clientHeight);
// const delta = e.deltaY > 0 ? 0.9 : 1.1;
// const unclampedScale = scaleRef.current * delta;
// const newScale = clampScale(Math.max(0.1, unclampedScale));
// const k = newScale / scaleRef.current;
// const newPosition = {
// x: positionRef.current.x * k + mouseX * (1 - k),
// y: positionRef.current.y * k + mouseY * (1 - k),
// };
// setScaleRef.current(newScale);
// setPositionRef.current(newPosition);
// };
// canvas.addEventListener("pointerdown", onPointerDown);
// canvas.addEventListener("pointermove", onPointerMove);
// canvas.addEventListener("pointerup", onPointerUp);
// canvas.addEventListener("pointercancel", onPointerCancel);
// canvas.addEventListener("pointerleave", onPointerUp);
// canvas.addEventListener("wheel", onWheel, { passive: false });
// return () => {
// canvas.removeEventListener("pointerdown", onPointerDown);
// canvas.removeEventListener("pointermove", onPointerMove);
// canvas.removeEventListener("pointerup", onPointerUp);
// canvas.removeEventListener("pointercancel", onPointerCancel);
// canvas.removeEventListener("pointerleave", onPointerUp);
// canvas.removeEventListener("wheel", onWheel as any);
// };
// }, [
// updateUserActivity,
// setIsAutoMode,
// cameraAnimationStore,
// isAutoMode,
// clampScale,
// ]);
// return (
// <div
// className="map-layer"
// style={{ width: "100%", height: "100vh", position: "relative" }}
// >
// <canvas
// ref={canvasRef}
// style={{ width: "100%", height: "100%", display: "block", zIndex: 0 }}
// />
// <div
// style={{
// position: "absolute",
// inset: 0,
// pointerEvents: "none",
// zIndex: 9999,
// }}
// >
// {stationLabels.map((l, idx) => (
// <div
// key={idx}
// style={{
// position: "absolute",
// left: l.x,
// top: l.y,
// transform: "translate(0, -50%)",
// color: "#fff",
// fontFamily: "Roboto",
// }}
// >
// <div style={{ fontWeight: 700, fontSize: 16 }}>{l.name}</div>
// {l.sub ? (
// <div
// style={{
// color: "#CBCBCB",
// fontWeight: 400,
// fontSize: 13,
// marginTop: 2,
// }}
// >
// {l.sub}
// </div>
// ) : null}
// </div>
// ))}
// {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 (
// <img
// key={`sight-${s.id}`}
// src={sightIcon}
// alt=""
// width={size}
// height={size}
// style={{
// position: "absolute",
// left: sx - size / 2,
// top: sy - size / 2,
// pointerEvents: "auto",
// cursor: "pointer",
// }}
// onClick={handleSightClick}
// />
// );
// })}
// {(() => {
// 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 (
// <TramIconWebGL
// x={screenX}
// y={screenY}
// optimalAngle={optimalAngle}
// scale={scale}
// />
// );
// })()}
// </div>
// </div>
// );
// });
// export default WebGLMap;