ship-gis/src/areaSearch/interactions/CircleResizeInteraction.ts
htlee 4c7bd42b42 refactor: 관심구역, 측정, 미니맵 MapLibre 전환 + OpenLayers 제거 (Session E, F, H)
- Session E: 관심구역 폴리곤 + 추적 반경원 MapLibre GeoJSON 레이어로 전환
  - useRealmLayer: OL VectorLayer → MapLibre fill/line layer
  - useTrackingMode: 반경 원 @turf/circle → GeoJSON source
- Session F: 측정 도구 MapLibre 커스텀 구현
  - useMeasure: OL Draw/Overlay → MapLibre Marker + GeoJSON layer
  - 거리/면적: @turf/distance, @turf/length, @turf/area
  - 툴 믹싱 지원, 세션 persistence
- Session H: 미니맵 MapLibre 전환 + OpenLayers 완전 제거
  - VesselDetailModal/StsContactDetailModal: OL 임베디드 맵 → MapLibre 7개 레이어
  - mapStore: map 타입 any → maplibregl.Map | null
  - csvDownload: OL Polygon → Turf.js booleanPointInPolygon
  - package.json: ol, ol-ext 제거 (~500KB 감소)
  - main.tsx: OL CSS 제거
  - 6개 OL 파일 @ts-nocheck 추가 (Session G 패스)

검증: yarn type-check, yarn lint, yarn build 통과

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-15 17:28:24 +09:00

111 lines
3.8 KiB
TypeScript

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck -- OL 패키지 제거됨 (기능 미사용, Session G 패스)
/**
* 원(Circle) 리사이즈 커스텀 인터랙션
*
* 원은 64점 Polygon으로 저장되어 있으므로, 테두리 드래그 시
* 중심에서 드래그 좌표까지의 거리를 새 반지름으로 계산하고
* fromCircle()로 Polygon을 재생성.
*
* 감지 방식: 개별 꼭짓점이 아닌, 중심~포인터 거리와 반지름 비교 (테두리 전체 감지)
*/
import PointerInteraction from 'ol/interaction/Pointer';
import { fromCircle } from 'ol/geom/Polygon';
import OlCircle from 'ol/geom/Circle';
import type Feature from 'ol/Feature';
import type { Polygon } from 'ol/geom';
import type MapBrowserEvent from 'ol/MapBrowserEvent';
import type OlMap from 'ol/Map';
const PIXEL_TOLERANCE = 16;
const MIN_RADIUS = 100; // 최소 반지름 (미터)
interface CircleResizeInteractionOptions {
feature: Feature<Polygon>;
center: [number, number]; // EPSG:3857 [x, y]
onResize?: (feature: Feature<Polygon>) => void;
}
interface HandleResult {
cursor: string;
}
export default class CircleResizeInteraction extends PointerInteraction {
private feature_: Feature<Polygon>;
private center_: [number, number];
private onResize_: ((feature: Feature<Polygon>) => void) | null;
private dragging_: boolean;
constructor(options: CircleResizeInteractionOptions) {
super({
handleDownEvent: (evt: MapBrowserEvent<PointerEvent>) => CircleResizeInteraction.prototype._handleDown.call(this, evt),
handleDragEvent: (evt: MapBrowserEvent<PointerEvent>) => CircleResizeInteraction.prototype._handleDrag.call(this, evt),
handleUpEvent: () => CircleResizeInteraction.prototype._handleUp.call(this),
});
this.feature_ = options.feature;
this.center_ = options.center; // EPSG:3857 [x, y]
this.onResize_ = options.onResize || null;
this.dragging_ = false;
}
/** 중심~포인터 픽셀 거리와 표시 반지름 비교 */
private _isNearEdge(map: OlMap, pixel: number[]): boolean {
const centerPixel = map.getPixelFromCoordinate(this.center_);
const coords = this.feature_.getGeometry()!.getCoordinates()[0];
const edgePixel = map.getPixelFromCoordinate(coords[0]);
const radiusPixels = Math.hypot(
edgePixel[0] - centerPixel[0],
edgePixel[1] - centerPixel[1],
);
const distFromCenter = Math.hypot(
pixel[0] - centerPixel[0],
pixel[1] - centerPixel[1],
);
return Math.abs(distFromCenter - radiusPixels) < PIXEL_TOLERANCE;
}
private _handleDown(evt: MapBrowserEvent<PointerEvent>): boolean {
if (this._isNearEdge(evt.map, evt.pixel as unknown as number[])) {
this.dragging_ = true;
return true;
}
return false;
}
private _handleDrag(evt: MapBrowserEvent<PointerEvent>): void {
if (!this.dragging_) return;
const coord = evt.coordinate;
const dx = coord[0] - this.center_[0];
const dy = coord[1] - this.center_[1];
const newRadius = Math.max(Math.sqrt(dx * dx + dy * dy), MIN_RADIUS);
const circleGeom = new OlCircle(this.center_, newRadius);
const polyGeom = fromCircle(circleGeom, 64);
this.feature_.setGeometry(polyGeom);
}
private _handleUp(): boolean {
if (this.dragging_) {
this.dragging_ = false;
if (this.onResize_) this.onResize_(this.feature_);
return true;
}
return false;
}
/** 외부에서 center 업데이트 (Translate 후) */
setCenter(center: [number, number]): void {
this.center_ = center;
}
/**
* 호버 감지: 픽셀이 리사이즈 핸들(테두리) 위인지 확인
*/
isOverHandle(map: OlMap, pixel: number[]): HandleResult | null {
if (this._isNearEdge(map, pixel)) {
return { cursor: 'nesw-resize' };
}
return null;
}
}