ship-gis/src/areaSearch/interactions/BoxResizeInteraction.js

150 lines
4.9 KiB
JavaScript
Raw Normal View 히스토리

/**
* 사각형(Box) 리사이즈 커스텀 인터랙션
*
* OL Modify는 사각형 제약을 지원하지 않으므로 PointerInteraction을 확장.
* - 모서리 드래그: 대각 꼭짓점 고정, 자유 리사이즈
* - 드래그: 반대쪽 고정, 1 리사이즈
*/
import PointerInteraction from 'ol/interaction/Pointer';
const CORNER_TOLERANCE = 16;
const EDGE_TOLERANCE = 12;
/** 점(p)과 선분(a-b) 사이 최소 픽셀 거리 */
function pointToSegmentDist(p, a, b) {
const dx = b[0] - a[0];
const dy = b[1] - a[1];
const lenSq = dx * dx + dy * dy;
if (lenSq === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);
let t = ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / lenSq;
t = Math.max(0, Math.min(1, t));
return Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));
}
export default class BoxResizeInteraction extends PointerInteraction {
constructor(options) {
super({
handleDownEvent: (evt) => BoxResizeInteraction.prototype._handleDown.call(this, evt),
handleDragEvent: (evt) => BoxResizeInteraction.prototype._handleDrag.call(this, evt),
handleUpEvent: (evt) => BoxResizeInteraction.prototype._handleUp.call(this, evt),
});
this.feature_ = options.feature;
this.onResize_ = options.onResize || null;
// corner mode
this.mode_ = null; // 'corner' | 'edge'
this.draggedIndex_ = null;
this.anchorCoord_ = null;
// edge mode
this.edgeIndex_ = null;
this.bbox_ = null;
}
_handleDown(evt) {
const pixel = evt.pixel;
const coords = this.feature_.getGeometry().getCoordinates()[0];
// 1. 모서리 감지 (우선)
for (let i = 0; i < 4; i++) {
const vp = evt.map.getPixelFromCoordinate(coords[i]);
if (Math.hypot(pixel[0] - vp[0], pixel[1] - vp[1]) < CORNER_TOLERANCE) {
this.mode_ = 'corner';
this.draggedIndex_ = i;
this.anchorCoord_ = coords[(i + 2) % 4];
return true;
}
}
// 2. 변 감지
for (let i = 0; i < 4; i++) {
const j = (i + 1) % 4;
const p1 = evt.map.getPixelFromCoordinate(coords[i]);
const p2 = evt.map.getPixelFromCoordinate(coords[j]);
if (pointToSegmentDist(pixel, p1, p2) < EDGE_TOLERANCE) {
this.mode_ = 'edge';
this.edgeIndex_ = i;
const xs = coords.slice(0, 4).map(c => c[0]);
const ys = coords.slice(0, 4).map(c => c[1]);
this.bbox_ = {
minX: Math.min(...xs), maxX: Math.max(...xs),
minY: Math.min(...ys), maxY: Math.max(...ys),
};
return true;
}
}
return false;
}
_handleDrag(evt) {
const coord = evt.coordinate;
if (this.mode_ === 'corner') {
const anchor = this.anchorCoord_;
const minX = Math.min(coord[0], anchor[0]);
const maxX = Math.max(coord[0], anchor[0]);
const minY = Math.min(coord[1], anchor[1]);
const maxY = Math.max(coord[1], anchor[1]);
this.feature_.getGeometry().setCoordinates([[
[minX, maxY], [maxX, maxY], [maxX, minY], [minX, minY], [minX, maxY],
]]);
} else if (this.mode_ === 'edge') {
let { minX, maxX, minY, maxY } = this.bbox_;
// Edge 0: top(TL→TR), 1: right(TR→BR), 2: bottom(BR→BL), 3: left(BL→TL)
switch (this.edgeIndex_) {
case 0: maxY = coord[1]; break;
case 1: maxX = coord[0]; break;
case 2: minY = coord[1]; break;
case 3: minX = coord[0]; break;
}
const x1 = Math.min(minX, maxX), x2 = Math.max(minX, maxX);
const y1 = Math.min(minY, maxY), y2 = Math.max(minY, maxY);
this.feature_.getGeometry().setCoordinates([[
[x1, y2], [x2, y2], [x2, y1], [x1, y1], [x1, y2],
]]);
}
}
_handleUp() {
if (this.mode_) {
this.mode_ = null;
this.draggedIndex_ = null;
this.anchorCoord_ = null;
this.edgeIndex_ = null;
this.bbox_ = null;
if (this.onResize_) this.onResize_(this.feature_);
return true;
}
return false;
}
/**
* 호버 감지: 픽셀이 리사이즈 핸들 위인지 확인
* @returns {{ cursor: string }} | null
*/
isOverHandle(map, pixel) {
const coords = this.feature_.getGeometry().getCoordinates()[0];
// 모서리 감지
const cornerCursors = ['nwse-resize', 'nesw-resize', 'nwse-resize', 'nesw-resize'];
for (let i = 0; i < 4; i++) {
const vp = map.getPixelFromCoordinate(coords[i]);
if (Math.hypot(pixel[0] - vp[0], pixel[1] - vp[1]) < CORNER_TOLERANCE) {
return { cursor: cornerCursors[i] };
}
}
// 변 감지
const edgeCursors = ['ns-resize', 'ew-resize', 'ns-resize', 'ew-resize'];
for (let i = 0; i < 4; i++) {
const j = (i + 1) % 4;
const p1 = map.getPixelFromCoordinate(coords[i]);
const p2 = map.getPixelFromCoordinate(coords[j]);
if (pointToSegmentDist(pixel, p1, p2) < EDGE_TOLERANCE) {
return { cursor: edgeCursors[i] };
}
}
return null;
}
}