카테고리 없음

스크린 좌표계 입력값으로 표기하기

완드로이드 2024. 12. 27. 13:48
import { useState, useRef, useEffect } from "react";
import Panzoom from '@panzoom/panzoom';
import io from 'socket.io-client';



export default function RealTimeLocation({ FenceSettings, fenceSettingsView }) {
    const [cursorPosition, setCursorPosition] = useState({ x: 0, y: 0 });
    const [panzoom, setPanzoom] = useState(null);
    const [locationData, setLocationData] = useState([]);
    const [fenceData, setFenceData] = useState(null);
    const svgRef = useRef(null);
    // 맵 크기 및 스케일 계산
    const width = Number(FenceSettings?.mapX) || 0;
    const height = Number(FenceSettings?.mapY) || 0;
    const PHX = Number(FenceSettings?.PHX) || 0;
    const PHY = Number(FenceSettings?.PHY) || 0;
    const scale = 1 / 100;
    
    // 패딩 계산
    const padding = Math.max(
        Math.abs(PHX * scale),
        Math.abs(PHY * scale),
        Math.abs((width - PHX) * scale),
        Math.abs((height - PHY) * scale)
    );
    // 1. viewBox 관련 계산
    const viewBoxWidth = (width * scale) + (padding * 2);
    const viewBoxHeight = (height * scale) + (padding * 2);

    // 2. elementScale 계산
    const baseSize = Math.min(viewBoxWidth, viewBoxHeight);
    const elementScale = Math.max(baseSize / 50, 0.1);

    // 3. fontSize 계산 (elementScale 사용)
    const fontSize = Math.min(Math.max(0.6 * elementScale, 0.2), 2);

    // 실제 크기 계산
    const realWidth = width * scale;
    const realHeight = height * scale;



    // 요소 크기 계산
    const strokeWidth = 0.04 * elementScale;

    useEffect(() => {
        setFenceData(fenceSettingsView);
    }, [fenceSettingsView]);

    // 소켓 연결
    useEffect(() => {
        // Socket.IO 연결 생성
        //const socket = io(process.env.REACT_APP_SOCKET_URL);  // 환경변수 사용
        const socket = io('localhost:8401');  // Socket.IO 서버 포트
        // 연결 성공 시
        socket.on('connect', () => {
            console.log('Socket.IO Connected');
        });

        // 8401포트에서 받은 location이라는 이벤트 이름으로 받은 데이터를 받음!!
        socket.on('location', (data) => {
            console.log('Received location data:', data);
            setLocationData(prevMessages => [...prevMessages, data]);
        });

        // 연결 에러 시
        socket.on('connect_error', (error) => {
            console.error('Socket.IO Connection Error:', error);
        });

        // 연결 종료 시
        socket.on('disconnect', () => {
            console.log('Socket.IO Disconnected');
        });

        // 컴포넌트 언마운트 시 정리
        return () => {
            socket.disconnect();
        };
    }, []);
    // PanZoom 초기화
    useEffect(() => {
        if (svgRef.current) {
            const pz = Panzoom(svgRef.current, {
                maxScale: 30,
                minScale: 0.5,
                initialZoom: 1,
                bounds: true,
                boundsPadding: 0.1
            });
            setPanzoom(pz);

            svgRef.current.parentElement.addEventListener('wheel', pz.zoomWithWheel);

            return () => {
                svgRef.current?.parentElement?.removeEventListener('wheel', pz.zoomWithWheel);
                pz.destroy();
            };
        }
    }, []);

    // 커서 위치 변환 함수
    const convertToRealCoordinates = (event) => {
        if (!FenceSettings?.mapX || !FenceSettings?.mapY || !FenceSettings?.PHX || !FenceSettings?.PHY) {
            return { x: 0, y: 0 };
        }

        const svgPoint = svgRef.current.createSVGPoint();
        svgPoint.x = event.clientX;
        svgPoint.y = event.clientY;

        const transformMatrix = svgRef.current.getScreenCTM().inverse();
        const transformedPoint = svgPoint.matrixTransform(transformMatrix);

        const realX = ((transformedPoint.x - padding)) * 100 + Number(FenceSettings.PHX);
        const realY = ((transformedPoint.y - padding)) * 100 + Number(FenceSettings.PHY);

        return { x: realX, y: realY };
    };

    // transformTagHistoryData 함수 수정
    const transformTagHistoryData = (x, y) => {
        const cmX = x * 100;
        const cmY = y * 100;

        // 오프라인/온라인 모드에 따른 원점 좌표 사용
        // const originX = getCheckOffline ?
        //     Number(offlineMapSettings?.PHX) || 0 :
        //     Number(FenceSettings?.PHX) || 0;

        // const originY = getCheckOffline ?
        //     Number(offlineMapSettings?.PHY) || 0 :
        //     Number(FenceSettings?.PHY) || 0;


        const originX = Number(FenceSettings?.PHX) || 0;
        const originY = Number(FenceSettings?.PHY) || 0;

        const transformedX = ((cmX - originX) * scale) + padding;
        const transformedY = ((cmY - originY) * scale) + padding;

        return {
            x: transformedX,
            y: transformedY
        };
    };

    // 마우스 이동 핸들러
    const handleMouseMove = (event) => {
        const coords = convertToRealCoordinates(event);
        setCursorPosition({
            x: Number(coords.x.toFixed(2)),
            y: Number(coords.y.toFixed(2))
        });
    };

    // locationData를 처리하여 각 태그의 최신 위치만 필터링
    const getLatestLocations = (data) => {
        const latestLocations = new Map();

        // 각 태그별로 가장 최신 데이터만 저장
        data.forEach(item => {
            const existing = latestLocations.get(item.tagId);
            if (!existing || item.timestamp > existing.timestamp) {
                latestLocations.set(item.tagId, item);
            }
        });

        // Map을 배열로 변환
        return Array.from(latestLocations.values());
    };
    // posGroup 문자열을 SVG points 형식으로 변환하는 함수

    const formatPoints = (posGroup) => {
        try {
            const points = Array.isArray(posGroup) ? posGroup : JSON.parse(posGroup);

            // 오프라인/온라인 모드에 따른 원점 좌표 사용
            const originX =
                Number(FenceSettings?.PHX) || 0;

            const originY =
                Number(FenceSettings?.PHY) || 0;

            return points.map(point => {
                const cmX = point[0] * 100;
                const cmY = point[1] * 100;

                const x = ((cmX - originX) * scale) + padding;
                const y = ((cmY - originY) * scale) + padding;

                return `${x},${y}`;
            }).join(' ');
        } catch (error) {
            console.error('Failed to process posGroup:', error);
            return '';
        }
    };
    // getFenceCenter 함수 수정
    const getFenceCenter = (posGroup) => {
        try {
            const points = Array.isArray(posGroup) ? posGroup : JSON.parse(posGroup);

            // 오프라인/온라인 모드에 따른 원점 좌표 사용
            const originX =
                Number(FenceSettings?.PHX) || 0;

            const originY =
                Number(FenceSettings?.PHY) || 0;

            const sum = points.reduce((acc, point) => {
                const cmX = point[0] * 100;
                const cmY = point[1] * 100;

                const x = ((cmX - originX) * scale) + padding;
                const y = ((cmY - originY) * scale) + padding;

                return {
                    x: acc.x + x,
                    y: acc.y + y
                };
            }, { x: 0, y: 0 });

            return {
                x: sum.x / points.length,
                y: sum.y / points.length
            };
        } catch (error) {
            console.error('Failed to calculate fence center:', error);
            return { x: 0, y: 0 };
        }
    };

    return (
        <div style={{
            width: '100%',
            height: '100%',
            position: 'relative'
        }}>
            {/* 맵 컨테이너 */}
            <div
                style={{
                    width: '100%',
                    height: '100%',
                    display: 'flex',
                    justifyContent: 'center',
                    alignItems: 'center',
                    position: 'relative',
                    border: '1px solid rgba(0, 0, 0, 0.1)',
                    borderRadius: '12px',
                    overflow: 'hidden'
                }}
                onMouseMove={handleMouseMove}
            >
                <svg
                    ref={svgRef}
                    width="100%"
                    height="100%"
                    viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}
                    preserveAspectRatio="xMidYMid meet"
                    style={{ cursor: 'grab' }}
                >
                    {/* 맵 영역 */}
                    <rect
                        x={padding}
                        y={padding}
                        width={realWidth}
                        height={realHeight}
                        fill="none"
                        stroke="black"
                        strokeWidth={strokeWidth}
                        vectorEffect="non-scaling-stroke"
                    />

                    {/* 맵 이미지 */}
                    <image
                        href={FenceSettings?.mapUrl || ''}
                        x={padding}
                        y={padding}
                        width={realWidth}
                        height={realHeight}
                    />
                    {fenceData && fenceData.map((data, index) => (
                        <g key={index}>
                            <polygon
                                points={formatPoints(data.posGroup)}
                                fill={data.fillColor === "null" ? 'rgba(128, 0, 128, 0.2)' : data.fillColor}
                                stroke={data.frameColor === "null" ? 'purple' : data.frameColor}
                                strokeWidth={strokeWidth}
                                vectorEffect="non-scaling-stroke"
                            />
                            <text
                                x={getFenceCenter(data.posGroup).x}
                                y={getFenceCenter(data.posGroup).y}
                                textAnchor="middle"
                                fill="purple"
                                fontSize={fontSize}
                                fontWeight="bold"
                                vectorEffect="non-scaling-stroke"
                            >
                                {data.fenceName || `Fence ${index + 1}`}
                            </text>
                        </g>
                    ))}

                    {getLatestLocations(locationData).map((data, index) => {
                        const coords = transformTagHistoryData(data.x, data.y);
                        return (
                            <g key={data.tagId || index}>
                                {/* 위치 표시 원 */}
                                <circle
                                    cx={coords.x}
                                    cy={coords.y}
                                    r={strokeWidth * 2}
                                    fill="rgba(46, 204, 113, 0.6)"
                                    stroke="#27ae60"
                                    strokeWidth={strokeWidth * 0.5}
                                    style={{ cursor: 'pointer' }}
                                />

                                {/* 태그 ID 표시 */}
                                <text
                                    x={coords.x}
                                    y={coords.y - (strokeWidth * 3)}
                                    textAnchor="middle"
                                    fill="#2c3e50"
                                    fontSize={`${strokeWidth * 6}px`}
                                    fontWeight="bold"
                                    style={{
                                        textShadow: '0 0 3px white'
                                    }}
                                >
                                    {data.tagId || `Tag ${index + 1}`}
                                </text>

                                {/* 좌표 값 표시 */}
                                <text
                                    x={coords.x}
                                    y={coords.y + (strokeWidth * 4)}
                                    textAnchor="middle"
                                    fill="#34495e"
                                    fontSize={`${strokeWidth * 4}px`}
                                    style={{
                                        textShadow: '0 0 3px white'
                                    }}
                                >
                                    ({data.x}, {data.y})
                                </text>


                            </g>
                        );
                    })}

                </svg>

                {/* 커서 위치 표시 */}
                <div style={{
                    position: 'absolute',
                    bottom: '24px',
                    left: '10%',
                    transform: 'translateX(-50%)',
                    backgroundColor: 'rgba(255, 255, 255, 0.9)',
                    padding: '8px 16px',
                    borderRadius: '24px',
                    boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
                    display: 'flex',
                    alignItems: 'center',
                    gap: '8px',
                    fontSize: '14px',
                    color: '#1a237e',
                    backdropFilter: 'blur(8px)',
                    border: '1px solid rgba(255, 255, 255, 0.2)',
                    zIndex: 100
                }}>
                    <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M12 2L2 22l10-6 10 6L12 2z" />
                    </svg>
                    <span>
                        X: <strong>{cursorPosition.x}</strong>cm,{' '}
                        Y: <strong>{cursorPosition.y}</strong>cm
                    </span>
                </div>
            </div>
        </div>
    );
}