- 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>
111 lines
3.8 KiB
TypeScript
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;
|
|
}
|
|
}
|