feat: 지도 나침반(OSM/위성) 추가 + 측점 검색·마커 정확도 개선 + POI/영상 수정
- 지도 기반 나침반(노스업, 현재위치, 시야 역삼각형): 호버 확대·휠 줌·클릭 위성전환, OSM/Esri 타일 서버 프록시(/api/tile) - 스테이션 검색: 실제 측점(chain) 기준 이동, 없으면 '측점 없음' 안내 - 역 마커: 직교 투영 측점 일치 시에만 표시, 미도착 종점은 추가 방식 - POI 팝업 겹침/재등장·라벨 정합 수정 - 영상 fps 데이터 기반 자동 산출 - 기술/발표/쉬운설명 문서 추가 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# 드론 흔들림 잡기 + 글자 붙이기 — 초등학생용 쉬운 설명 작성
|
||||
|
||||
## 작업 내용
|
||||
|
||||
기존 기술문서 [2026-06-29_1436_드론자세평활-오버레이렌더링-기술문서화.md](2026-06-29_1436_드론자세평활-오버레이렌더링-기술문서화.md)의 내용이 실제 코드에 적용되어 있는지 검증한 뒤, 그 기술을 **초등학생 눈높이 + SVG 그림**으로 쉽게 풀어쓴 문서를 신규 작성.
|
||||
|
||||
### 1. 기술문서 ↔ 실제 코드 적용 여부 검증
|
||||
- [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx)(1639줄)에서 문서가 인용한 함수/상수 위치를 grep으로 대조
|
||||
- `smoothFrame`(L577), `poseAt`(L670), `smoothStep`(L48), `startLabelPrecompute`(L692), `buildLines`(L606), `coverRef`(L257), `VIDEO_FPS=30000/1001`(L25), `YAW_EDGE=8`(L580), `REJECT_DIST=0.12`, `MAX_REJECT_FRAMES=8`, `SMOOTH_VEL_BETA=0.25` 모두 실제 존재·일치 확인
|
||||
- 결론: 기술문서는 가상이 아니라 실제 구현 코드를 정확히 기술한 것
|
||||
|
||||
### 2. 초등학생용 쉬운설명 문서 작성
|
||||
- 신규 파일: [docs/쉬운설명_드론흔들림잡기와_글자붙이기.md](../쉬운설명_드론흔들림잡기와_글자붙이기.md)
|
||||
- 기존 `쉬운설명_GhiVideo_기술이야기.md`의 SVG figure 스타일을 따름
|
||||
- 8개 SVG 삽화 포함, 비유 중심 서술 (도시락/만화 사이그림/거북이토끼 등)
|
||||
- 다룬 핵심 기술 4+1가지:
|
||||
1. 에지보존 적응형 평활(smoothFrame) → "곧을 땐 많이, 돌 땐 멈춰서"
|
||||
2. 연속프레임 보간(poseAt) → "30장을 60장처럼 사이 채우기"
|
||||
3. 속도적응 EMA + 이상치거부(smoothStep) → "느리면 살살, 빠르면 빨리"
|
||||
4. precompute + RAF 2단 분리 → "미리 정하기 vs 그때그때 그리기 (도시락 비유)"
|
||||
5. 겹침 우선순위 + 상/하행 형제 라벨 + 나침반(보너스)
|
||||
|
||||
### 3. HTML/PDF 변환
|
||||
- `scripts/md2docs.sh`로 변환 완료
|
||||
- 산출물: `쉬운설명_드론흔들림잡기와_글자붙이기.{html,pdf}` (PDF 118KB)
|
||||
|
||||
## 산출물
|
||||
- `docs/쉬운설명_드론흔들림잡기와_글자붙이기.md` (22KB)
|
||||
- `docs/쉬운설명_드론흔들림잡기와_글자붙이기.html` (28KB)
|
||||
- `docs/쉬운설명_드론흔들림잡기와_글자붙이기.pdf` (118KB)
|
||||
|
||||
---
|
||||
|
||||
**소요 시간**: 12분
|
||||
**Context 사용량**: input 95k / output 11k tokens
|
||||
@@ -0,0 +1,39 @@
|
||||
# 지도 POI → 영상 매핑 기술 — 초등학생용 쉬운 설명 작성
|
||||
|
||||
## 작업 내용
|
||||
|
||||
"지도상의 POI 좌표를 영상에 매핑한 기술들"을 실제 코드에서 모두 조사·나열하고, 초등학생 눈높이 + SVG 그림으로 설명하는 문서를 신규 작성.
|
||||
|
||||
### 1. 실제 구현 기술 조사
|
||||
- [geoProjection.ts](../../client/src/utils/geoProjection.ts) 전체 정독 + [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx)의 POI 처리부 분석
|
||||
- 매핑 파이프라인을 구성하는 12개 기술 식별:
|
||||
1. 좌표계 변환 (proj4, EPSG:4326→5186 한국 TM)
|
||||
2. ENU 월드 3D 좌표 (East-North-Up)
|
||||
3. 지오이드 높이 보정 (정표고↔타원체고, 대전≈25.8m)
|
||||
4. 자세 회전 행렬 (yaw/pitch/roll → R_b2w/R_w2c)
|
||||
5. 상대 위치 벡터 (대상−드론, offX/Y/Z)
|
||||
6. 핀홀 카메라 원근투영 (Xc/Zc·f/sensor)
|
||||
7. 화각(초점거리 24mm·센서 36×20.25)
|
||||
8. 카메라 뒤 클리핑 (Zc<CLIP_Z)
|
||||
9. POI 표고 추정 (최근접 중심선 nearestCL / 드론−24m)
|
||||
10. 거리 필터 (수평거리 distH<MAX_RANGE 1000m, 앞/옆 fwd/side)
|
||||
11. 역투영 보정 (worldFromPixel/solveZForPixelY/groundPointFromPixel + focal 역산)
|
||||
12. object-fit:cover 정규좌표→화면 변환
|
||||
|
||||
### 2. 쉬운설명 문서 작성
|
||||
- 신규 파일: [docs/쉬운설명_지도POI를_영상에_붙이는기술.md](../쉬운설명_지도POI를_영상에_붙이는기술.md)
|
||||
- 기존 쉬운설명 문서 스타일 계승, SVG 삽화 14개 + 비유 중심 서술
|
||||
- 기술 한눈에 보기 표 → 각 기술별 그림 상세 → 전체 요약 → 특허포인트 → 용어사전
|
||||
|
||||
### 3. HTML/PDF 변환
|
||||
- `scripts/md2docs.sh`로 변환 완료 (PDF 137KB)
|
||||
|
||||
## 산출물
|
||||
- `docs/쉬운설명_지도POI를_영상에_붙이는기술.md` (33KB)
|
||||
- `docs/쉬운설명_지도POI를_영상에_붙이는기술.html` (41KB)
|
||||
- `docs/쉬운설명_지도POI를_영상에_붙이는기술.pdf` (137KB)
|
||||
|
||||
---
|
||||
|
||||
**소요 시간**: 15분
|
||||
**Context 사용량**: input 130k / output 18k tokens
|
||||
@@ -0,0 +1,20 @@
|
||||
# DefVideo → GhiVideo 업그레이드 문서화
|
||||
|
||||
**소요 시간**: 약 20분
|
||||
**Context 사용량**: input ~70k / output ~9k tokens
|
||||
|
||||
## 작업 내용
|
||||
- `b23042/DefVideo`(구버전)와 `b23042/GhiVideo`(신버전) 소스 트리를 `diff -r`로 전체 비교
|
||||
- 두 저장소는 git 히스토리 분리(공통 커밋 없음) → 워킹 트리 직접 비교로 차이 분석
|
||||
- 변경 규모 정량화(파일별 +/− 라인 수, 신규 파일 줄수)
|
||||
- 주요 변경 파일 3그룹을 병렬 Explore 에이전트로 상세 분석
|
||||
- StationOverlay/Minimap/RoutePanel
|
||||
- geoData/geoProjection/chainage/geo타입/geoStore
|
||||
- StationBar/Timeline/VideoPlayer/settingsStore/elevation
|
||||
|
||||
## 결론
|
||||
- 서버·공유타입은 거의 동일(elevation API 라우트만 신규), 클라이언트는 약 +3,000줄 순증으로 대폭 발전
|
||||
- 핵심 3축: ① V2.0 building/ 폴더 CSV 형식 대응 ② 투영 정확도 개선(지오이드/DEM/역투영) ③ 영상 오버레이·편집 UI 고도화
|
||||
|
||||
## 산출물
|
||||
- `docs/DefVideo→GhiVideo_업그레이드_상세.md` (상세 업그레이드 문서)
|
||||
@@ -0,0 +1,42 @@
|
||||
# KMZ 필수화 — building POI/구조물 CSV 폴백 제거 + 누락 경고
|
||||
|
||||
## 배경 / 결정
|
||||
- 정책: **KMZ는 항상 필수**. 없으면 "데이터 누락"으로 보고 재구축해서 KMZ를 전달받음.
|
||||
- 기존 코드는 KMZ 없으면 **조용히 building CSV로 폴백** → 불완전 데이터가 에러 없이 통과(정책과 상충).
|
||||
- 사용자 선택: **"경고만 표시"** (앱은 계속 동작, CSV POI/구조물 폴백 제거, 측점은 유지).
|
||||
|
||||
## 변경 내용
|
||||
|
||||
### 1. geoData.ts — 죽은 파서 제거 + KMZ 단일 소스화
|
||||
- 삭제: `parsePois`, `parseStructures`, `parseHistoricStations`, `parseAccessDoors` + 전용 헬퍼 `rowProps`, `cleanHeader`
|
||||
- `loadFolderGeoData`:
|
||||
- `Promise.all`에서 위 4개 파서 호출 제거(헛수고 파싱 제거)
|
||||
- `useKmz` 분기 → `kmzMissing` 판정으로 교체: `!kmz || (pois==0 && structures==0)`
|
||||
- KMZ 누락 시 `console.warn`, 정상 시 `console.log`
|
||||
- `pois = kmz?.pois ?? []`, `baseStructures = kmz?.structures ?? []`
|
||||
- `structures`에서 `historicStations` concat 제거 → **역사 중복 위험 제거**(역사는 KMZ KAKAO_RAIL에서만 생성)
|
||||
- 반환값에 `kmzMissing` 추가
|
||||
- 유지: `parseStations`(측점), `parseDroneFrames`, `parseKmz`, `mergeStructures`, `parseRouteMeta`, `parsePoiOverrides`, `buildCenterlineFromStations` 및 공용 헬퍼(`makeFieldIndexer`/`cell`/`readCsv`/`findBuildingFile`/`mmssToSeconds` 등)
|
||||
|
||||
### 2. 타입/스토어 (kmzMissing 전파)
|
||||
- [types/geo.ts](../../client/src/types/geo.ts): `FolderGeoData.kmzMissing: boolean` 추가
|
||||
- [store/geoStore.ts](../../client/src/store/geoStore.ts): `GeoStore.kmzMissing` + `EMPTY` 기본값 + `loadFromFolder`에서 set
|
||||
|
||||
### 3. UI 경고
|
||||
- [VideoPlayer.tsx](../../client/src/components/player/VideoPlayer.tsx) `handleSelectFolder`: 로드 후 `kmzMissing && (stations||frames 존재)` 면 `alert`로 "KMZ 누락 → 재구축 필요(측점·드론은 정상)" 안내
|
||||
|
||||
### 4. 문서 갱신
|
||||
- [docs/구현상세_GhiVideo_기술-소스코드매칭.md](../구현상세_GhiVideo_기술-소스코드매칭.md): 데이터 흐름도 + 소스 우선순위 콜아웃 + 파서 표를 KMZ 필수 정책으로 갱신(폴백 제거 명시)
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` (node v20.20.2): **통과** (시스템 기본 node v12라 nvm node20 사용)
|
||||
- `npm run build` (tsc && vite build): **성공** (219 modules, 빌드 1.49s)
|
||||
|
||||
## 비고
|
||||
- 측점.csv(루트)는 KMZ에 없어 그대로 필수 — 빠지면 스테이션바/체이니지/중심선 동작 불가.
|
||||
- 빌드 산출물 반영됨 → 55000 PM2 서빙 시 재시작/재배포 필요할 수 있음.
|
||||
|
||||
---
|
||||
|
||||
**소요 시간**: 25분
|
||||
**Context 사용량**: input 175k / output 22k tokens
|
||||
@@ -0,0 +1,22 @@
|
||||
# 구현 상세 — 기술·소스코드 매칭 문서화
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~120k / output ~16k tokens
|
||||
|
||||
## 작업 내용
|
||||
- 앞서 설명한 기술들을 실제 소스코드(파일·라인·핵심 코드)와 1:1 매칭한 구현 상세 문서 작성
|
||||
- 영역별 3개 Explore 에이전트로 정확한 함수 위치/라인번호/코드 스니펫 추출
|
||||
- 오버레이/미니맵/RoutePanel
|
||||
- geoData/geoProjection/chainage/타입/geoStore
|
||||
- StationBar/Timeline/VideoPlayer/settingsStore/elevation
|
||||
- 추출 결과를 14개 섹션 + 빠른 색인표로 종합
|
||||
|
||||
## 산출물
|
||||
- `docs/구현상세_GhiVideo_기술-소스코드매칭.md`
|
||||
- 전체 데이터 흐름도 → 파싱 → 투영/역투영 → 체이니지 → 오버레이 렌더 → 라벨평활 → 겹침/팝업 → 위치보정(드래그/DEM/세로화각) → 미니맵 → 측점바/이동거리축 → smoothTimeRef → 상태관리 → 서버 고도 API
|
||||
- 기능↔소스 빠른 색인표(파일:라인) 포함
|
||||
- 각 기능마다 핵심 코드 스니펫 + 동작 원리 설명
|
||||
|
||||
## 참고
|
||||
- 라인번호는 2026-06-30 기준이며 코드 수정 시 달라질 수 있음
|
||||
- 기존 문서 `DefVideo→GhiVideo_업그레이드_상세.md`, `쉬운설명_GhiVideo_기술이야기.md`와 상호 참조
|
||||
@@ -0,0 +1,20 @@
|
||||
# 발표 문서 작성 — 좌표 정합과 측점 기반 구현
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~150k / output ~20k tokens
|
||||
|
||||
## 작업 내용
|
||||
- 개발 내용을 발표용 슬라이드형 마크다운으로 작성
|
||||
- 주제: 드론 GPS·영상·POI 좌표를 영상 위 정확한 위치에 배치하는 기술 + 프레임→측점 기반 전환
|
||||
- 기존 소스 분석(geoProjection/chainage/StationBar/StationOverlay/VideoPlayer) 결과를 재활용해 그림·도식 중심으로 구성
|
||||
|
||||
## 산출물
|
||||
- `docs/발표_GhiVideo_좌표정합과측점기반구현.md`
|
||||
- 9개 슬라이드 섹션 + 부록 소스 색인
|
||||
- 핵심 ①공간(4단계 투영+지오이드) ②시간(smoothTimeRef) ③안정(One Euro) ④프레임→측점 전환
|
||||
- ASCII 도식: 투영 파이프라인, 핀홀 카메라, ENU, 측점 투영, 이동거리축(호버), 측점바
|
||||
- 프레임 vs 측점 비교표, 핵심 소스 색인표
|
||||
|
||||
## 참고
|
||||
- `---` 슬라이드 구분으로 Marp/reveal.js 변환 가능
|
||||
- 상세 구현 문서(`구현상세_...md`)와 상호 참조
|
||||
@@ -0,0 +1,37 @@
|
||||
# 재생 중 데이터 폴더 교체 시 "media could not be loaded" 수정
|
||||
|
||||
## 증상
|
||||
재생 중에 다른 데이터 폴더를 드래그&드롭하면 지오 데이터(측점/POI/스테이션바)는 정상 로드되는데
|
||||
**영상만** 상단에 "The media could not be loaded …" 에러가 뜨고 검은 화면.
|
||||
|
||||
## 원인 — objectURL 조기 해제 race
|
||||
[useVideoPlayer.loadLocalFile](../../client/src/hooks/useVideoPlayer.ts#L72)이 blob URL 해제를 `player.one('emptied', …)` 이벤트에 의존.
|
||||
소스 교체 시:
|
||||
1. 첫 로드 → `.one('emptied', revoke url1)` 등록
|
||||
2. 드롭 → `createObjectURL`로 **url2** 생성 → `player.src(url2)` (이때 `'emptied'`가 **비동기 예약**) → 직후 `.one('emptied', revoke url2)` 등록
|
||||
3. 예약된 `'emptied'` 발화 → url1·url2 핸들러가 **동시 실행** → 방금 만든 **url2까지 revoke** → 소스 무효 → 로드 실패
|
||||
|
||||
첫 로드는 이전 'emptied'가 없어 정상, **교체 시에만** 재현.
|
||||
|
||||
## 수정 — ref 기반 결정적 해제
|
||||
- `objectUrlRef`로 현재 blob URL 추적, `'emptied'` 핸들러 제거
|
||||
- `loadLocalFile`: 새 `src` 설정 후 **이전 URL만** revoke (새 URL은 보존)
|
||||
- `loadServerStream`: 로컬→서버 전환 시 이전 blob URL revoke
|
||||
- 언마운트(dispose) cleanup: 남은 blob URL revoke
|
||||
- 부가: 교체 직전 재생 중이었으면(=드롭 교체) 새 영상도 `player.play()`로 이어서 재생
|
||||
(drop은 사용자 제스처라 자동재생 정책 통과. 최초 로드는 `prevUrl` 없음 → 기존처럼 자동재생 안 함)
|
||||
|
||||
## 파일
|
||||
- [client/src/hooks/useVideoPlayer.ts](../../client/src/hooks/useVideoPlayer.ts)
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit`: 통과 / `npm run build`: 성공 (node v20.20.2)
|
||||
- 재현/회귀: 재생 중 폴더 드롭 → 새 영상 로드+재생, 콘솔 에러 없음(코드 경로상). 실브라우저 확인은 하드 새로고침 후 폴더 재드롭 필요.
|
||||
|
||||
## 반영 방법
|
||||
- client만 변경 → **하드 새로고침(Ctrl+Shift+R)** 후 폴더 드롭으로 확인. PM2 재시작 불필요.
|
||||
|
||||
---
|
||||
|
||||
**소요 시간**: 12분
|
||||
**Context 사용량**: input 198k / output 26k tokens
|
||||
@@ -0,0 +1,24 @@
|
||||
# 발표자료 SVG 도식화 전면 개편
|
||||
|
||||
## 작업 내용
|
||||
기존 [발표_GhiVideo_좌표정합과측점기반구현.md](../발표_GhiVideo_좌표정합과측점기반구현.md)가 ASCII 아트 도식이라 가독성이 낮아, **인라인 SVG 그림 16개로 전면 교체**하고 색상 체계·표로 도식화해 이해도를 높임.
|
||||
|
||||
## 변경점
|
||||
- ASCII 다이어그램 → **SVG figure 16개** (기존 쉬운설명 문서들의 `figure.fig` 스타일 계승)
|
||||
- 색상 의미 통일: 🟦공간(투영, 파랑) · 🟩시간(초록) · 🟧안정(주황) · 🟦측점(청록) · 드론(보라)
|
||||
- 주요 그림: 목표 화면 목업, 입력3종 결합, 전체 파이프라인(레인), 4단계 투영, ENU·회전·핀홀·지오이드, 시간보간(sparse↔dense), One Euro 3케이스, 시간축 한계, GPS→측점 투영, 이동거리축(호버), 측점바 목업, 역투영 보정, 최종 정리도
|
||||
- 내용/코드근거/소스색인 표는 유지(정확도 보존), 슬라이드 `---` 구분 유지(Marp 호환)
|
||||
- `<style>` 블록 추가(figure 스타일 + lead 콜아웃)
|
||||
|
||||
## 산출물
|
||||
- `docs/발표_GhiVideo_좌표정합과측점기반구현.md` (41KB)
|
||||
- `.html` (59KB) / `.pdf` (181KB) — md2docs.sh 변환
|
||||
|
||||
## 비고
|
||||
- weasyprint 경고 `user-select: none`(31행)은 무시됨(렌더 영향 없음).
|
||||
- 원본 ASCII 버전은 덮어씀(내용은 보존+개선).
|
||||
|
||||
---
|
||||
|
||||
**소요 시간**: 18분
|
||||
**Context 사용량**: input 215k / output 32k tokens
|
||||
@@ -0,0 +1,103 @@
|
||||
# POI 라벨 사라진 뒤 팝업 재등장 수정
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~80k / output ~3k tokens
|
||||
|
||||
## 문제
|
||||
POI 라벨이 화면 밖으로 사라졌는데, 방금 전 위치에 POI 팝업이 다시 뜨는 문제.
|
||||
|
||||
## 원인
|
||||
[client/src/components/overlay/StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx) 의
|
||||
마커 그리기 루프(1114행)가 라벨이 화면(비디오 크롭) 밖으로 나가도 `cc.Zc >= CLIP_Z` 이기만 하면
|
||||
매 프레임 `visStructRef` 에 계속 등록.
|
||||
- 팝업 제거 로직(RAF): 라벨이 off-container 상태 4프레임이면 팝업 제거.
|
||||
- 자동 sync 인터벌(150ms): `vis.has(title)` 이 아직 true → 팝업을 즉시 다시 추가.
|
||||
→ 둘이 싸워 팝업이 마지막 위치에 되살아남.
|
||||
|
||||
## 수정
|
||||
`visStructRef.set` 을 팝업 off 판정과 **동일한 화면 범위**(`px/py ∈ [-16, W+16]/[-16, H+16]`)로 제한.
|
||||
`vx(nx)=offX+nx*dispW` 는 팝업 off-check 의 `sx=offX+disp.x*dispW` 와 같은 좌표계라 일치.
|
||||
→ 라벨이 화면 밖이면 vis 에서도 빠져 sync 가 되살리지 않음(제거 로직과 일관).
|
||||
|
||||
## 추가 수정 (잔여 1프레임 번쩍임)
|
||||
`visStructRef` 가드만으론 sync 인터벌이 팝업 DOM 을 **새로 mount** 하는 순간의 번쩍임이 남음:
|
||||
새 팝업 요소는 React 초기 스타일(`top: sy+27`)로 **보이는 상태**로 붙고, 다음 RAF 가
|
||||
'화면 밖→숨김' 판정을 내리기 전 한 프레임 stale 위치에 번쩍임.
|
||||
|
||||
→ 팝업 ref 콜백에서 `popupPosRef`(RAF 위치 확정 시 기록)에 아직 없는 '신규' 팝업은
|
||||
`visibility:hidden` 으로 mount. RAF 가 온스크린 확인 후에만 visible 로 전환(off 면 계속 숨김→제거).
|
||||
visibility 는 RAF 명령형으로만 관리(style prop 미포함)라 재렌더에도 유지됨.
|
||||
|
||||
## 추가 수정 2 (hidden-mount 가드 무력화 — 진짜 원인)
|
||||
hidden-mount 가드는 `!popupPosRef.has(id)` 일 때만 숨긴다. 그런데 팝업 제거 경로가 여럿인데
|
||||
보조 맵(popupPosRef/popupMissRef) 정리는 **RAF off-4프레임 경로만** 했음.
|
||||
→ sync `toRemoveIds` / 수동(✕·ESC·빈곳클릭) 제거 시 `popupPosRef` 잔존 → 같은 title 재추가 시
|
||||
`popupPosRef.has=true` → 숨김 안 됨 → React 초기 스타일(`top: sy+27`)로 **보이는 채 mount** →
|
||||
라벨 없이 팝업만 stale 위치에 나타남(+ RAF 가 stale 위치에서 EMA 로 미끄러짐).
|
||||
|
||||
수정:
|
||||
1. `infoPopups` 변경 effect 에서 사라진 id 의 보조 맵 일괄 정리(단일 출처).
|
||||
2. sync `toRemoveIds` 처리 시 즉시 `popupPosRef`/`popupMissRef` 정리(재추가 전에 확실히 비움).
|
||||
|
||||
## 추가 수정 3 (definitive — 라벨 가시성과 완전 결합)
|
||||
증상이 계속 동일해 근본 재분석: 팝업 off 판정이 라벨 **십자마커 중심** ±16px 슬랙 + 화면 안 **clamp**
|
||||
조합이라, 마커가 경계 근처(예: 오른쪽 끝 밖 ~16px)면 라벨 텍스트는 화면 밖(안 보임)인데
|
||||
팝업만 clamp 되어 화면 안에 뜸 → "라벨 없이 팝업만".
|
||||
|
||||
수정: 이번 프레임에 라벨이 **실제로 화면 안에 그려졌는지**(히트박스∩뷰포트)를 `onScreenLabels`
|
||||
집합으로 만들고, 팝업 표시/visStruct 등록을 이 집합에만 의존하도록 통일.
|
||||
- `off = !disp || !onScreenLabels.has(id)` (좌표 슬랙·clamp 기반 판정 제거)
|
||||
- visStruct 등록도 `poiVisible`(히트박스 온스크린) 기준
|
||||
|
||||
## 추가 수정 4 (사라졌다 잠깐 재등장 → 완전 제거)
|
||||
증상: 라벨 사라짐 → 팝업 사라짐 → 잠깐 재등장 → 사라짐. 원인은 sync(150ms 추가) ↔ RAF(제거) 충돌.
|
||||
- 경계에서 라벨이 1~2프레임 깜빡이면 visStruct 재등록 → sync 가 팝업 재생성 → RAF 가 다시 제거.
|
||||
|
||||
수정:
|
||||
1. 추가 히스테리시스: 라벨이 **연속 ADD_STREAK(6프레임 ~100ms)** 이상 화면 안일 때만 visStruct 등록
|
||||
(poiOnScreenStreakRef). 짧은 깜빡임으론 팝업 생성 안 됨.
|
||||
2. 제거 단일화: sync 의 팝업 제거 로직 삭제 → **RAF(onScreenLabels + 4프레임 유예)가 유일한 제거 권한**.
|
||||
add/remove 충돌 제거 → 라벨 사라지면 팝업도 사라진 뒤 재생성 안 됨.
|
||||
|
||||
## 추가 수정 5 (진짜 리그레션 원인 — flip 왕복)
|
||||
사용자 지적: 이 증상은 '하단 겹침' 수정(추가수정 없음, 첫 위치 수정) 이후 생긴 리그레션.
|
||||
→ 가시성 문제가 아니라 flip(아래↔위 전환) 로직에 **히스테리시스가 없어서** 발생.
|
||||
라벨이 하단 flip 임계값 부근이면 드론 흔들림/평활로 `sy` 가 미세하게 오르내릴 때마다
|
||||
`ty+ph > H-2` 가 참↔거짓 왕복 → 팝업이 아래↔위로 **순간이동(bigJump 스냅)** → "사라졌다 나타났다".
|
||||
|
||||
수정: `popupFlipRef`(위/아래 상태) + 히스테리시스(HYST=48px). '위'로 간 뒤엔 아래에 48px 이상
|
||||
여유가 생겨야만 '아래'로 복귀 → 경계 왕복 제거. flip 상태도 제거 경로에서 정리.
|
||||
|
||||
## 추가 수정 6 (하단 이탈 즉시 제거 — 유예 삭제)
|
||||
요구: 라벨이 하단으로 벗어나면 팝업 즉시 사라지고 재등장 금지.
|
||||
잔여 원인: 제거에 4프레임 '유예'가 있어, 그 사이 흔들림으로 라벨이 경계로 잠깐 되들어오면
|
||||
RAF 가 아직 안 지워진 팝업을 다시 visible 로 바꿈 → "사라졌다 잠깐 나타났다".
|
||||
|
||||
수정: off(라벨 화면 밖) 판정 시 유예 없이 **즉시 toRemove**(+숨김). 재등장은 sync 의
|
||||
ADD_STREAK(6프레임 연속 안정) 게이트가 막음 → 짧은 복귀로는 재생성 안 됨.
|
||||
mid-screen 은 라벨 히트박스가 가장자리에서 멀어 off 가 안 나므로 영향 없음.
|
||||
|
||||
## 추가 수정 7 (하단 기준 = 영상 영역(스테이션바 위), 모니터 아님)
|
||||
요구: 팝업이 스테이션바에 걸치지 않게, 라벨이 바 밑으로 내려가면 팝업 제거. 기준은 영상 재생 영역.
|
||||
- 스테이션바는 영상 위 z-20 오버레이(absolute bottom-0), 팝업은 z-40 → 바 위에 걸쳐 보였음.
|
||||
- 유효 하단 `HB = H − barHeight` 도입(barHeightRef, ResizeObserver 실측 ~130px).
|
||||
- `labelOnScreen` 하단 기준 `H-2` → `HB-2`: 라벨 히트박스가 바 밑이면 off → 즉시 제거.
|
||||
- 팝업 flip/clamp 하단 기준도 `H` → `HB`: 팝업이 바 위로만 배치(걸침 방지).
|
||||
|
||||
## 추가 수정 8 (flip 제거 + 바 뒤로 슬라이드)
|
||||
요구: 팝업이 라벨 위로 flip하면 안 됨. 항상 라벨 아래에 있다가 라벨이 바 밑으로 사라질 때 같이 사라짐.
|
||||
- flip/clamp 둘 다 문제였음(flip=위로 튐, clamp=바에 걸쳐 멈춤).
|
||||
|
||||
수정:
|
||||
1. flip·하단 clamp 제거 → 팝업 `ty = sy + LABEL_HALF + GAP` (항상 라벨 아래, 자유 하강).
|
||||
2. 팝업들을 클립 컨테이너로 감쌈: `absolute left-0 right-0 top-0 overflow-hidden` + `bottom: barHeight`
|
||||
(높이 = 영상영역 HB). 팝업이 라벨 따라 내려가면 바 영역에서 클립돼 '바 뒤로 슬라이드'되어 사라짐.
|
||||
3. 제거는 label off(HB 기준) 그대로 → 라벨이 바 밑이면 팝업 제거.
|
||||
|
||||
## 배포 확인
|
||||
- :55000 서빙 index.html → `assets/index-yfJyCKp1.js` (이번 빌드 해시와 일치)
|
||||
- ecosystem: ghiVideo, cwd=프로젝트루트, clientDist=client/dist (정적 즉시 반영)
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과
|
||||
- `npm run build` 성공 → :55000 프로덕션 반영 (브라우저 강력 새로고침 필요)
|
||||
@@ -0,0 +1,23 @@
|
||||
# POI 팝업 ↔ 라벨 겹침 수정
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~60k / output ~4k tokens
|
||||
|
||||
## 문제
|
||||
POI 팝업이 화면 아래쪽에 다가올수록 라벨(십자마커+글자)과 팝업창이 겹치는 문제.
|
||||
|
||||
## 원인
|
||||
[client/src/components/overlay/StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx) 의 RAF 팝업 위치 계산에서
|
||||
여백 `GAP=16` 을 라벨 **중심**(`sy`, 십자마커 위치) 기준으로 잡음.
|
||||
라벨 글자·아이콘이 `sy` 위아래로 ~13px 차지 → 실제 여백은 3px뿐. 하단 위로 플립해도 동일 3px라 계속 겹침.
|
||||
|
||||
## 수정
|
||||
- `LABEL_HALF=15`(라벨 반높이) 도입 → 라벨 **바깥 가장자리** 기준으로 띄움.
|
||||
- 아래: `ty = sy + LABEL_HALF + GAP`
|
||||
- 위 플립: `aboveTy = sy - LABEL_HALF - GAP - ph`
|
||||
- `GAP` 16→12 (실제 여백 3px → 12px)
|
||||
- 초기 렌더 top `sy+16 → sy+27` 로 맞춰 1프레임 튐 방지.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과
|
||||
- `npm run build` 성공 → :55000 프로덕션 반영
|
||||
@@ -0,0 +1,27 @@
|
||||
# 역 마커 — 실제 측점 일치 기준으로 정정 (162080 누락 수정)
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~230k / output ~4k tokens
|
||||
|
||||
## 문제
|
||||
직전 수정(kmExists 게이트)이 너무 세서, **진짜 162080 위치의 대전조차장 마커까지 사라짐**.
|
||||
|
||||
## 원인
|
||||
kmExists 게이트가 `|p.km − stationKmVal|` 비교인데:
|
||||
- 탐지는 `s.station`(등록 측점 162080) → p.km = 162080
|
||||
- stationKmVal = `projectChainage(역 좌표)` (좌표 투영) — 등록값과 어긋날 수 있음
|
||||
→ 둘이 다르면 게이트가 실제 162080 통과까지 제거.
|
||||
|
||||
## 수정 ([StationBar.tsx](../../client/src/stationbar/StationBar.tsx))
|
||||
게이트 방식 폐기, 탐지 소스로 구분:
|
||||
1. 역사(역)는 **좌표근접(pxPassesTo) 폴백을 배제** → `pxPassesAtMileage`(실제 측점 일치)로만 탐지.
|
||||
- `if (!passes.length && cat !== '역사' && ...) pxPassesTo(...)` (교량/터널만 허용)
|
||||
2. `if (!kmExists) continue` 게이트 제거(주 경로 + 폴백 경로). kmExists 는 라벨/검색 참고용으로만 유지.
|
||||
|
||||
## 효과
|
||||
- ① 좌표만 가까운(측점 다른) 조차장 재진입(드론 162210인데 162080)은 안 찍힘.
|
||||
- ② 진짜 162080 통과에는 정상 표시.
|
||||
- 검색은 이미 chain 기준(별도 수정)이라 마커 변경과 무관하게 정확.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, `npm run build` 성공 → 해시 `index-Cdsf6M1x.js`(HUD `· 검색v3`) :55000 서빙.
|
||||
@@ -0,0 +1,26 @@
|
||||
# 역(스테이션) 마커 — 직교 투영 측점 일치 시에만 표시
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~180k / output ~4k tokens
|
||||
|
||||
## 문제
|
||||
스테이션바에서 드론이 대전조차장(역 측점 162k080) '좌표에만' 가까우면, 실제 드론 측점이
|
||||
162k290(210m 밖)이어도 대전조차장역 마커가 찍혔다. → 직교 위치가 안 맞는데 역 표시됨.
|
||||
|
||||
## 원인
|
||||
[StationBar.tsx](../../client/src/stationbar/StationBar.tsx) 구조물 마크 생성:
|
||||
- 역사 '보강 탐지'가 `pxPassesTo`(좌표 근접) 통과를 합집합으로 추가 → 측점 불일치 통과까지 마커.
|
||||
- `kmExists`(=드론 측점 p.km 와 역 측점 stationKmVal 차이 ≤ tol)가 false여도 마커(동그라미)는 유지했음.
|
||||
|
||||
## 수정
|
||||
1. 역사 좌표근접 '보강 탐지' 블록 제거 (측점 불일치 재진입을 억지로 추가하던 부분).
|
||||
2. 마커 push 시 `if (!kmExists) continue;` 게이트 추가(주 경로 + 폴백 경로 both).
|
||||
→ 역은 **드론의 직교 투영 측점이 역 측점과 stationTolerance(기본 20m) 이내일 때만** 표시.
|
||||
→ 교량/터널은 stationKmVal=null → kmExists 항상 true → 영향 없음.
|
||||
|
||||
## 효과
|
||||
- 대전조차장에서 드론이 162k290일 때 162k080 역 마커가 더 이상 안 뜸.
|
||||
- 측점 일치 통과(pxPassesAtMileage)는 그대로 유지 → 정상 통과·재진입은 계속 표시.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, `npm run build` 성공 → 새 해시 `index-CJflcoyT.js` :55000 서빙 확인.
|
||||
@@ -0,0 +1,34 @@
|
||||
# 영상 fps 데이터 기반 자동 산출 (고정 29.97 → 가변)
|
||||
|
||||
**소요 시간**: 약 20분
|
||||
**Context 사용량**: input ~150k / output ~6k tokens
|
||||
|
||||
## 배경
|
||||
프레임번호↔시간 변환이 `VIDEO_FPS = 30000/1001`(29.97)로 하드코딩 → 30fps 영상 전용.
|
||||
24/60fps 등 다른 영상은 이름표가 어긋남. 어떤 fps 영상이 로드될지 미리 알 수 없음.
|
||||
|
||||
## 문제 데이터
|
||||
드론 CSV(`frame_cnt,latitude,...`)엔 **시간이 없고 frame_cnt(프레임 번호)만** 있음.
|
||||
→ fps 를 CSV 단독으로 못 구함. **영상 길이(duration)** 와 결합 필요.
|
||||
|
||||
## 해결
|
||||
`effectiveFps = maxFrameCnt / duration` → 표준 fps(23.976/24/25/29.97/30/50/59.94/60) 중
|
||||
±10% 이내면 스냅, 아니면 원시값. 데이터/길이 미확보 시 29.97 폴백.
|
||||
|
||||
### 변경 파일 (2개, 소규모)
|
||||
- [VideoPlayer.tsx](../../client/src/components/player/VideoPlayer.tsx)
|
||||
- `effectiveFps` useMemo 신설(storeFrames+duration). `VIDEO_FPS` 하드코딩 4곳 제거
|
||||
(frame 계산 / HUD 표시 / 프레임입력 이동 / StationOverlay prop).
|
||||
- useFrameStep 의 fps(VFC 감지, 31 오감지)는 미사용 → destructure 제거.
|
||||
- [StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx)
|
||||
- `fpsRef`(prop fps 기반) 추가. 프레임↔시간 변환 VIDEO_FPS 사용처(최근접 프레임 탐색, estFrame)를
|
||||
fpsRef.current 로 교체. VIDEO_FPS 상수는 폴백 기본값으로만 유지.
|
||||
|
||||
## 주의/전제
|
||||
- 드론 CSV 가 영상 전체 구간을 덮는다는 전제(마지막 frame_cnt ≈ 영상 끝). 부분만 덮으면 fps 과소 →
|
||||
스냅 실패 시 원시값 사용. 필요 시 메타데이터/FFprobe 로 보강 가능.
|
||||
- 표준 목록 밖 fps 는 스냅 없이 원시값 사용.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과
|
||||
- `npm run build` 성공 → 새 해시 `index-BbUe4Gr4.js` :55000 서빙 확인
|
||||
@@ -0,0 +1,33 @@
|
||||
# 종점(대전조차장) 마커 끝-이동 제거 → 실제 직교 위치에 그대로 표시
|
||||
|
||||
**소요 시간**: 약 15분
|
||||
**Context 사용량**: input ~260k / output ~5k tokens
|
||||
|
||||
## 사용자 원칙
|
||||
- POI(영상 오버레이) 정보: 현재 위치 반경 안만 표시.
|
||||
- **스테이션바 항목: 반경 없이, 측점선과 직교로 만나는 시설물이면 무조건 실제 위치에 표시.**
|
||||
|
||||
## 문제
|
||||
검색이 찾은 162080 자리에 대전조차장 마커가 없음.
|
||||
원인: `placeUnreachedTerminal` 이 endGapPx>0 일 때 **최우측 역사 마커를 트랙 끝(TRACK_END)** 으로
|
||||
이동('미도착' 표시) → 드론이 종점 야드에서 162080을 여러 번 지나는데 마지막 통과 마커가 끝으로
|
||||
옮겨져 검색 위치(실제 162080)와 어긋남.
|
||||
|
||||
## 수정 ([StationBar.tsx](../../client/src/stationbar/StationBar.tsx))
|
||||
`placeUnreachedTerminal` 를 **no-op**(marks 그대로 반환)으로 변경 → 마커 끝-이동 제거.
|
||||
- 모든 162080 통과 마커가 실제 직교 위치에 유지(사용자 원칙과 일치).
|
||||
- 종점 이름(endStationName=대전조차장역)은 오른쪽 끝 고정 표시 유지.
|
||||
- 회색 미도착 gap(endGapPx) 자체는 트랙 색으로만 유지(마커는 안 옮김).
|
||||
|
||||
## 관련 상태
|
||||
- 역사: pxPassesAtMileage(측점 일치)로만 탐지(좌표근접·게이트 없음) → 직교 일치 통과에만, 반경 없음.
|
||||
- 교량/터널: pxPassesAtMileage 우선 + (측점/좌표 투영 없을 때만) pxPassesTo 폴백. 필요 시 폴백 제거 검토.
|
||||
|
||||
## 보정: 미도착 종점 마커는 '추가'로 복원
|
||||
no-op 로 두니 드론이 못 간 '종점 162080'의 대전조차장 마커까지 사라짐(사용자 지적).
|
||||
→ placeUnreachedTerminal 을 '이동'이 아니라 '추가'로 변경:
|
||||
- endGapPx>0 이면 최우측 역사 마커를 **복사**해 TRACK_END 에 unreached 로 push(기존 마커 유지).
|
||||
- 결과: 지나간 162080 통과는 실제 위치에 그대로 + 못 간 종점 162080은 끝에 미도착 표시.
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, `npm run build` 성공 → 해시 `index-D66Yy1Dn.js`(HUD `· 검색v5`) :55000 서빙.
|
||||
@@ -0,0 +1,57 @@
|
||||
# 지도 기반 나침반(OSM, 노스업) 추가 + 타입 전환
|
||||
|
||||
**소요 시간**: 약 30분
|
||||
**Context 사용량**: input ~340k / output ~9k tokens
|
||||
|
||||
## 요구
|
||||
우측 상단 나침반을 2종 중 선택. 새 '지도 기반 나침반' = 나침반 안에 지도 + 실시간 위치.
|
||||
사용자 선택: **OSM 온라인 타일 · 노스업(북쪽 고정) · 화면표시 옵션에 전환 버튼.**
|
||||
|
||||
## 구현
|
||||
### 클라이언트
|
||||
- [store/settingsStore.ts](../../client/src/store/settingsStore.ts): `compassType: 'analog'|'map'` + setter (persist).
|
||||
- [components/overlay/MapCompass.tsx](../../client/src/components/overlay/MapCompass.tsx) (신규):
|
||||
- OSM 타일을 Web Mercator 로 배치, 원형 클립. 노스업(지도 고정) + 현재위치 중심 점 + 진행방향 화살표(yaw 회전) + 상단 N.
|
||||
- `poseRef`(부모 RAF 갱신 {lat,lon,yaw}) 를 내부 RAF 로 읽어: 화살표 즉시 회전 + 지도 팬(transform) + 임계 이동 시 타일 재배치(setState).
|
||||
- 타일 URL = **서버 프록시** `/api/tile/{z}/{x}/{y}.png` (외부 직접 로드는 CSP/COEP 로 차단됨).
|
||||
- [components/overlay/StationOverlay.tsx](../../client/src/components/overlay/StationOverlay.tsx):
|
||||
- `mapPoseRef` 추가, RAF 나침반 블록에서 headingDeg 항상 계산해 기록(아날로그 회전은 렌더 중일 때만).
|
||||
- 렌더: compassType 에 따라 `<MapCompass>` / `<Minimap>` 선택.
|
||||
- 화면표시 옵션 '표시 토글'에 나침반 [눈금|지도] 버튼 추가.
|
||||
|
||||
### 서버 (프록시 필수)
|
||||
- CSP `img-src 'self'` + COEP `require-corp` 라 외부 타일 `<img>` 직접 로드 불가(elevation.ts 와 동일 제약).
|
||||
- [server/src/routes/tile.ts](../../server/src/routes/tile.ts) (신규): `GET /api/tile/:z/:x/:y` →
|
||||
OSM 타일 fetch(식별 User-Agent) 후 같은 출처로 중계. `Cross-Origin-Resource-Policy: same-origin`, 7일 캐시, 좌표 검증(범위 밖 400).
|
||||
- [server/src/app.ts](../../server/src/app.ts): `app.use('/api/tile', tileRouter)`.
|
||||
|
||||
## 배포
|
||||
- server/client 빌드 + **PM2 restart ghiVideo**(서버 라우트 반영 필수) 완료.
|
||||
- 검증: `/api/tile/16/x/y.png` → 200 image/png, CORP same-origin, 7일 캐시. 범위 밖 → 400. 클라 `index-BS7bYMJ3.js`.
|
||||
|
||||
## 추가: 줌 + 호버 확대 + 휠 줌
|
||||
- settingsStore `compassZoom`(12~19, 기본16) + setter(persist). MapCompass 에서 project(z 동적) 사용.
|
||||
- 나침반 하단 −/z/+ 버튼(pointer-events auto). 줌 변경 시 현재위치 기준 타일 재배치.
|
||||
- **마우스 오버 → 2배 확대**(transform scale, 우상단 고정 origin, 140ms), **벗어나면 원복**.
|
||||
- **휠 줌**: 컨테이너 native wheel 리스너(passive:false) → deltaY 로 compassZoom ±1(위=확대).
|
||||
- 호버/휠 위해 컨테이너 pointer-events auto(해당 영역만 이벤트 캡처).
|
||||
|
||||
## 개선(사용자 피드백)
|
||||
- 확대: CSS scale(흐릿) → **위젯 지름 자체 확대**(BASE_D 150 → HOVER_D 300, 타일 재배치) → 더 넓은
|
||||
영역을 선명하게. 마우스 오버 시 확대, **벗어나면 기본 크기+기본 줌(16) 복귀**.
|
||||
- 방향 표시: 빨간 화살표 → **반투명 역삼각형(▽)** = 드론 시야(중심에서 촬영방향으로 벌어짐, yaw 회전).
|
||||
- **줌 +/− 버튼·z값 표시 삭제.** 줌은 휠만(오버 중), 벗어나면 기본값.
|
||||
- 상단 **N**: SVG text 로(위젯 크기에 맞춰 스케일) **크게·붉은색·반투명**.
|
||||
- settingsStore 의 compassZoom 제거(줌은 MapCompass 로컬 상태로 이동, 세션 임시).
|
||||
|
||||
## 추가: 위성지도 옵션
|
||||
- 타일 프록시를 소스별로 확장: [server/routes/tile.ts](../../server/src/routes/tile.ts) `GET /api/tile/:source/:z/:x/:y`
|
||||
- source=osm → OpenStreetMap 일반, source=sat → **Esri World Imagery 위성**(z/row/col 순서, jpeg).
|
||||
- settingsStore `compassMapStyle: 'street'|'sat'`(persist). MapCompass 타일 URL `/api/tile/${src}/...`.
|
||||
- 화면표시 옵션에 지도 [일반|위성] 토글(지도 타입일 때만 노출).
|
||||
- server 재빌드 + PM2 restart. 검증: osm→200 png, sat→200 jpeg.
|
||||
- 주의: Esri World Imagery 는 출처표기 필요(내부 도구, 저부하). 대량이면 라이선스 검토.
|
||||
|
||||
## 주의/추후
|
||||
- OSM 타일 사용 정책: 저부하 유지(줌16 고정 + 클라 96px 이동 시에만 재배치 + 서버 7일 캐시). 대량이면 자체 타일/유료 서비스 검토.
|
||||
- 인터넷 없으면 타일 미표시(테두리/점/화살표만). 오프라인 필요 시 '노선 개요도(데이터 기반)' 타입 추가 검토.
|
||||
@@ -0,0 +1,45 @@
|
||||
# 측점 검색 — 없는 측점 검색 시 엉뚱한 곳으로 튀는 문제 수정
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~200k / output ~3k tokens
|
||||
|
||||
## 문제
|
||||
스테이션바에서 `162080`(162k080) 검색 → 130m 떨어진 `162k210`으로 이동.
|
||||
|
||||
## 원인
|
||||
[StationBar.tsx `handleJumpToMileage`](../../client/src/stationbar/StationBar.tsx#L845) 검색 3단계:
|
||||
1. 마커(structureMarks) 표시측점 일치
|
||||
2. 연속 측점(chain)이 허용오차(stationTolerance 20m) 안
|
||||
3. **폴백: 둘 다 없으면 '무조건 가장 가까운 측점'으로 이동** ← 문제
|
||||
|
||||
162080이 이 영상에 (직교 투영으로) 없으니(조차장에서 드론 투영측점=162210) 3번 폴백이
|
||||
130m 밖 162210으로 점프. '직교 일치 시에만' 원칙과도 불일치.
|
||||
|
||||
## 수정
|
||||
폴백(882~889) 제거 → `if (!times.length) return;`.
|
||||
TOL(기본 20m) 안에 없으면 그 측점은 (직교로) 존재하지 않으므로 **점프하지 않음**.
|
||||
프레임이 조밀(30fps)해 실제 지나는 측점은 항상 TOL 안에서 잡히므로 폴백 불필요.
|
||||
|
||||
## 효과
|
||||
- 없는 측점 검색 시 엉뚱한 곳으로 안 튐(아무 동작 안 함).
|
||||
- 존재하는 측점(마커/연속측점)은 그대로 검색·순환 이동.
|
||||
|
||||
## 추가: '측점 없음' 안내 (사용자 선택 = 안내+이동안함)
|
||||
'이동 안 함'만으로는 커서가 직전 위치(162210)에 남아 "검색이 거기로 간 것처럼" 보이는 혼동 →
|
||||
검색 실패 시 **안내 메시지** 표시.
|
||||
- `handleJumpToMileage` 가 boolean 반환(이동 true / 못 찾음 false).
|
||||
- [PlaybackControls.tsx](../../client/src/stationbar/components/PlaybackControls/PlaybackControls.tsx):
|
||||
Enter 시 못 찾으면 입력창 옆에 **"측점 없음"**(빨강) 1.8초 표시, 재입력/블러 시 숨김.
|
||||
|
||||
## 추가 2: 검색을 '실제 측점(chain)' 기준으로만 (마커 라벨 기준 제거)
|
||||
증상: 162080 검색 시 여러 162080 위치를 순환하다 '맨 끝에' 162210으로 이동.
|
||||
원인: 검색 1순위가 structureMarks 를 '표시 측점 라벨(round km)'로 매칭 → 라벨은 162080이지만
|
||||
실제 위치가 다른(조차장 좌표근접) 마커가 순환에 끼어듦.
|
||||
|
||||
수정: 검색 1순위(마커 라벨 매칭) 제거. **드론 실제 투영 측점(arr[].chain)이 입력값과 TOL 이내인
|
||||
통과로만** 이동(연속구간=1통과, 순환). 없으면 false → '측점 없음'.
|
||||
- deps 에서 structureMarks/timeAtFrac/timeTrackWidth 제거.
|
||||
- 진단용 HUD 표시 `· 검색v2`(빌드 확인용, 확인 후 제거 예정).
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, `npm run build` 성공 → 해시 `index-BrZ52ILs.js` :55000 서빙 확인.
|
||||
@@ -0,0 +1,25 @@
|
||||
# '측점 없음' 안내가 안 뜨던 문제 수정 (CSS 레이아웃)
|
||||
|
||||
**소요 시간**: 약 10분
|
||||
**Context 사용량**: input ~300k / output ~2k tokens
|
||||
|
||||
## 문제
|
||||
없는 측점 검색 시 "측점 없음" 안내가 화면에 안 떴다(로직은 정상).
|
||||
|
||||
## 원인
|
||||
재생/입력 패널 `.transportGroup` 은 고정 크기 + 자식 요소가 모두 `position: absolute`.
|
||||
안내 `<span>` 이 일반 흐름이라 패널 좌상단에 깔려 버튼/패널 뒤에 가려짐.
|
||||
|
||||
## 수정
|
||||
- [PlaybackControls.module.scss](../../client/src/stationbar/components/PlaybackControls/PlaybackControls.module.scss)
|
||||
에 `.notFound` 클래스 추가: 입력창과 같은 위치·크기로 **절대배치**, `z-index:6`, 어두운 배경 + 빨간 글씨.
|
||||
- [PlaybackControls.tsx](../../client/src/stationbar/components/PlaybackControls/PlaybackControls.tsx)
|
||||
안내 span 을 인라인 스타일 → `className={styles.notFound}` (문구 `⚠ 측점 없음`).
|
||||
- 1.8초 후 자동 숨김 / 재입력 시 숨김(기존 로직 유지).
|
||||
|
||||
## 부수 정리
|
||||
- 진단용 HUD 표시 `· 검색v5` 제거(빌드 확인용이었음).
|
||||
|
||||
## 검증
|
||||
- `tsc --noEmit` 통과, `npm run build` 성공 → :55000 서빙.
|
||||
- 168000(범위 밖) 검색 시 '⚠ 측점 없음' 정상 표시 확인(사용자).
|
||||
Reference in New Issue
Block a user