junh_eee(이준희)
1 year ago
6 changed files with 1259 additions and 29 deletions
@ -0,0 +1,679 @@ |
|||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
import { InfoModal } from '../../../modal/InfoModal'; |
||||||
|
import * as turf from '@turf/turf'; |
||||||
|
import { useEffect, useState } from 'react'; |
||||||
|
|
||||||
|
export const InitFeature = (type, id) => { |
||||||
|
return { |
||||||
|
type: 'Feature', |
||||||
|
geometry: { |
||||||
|
type: type, |
||||||
|
coordinates: [] |
||||||
|
}, |
||||||
|
properties: { id: id, center: '' } |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
//소수점 7자리까지만 자름 / coord = [lng, lat]
|
||||||
|
export const FormattingCoord = coord => { |
||||||
|
const resultArr = []; |
||||||
|
|
||||||
|
coord.forEach(co => { |
||||||
|
const split = String(co).split('.'); |
||||||
|
const result = split[0] + '.' + split[1].substring(0, 7); |
||||||
|
|
||||||
|
resultArr.push(Number(result)); |
||||||
|
}); |
||||||
|
|
||||||
|
return resultArr; |
||||||
|
}; |
||||||
|
|
||||||
|
//거리 계산(meters로 리턴)
|
||||||
|
// 사용 1. 원의 센터로부터 e.coord까지
|
||||||
|
// 사용 2. distance 마커
|
||||||
|
export const CalculateDistance = (mouse, center) => { |
||||||
|
const centerCoord = turf.point(center); |
||||||
|
const mouseCoord = turf.point(mouse); |
||||||
|
|
||||||
|
const options = { units: 'kilometers' }; |
||||||
|
|
||||||
|
const distance = Math.round( |
||||||
|
turf.distance(centerCoord, mouseCoord, options) * 1000 |
||||||
|
); |
||||||
|
|
||||||
|
return distance; |
||||||
|
}; |
||||||
|
|
||||||
|
export const LanncDraw = props => { |
||||||
|
const dispatch = useDispatch(); |
||||||
|
const mapControl = useSelector(state => state.controlMapReducer); |
||||||
|
const mapObject = props.mapObject; |
||||||
|
const canvas = mapObject.getCanvasContainer(); |
||||||
|
|
||||||
|
//비행계획서 작성 완료 후에는 수정 불가일거라서 이거는 임시 보류
|
||||||
|
const isDone = props.isDone; |
||||||
|
const isDisabled = props.isDisabled; |
||||||
|
|
||||||
|
const [alertModal, setAlertModal] = useState({ |
||||||
|
isOpen: false, |
||||||
|
title: '', |
||||||
|
desc: '' |
||||||
|
}); |
||||||
|
|
||||||
|
//도형들이 온전히 그려진 후 변경 될 때 마다 감지
|
||||||
|
const [isDrawDone, setIsDrawDone] = useState(false); |
||||||
|
|
||||||
|
const [mouseDownEve, setMouseDownEve] = useState(false); |
||||||
|
|
||||||
|
const areaInfo = { |
||||||
|
coordinates: [], |
||||||
|
bufferZone: 0, |
||||||
|
areaType: '' |
||||||
|
}; |
||||||
|
|
||||||
|
const geojson = props.geojson; |
||||||
|
let mode = props.mode; |
||||||
|
|
||||||
|
let guideLine = InitFeature('LineString', 'guideline'); |
||||||
|
let lineString = InitFeature('LineString', 'polyline'); |
||||||
|
let bufferPolyline = InitFeature('LineString', 'buffer'); |
||||||
|
|
||||||
|
let polygon = InitFeature('Polygon', 'polygon'); |
||||||
|
|
||||||
|
let circle = InitFeature('Polygon', 'circle'); |
||||||
|
|
||||||
|
let point = []; |
||||||
|
|
||||||
|
let dragCircleIdx; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (mapControl.drawType) drawInit(); |
||||||
|
}, [mapControl.drawType]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (isDrawDone) { |
||||||
|
props.handlerConfirm(props.areaCoordList); |
||||||
|
setIsDrawDone(false); |
||||||
|
} |
||||||
|
}, [isDrawDone]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const area = props.areaCoordList[0]; |
||||||
|
console.log(area, '>>>>>draw area'); |
||||||
|
if (area.areaType && area.areaType !== '') { |
||||||
|
handlerPastDraw(); |
||||||
|
} |
||||||
|
}, [props.areaCoordList]); |
||||||
|
|
||||||
|
const drawInit = () => { |
||||||
|
handlerButtonClick(mapControl.drawType); |
||||||
|
}; |
||||||
|
|
||||||
|
//그리기 타입 선택
|
||||||
|
const handlerButtonClick = newMode => { |
||||||
|
handlerClearMode(mode); |
||||||
|
|
||||||
|
if (mode === newMode) { |
||||||
|
mode = null; |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
handlerStartMode(newMode); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerClearMode = () => { |
||||||
|
console.log('clearMode'); |
||||||
|
finishDraw(); |
||||||
|
|
||||||
|
removeGeoJson(); |
||||||
|
props.handlerInitCoordinates(); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerStartMode = mode => { |
||||||
|
if (!mode) return; |
||||||
|
|
||||||
|
mapObject.on('click', clickEve); |
||||||
|
}; |
||||||
|
|
||||||
|
const removeListener = () => { |
||||||
|
console.log('removeListener'); |
||||||
|
|
||||||
|
mapObject.off('click', clickEve); |
||||||
|
mapObject.off('click', onClickCircle); |
||||||
|
mapObject.off('mouseup', onMouseUp); |
||||||
|
mapObject.off('mousedown', 'waypoint', onMouseDown); |
||||||
|
mapObject.off('mousedown', 'polygon', onMouseDown); |
||||||
|
mapObject.off('mousemove', onMouseMove); |
||||||
|
mapObject.off('mousemove', onMouseMovePolyline); |
||||||
|
mapObject.off('mousemove', onMouseMovePolygon); |
||||||
|
mapObject.off('contextmenu', finishDraw); |
||||||
|
|
||||||
|
// 이거 있나 없나 뭔 차이지?
|
||||||
|
setMouseDownEve(false); |
||||||
|
}; |
||||||
|
|
||||||
|
const removeGeoJson = () => { |
||||||
|
console.log('removeGeoJson'); |
||||||
|
|
||||||
|
handlerRemoveMarker(); |
||||||
|
|
||||||
|
guideLine.geometry.coordinates = []; |
||||||
|
lineString.geometry.coordinates = []; |
||||||
|
bufferPolyline.geometry.coordinates = []; |
||||||
|
|
||||||
|
polygon.geometry.coordinates = []; |
||||||
|
|
||||||
|
circle.geometry.coordinates = []; |
||||||
|
|
||||||
|
point = []; |
||||||
|
|
||||||
|
geojson.features = []; |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
const finishDraw = () => { |
||||||
|
removeListener(); |
||||||
|
console.log('finish'); |
||||||
|
|
||||||
|
const drawType = mapControl.drawType; |
||||||
|
const path = handlerGetGeoJsonCoord('point'); |
||||||
|
if (path.length > 0) { |
||||||
|
if (drawType === 'LINE') { |
||||||
|
if (path.length > 1) { |
||||||
|
handlerReplaceDuplicate('guideline', ''); |
||||||
|
handlerSaveAreaInfo(lineString.geometry.coordinates); |
||||||
|
} else { |
||||||
|
setAlertModal({ |
||||||
|
isOpen: true, |
||||||
|
title: '좌표 최소 개수', |
||||||
|
desc: '좌표를 두 개 점으로 이어주세요.' |
||||||
|
}); |
||||||
|
removeGeoJson(); |
||||||
|
props.handlerDrawType('RESET'); |
||||||
|
// mapObject.on('click', clickEve);
|
||||||
|
} |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
} else if (drawType === 'POLYGON') { |
||||||
|
if (path.length > 2) { |
||||||
|
polygon.geometry.coordinates[0] = path; |
||||||
|
|
||||||
|
handlerReplaceDuplicate('polygon', polygon); |
||||||
|
handlerSaveAreaInfo(polygon.geometry.coordinates[0]); |
||||||
|
} else { |
||||||
|
setAlertModal({ |
||||||
|
isOpen: true, |
||||||
|
title: '좌표 최소 개수', |
||||||
|
desc: '좌표를 세 개 점으로 이어주세요.' |
||||||
|
}); |
||||||
|
removeGeoJson(); |
||||||
|
props.handlerDrawType('RESET'); |
||||||
|
// mapObject.on('click', clickEve);
|
||||||
|
} |
||||||
|
} |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const clickEve = e => { |
||||||
|
const drawType = mapControl.drawType; |
||||||
|
if (drawType === 'LINE') onClickFeature(e, lineString); |
||||||
|
if (drawType === 'POLYGON') onClickFeature(e, polygon); |
||||||
|
if (drawType === 'CIRCLE') onClickCircle(e); |
||||||
|
}; |
||||||
|
|
||||||
|
// polyline, polygon 생성
|
||||||
|
const onClickFeature = (e, obj) => { |
||||||
|
const formatCoord = FormattingCoord([e.lngLat.lng, e.lngLat.lat]); |
||||||
|
//현재 내 좌표가 waypoint레이어의 geojson도형 안에 속해있는지 안해있는지?
|
||||||
|
//geojson을 반환해주는 듯?
|
||||||
|
const features = mapObject.queryRenderedFeatures(e.point, { |
||||||
|
layers: ['waypoint'] |
||||||
|
}); |
||||||
|
|
||||||
|
const id = obj.properties.id; |
||||||
|
|
||||||
|
if (geojson.features.length > 1) { |
||||||
|
handlerReplaceDuplicate(id, ''); |
||||||
|
} |
||||||
|
|
||||||
|
if (features.length) { |
||||||
|
const featuresId = features[0].properties.id; |
||||||
|
handlerReplaceDuplicate(featuresId, ''); |
||||||
|
} else { |
||||||
|
const index = geojson.features.filter( |
||||||
|
geo => geo.properties?.id === 'point' |
||||||
|
).length; |
||||||
|
|
||||||
|
const wayPoint = handlerCreatePoint( |
||||||
|
formatCoord, |
||||||
|
index, |
||||||
|
mapControl.drawType |
||||||
|
); |
||||||
|
handlerReplaceDuplicate('Point', wayPoint); |
||||||
|
} |
||||||
|
|
||||||
|
guideLine.geometry.coordinates = [formatCoord]; |
||||||
|
|
||||||
|
if (geojson.features.length > 1) { |
||||||
|
//나머지 좌표
|
||||||
|
const coordinates = handlerGetGeoJsonCoord('point'); |
||||||
|
|
||||||
|
obj.geometry.coordinates = |
||||||
|
id === 'polyline' ? coordinates : [coordinates]; |
||||||
|
geojson.features.push(obj); |
||||||
|
|
||||||
|
addMileStone(coordinates, ''); |
||||||
|
} else { |
||||||
|
//첫 좌표
|
||||||
|
mapObject.on('contextmenu', finishDraw); |
||||||
|
mapObject.on( |
||||||
|
'mousemove', |
||||||
|
id === 'polyline' ? onMouseMovePolyline : onMouseMovePolygon |
||||||
|
); |
||||||
|
addMileStone(formatCoord, ''); |
||||||
|
} |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
// polyline 가이드 생성
|
||||||
|
const onMouseMovePolyline = e => { |
||||||
|
const formatCoord = FormattingCoord([e.lngLat.lng, e.lngLat.lat]); |
||||||
|
if (guideLine.geometry.coordinates.length > 1) { |
||||||
|
guideLine.geometry.coordinates.pop(); |
||||||
|
handlerReplaceDuplicate('guideline', guideLine); |
||||||
|
} |
||||||
|
guideLine.geometry.coordinates.push(formatCoord); |
||||||
|
|
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
// polygon 가이드 생성
|
||||||
|
const onMouseMovePolygon = e => { |
||||||
|
const formatCoord = FormattingCoord([e.lngLat.lng, e.lngLat.lat]); |
||||||
|
|
||||||
|
if (polygon.geometry.coordinates.length > 0) { |
||||||
|
if (polygon.geometry.coordinates[0].length > 1) { |
||||||
|
if (guideLine.geometry.coordinates.length > 1) { |
||||||
|
guideLine.geometry.coordinates.pop(); |
||||||
|
polygon.geometry.coordinates[0].pop(); |
||||||
|
} |
||||||
|
guideLine.geometry.coordinates.push(formatCoord); |
||||||
|
polygon.geometry.coordinates[0].push(formatCoord); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 이거.. 왜 안해도 잘 되지....?
|
||||||
|
// handlerReplaceDuplicate('polygon', polygon);
|
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
// circle 생성
|
||||||
|
const onClickCircle = e => { |
||||||
|
console.log('circleClick'); |
||||||
|
const formatCoord = FormattingCoord([e.lngLat.lng, e.lngLat.lat]); |
||||||
|
|
||||||
|
if (circle.geometry.coordinates.length === 0) { |
||||||
|
mapObject.on('mousedown', 'polygon', onMouseDown); |
||||||
|
} |
||||||
|
|
||||||
|
const circleCoords = handlerGetCircleCoord(formatCoord, 100); |
||||||
|
circle.properties.center = formatCoord; |
||||||
|
circle.geometry.coordinates = circleCoords; |
||||||
|
|
||||||
|
handlerReplaceDuplicate('circle', circle); |
||||||
|
handlerSaveAreaInfo(''); |
||||||
|
|
||||||
|
addMileStone(formatCoord, 100); |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
const onMouseDown = e => { |
||||||
|
e.preventDefault(); |
||||||
|
console.log('down'); |
||||||
|
//타입 교체만 하면 왜 처음엔 down이 두번 잡힐까...
|
||||||
|
// console.log(e.features[0].properties.type, '>>down e');
|
||||||
|
|
||||||
|
canvas.style.cursor = 'grab'; |
||||||
|
|
||||||
|
if (circle.geometry.coordinates.length > 0) { |
||||||
|
removeListener(); |
||||||
|
mapObject.on('mousedown', 'polygon', onMouseDown); |
||||||
|
} else { |
||||||
|
dragCircleIdx = e.features[0].properties.index; |
||||||
|
} |
||||||
|
|
||||||
|
mapObject.on('mousemove', onMouseMove); |
||||||
|
mapObject.on('mouseup', onMouseUp); |
||||||
|
|
||||||
|
mapObject.off('click', clickEve); |
||||||
|
}; |
||||||
|
|
||||||
|
const onMouseMove = e => { |
||||||
|
const formatCoord = FormattingCoord([e.lngLat.lng, e.lngLat.lat]); |
||||||
|
canvas.style.cursor = 'grabbing'; |
||||||
|
|
||||||
|
if (circle.geometry.coordinates.length > 0) { |
||||||
|
const distance = CalculateDistance(formatCoord, circle.properties.center); |
||||||
|
const center = circle.properties.center; |
||||||
|
const circleCoords = handlerGetCircleCoord(center, distance); |
||||||
|
circle.geometry.coordinates = circleCoords; |
||||||
|
} else { |
||||||
|
geojson.features = geojson.features.map(geo => { |
||||||
|
const coord = formatCoord; |
||||||
|
|
||||||
|
if (geo.properties?.index === dragCircleIdx) { |
||||||
|
geo.geometry.coordinates = coord; |
||||||
|
} |
||||||
|
|
||||||
|
if (geo.properties?.id === 'polyline') { |
||||||
|
geo.geometry.coordinates[dragCircleIdx] = coord; |
||||||
|
lineString = geo; |
||||||
|
} |
||||||
|
|
||||||
|
if (geo.properties?.id === 'buffer') { |
||||||
|
geo.geometry.coordinates = []; |
||||||
|
} |
||||||
|
|
||||||
|
if (geo.properties?.id === 'polygon') { |
||||||
|
geo.geometry.coordinates[0][dragCircleIdx] = coord; |
||||||
|
polygon = geo; |
||||||
|
} |
||||||
|
|
||||||
|
return geo; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
}; |
||||||
|
|
||||||
|
const onMouseUp = () => { |
||||||
|
canvas.style.cursor = ''; |
||||||
|
console.log('up'); |
||||||
|
|
||||||
|
mapObject.off('mousedown', 'waypoint', onMouseDown); |
||||||
|
mapObject.off('mousemove', onMouseMove); |
||||||
|
mapObject.off('mouseup', onMouseUp); |
||||||
|
mapObject.off('click', clickEve); |
||||||
|
setMouseDownEve(false); |
||||||
|
|
||||||
|
const type = mapControl.drawType; |
||||||
|
const obj = |
||||||
|
type === 'LINE' |
||||||
|
? lineString |
||||||
|
: type === 'POLYGON' |
||||||
|
? polygon |
||||||
|
: type === 'CIRCLE' |
||||||
|
? circle |
||||||
|
: undefined; |
||||||
|
|
||||||
|
if (obj) { |
||||||
|
const id = obj.properties?.id; |
||||||
|
const coord = |
||||||
|
id === 'polyline' |
||||||
|
? obj.geometry.coordinates |
||||||
|
: obj.geometry.coordinates[0]; |
||||||
|
|
||||||
|
if (id === 'circle') { |
||||||
|
mapObject.on('click', clickEve); |
||||||
|
handlerSaveAreaInfo(''); |
||||||
|
} else { |
||||||
|
// mapObject.on('mousedown', 'waypoint', onMouseDown);
|
||||||
|
handlerSaveAreaInfo(coord); |
||||||
|
} |
||||||
|
} else { |
||||||
|
// 저장된 좌표 불러왔을 때
|
||||||
|
const areas = props.areaCoordList[0]; |
||||||
|
const type = areas.areaType; |
||||||
|
|
||||||
|
const paths = []; |
||||||
|
areas.coordList.forEach(coord => paths.push([coord.lon, coord.lat])); |
||||||
|
|
||||||
|
if (type === 'LINE') { |
||||||
|
handlerSaveAreaInfo(lineString.geometry.coordinates); |
||||||
|
} else if (type === 'POLYGON') { |
||||||
|
handlerSaveAreaInfo(polygon.geometry.coordinates[0]); |
||||||
|
} else if (type === 'CIRCLE') { |
||||||
|
handlerSaveAreaInfo(''); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 도형 정보 변경되면 저장
|
||||||
|
const handlerSaveAreaInfo = coord => { |
||||||
|
console.log('areaInfo'); |
||||||
|
const bufferZone = polygon.geometry.coordinates.length > 0 ? 0 : 100; |
||||||
|
const prePath = []; |
||||||
|
|
||||||
|
if (lineString.geometry.coordinates.length > 0) { |
||||||
|
areaInfo.areaType = 'LINE'; |
||||||
|
} else if (polygon.geometry.coordinates.length > 0) { |
||||||
|
areaInfo.areaType = 'POLYGON'; |
||||||
|
} else if (circle.geometry.coordinates.length > 0) { |
||||||
|
areaInfo.areaType = 'CIRCLE'; |
||||||
|
} |
||||||
|
|
||||||
|
if (areaInfo.areaType !== 'CIRCLE') { |
||||||
|
coord.forEach(item => { |
||||||
|
const p = { |
||||||
|
lat: item[1], |
||||||
|
lon: item[0] |
||||||
|
}; |
||||||
|
prePath.push(p); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
areaInfo.coordinates = prePath; |
||||||
|
areaInfo.bufferZone = bufferZone; |
||||||
|
if (areaInfo.areaType === 'CIRCLE') { |
||||||
|
const point = { |
||||||
|
lat: circle.properties.center[1], |
||||||
|
lon: circle.properties.center[0] |
||||||
|
}; |
||||||
|
areaInfo.coordinates = [point]; |
||||||
|
areaInfo.bufferZone = CalculateDistance( |
||||||
|
circle.geometry.coordinates[0][0], |
||||||
|
circle.properties.center |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
props.handlerCoordinates(areaInfo); |
||||||
|
setIsDrawDone(true); |
||||||
|
}; |
||||||
|
|
||||||
|
// 확정된 도형 재 생성(let이라서 지워지기 때문에)
|
||||||
|
const handlerPastDraw = () => { |
||||||
|
if (props.areaCoordList) { |
||||||
|
console.log('pastDraw'); |
||||||
|
|
||||||
|
const areas = props.areaCoordList[0]; |
||||||
|
const paths = []; |
||||||
|
areas.coordList.forEach(coord => paths.push([coord.lon, coord.lat])); |
||||||
|
|
||||||
|
if (areas.areaType) { |
||||||
|
if (areas.areaType === 'CIRCLE') { |
||||||
|
const radius = areas.bufferZone; |
||||||
|
const circleCoords = handlerGetCircleCoord(paths[0], radius); |
||||||
|
|
||||||
|
circle.properties.center = paths[0]; |
||||||
|
circle.geometry.coordinates = circleCoords; |
||||||
|
|
||||||
|
geojson.features.push(circle); |
||||||
|
} else { |
||||||
|
if (areas.areaType === 'LINE') { |
||||||
|
lineString.geometry.coordinates = paths; |
||||||
|
geojson.features.push(lineString); |
||||||
|
|
||||||
|
// 버퍼 생성
|
||||||
|
if (areas.bufferCoordList) { |
||||||
|
const bufferPaths = []; |
||||||
|
|
||||||
|
areas.bufferCoordList.forEach(bfCoord => |
||||||
|
bufferPaths.push([bfCoord.lon, bfCoord.lat]) |
||||||
|
); |
||||||
|
|
||||||
|
bufferPolyline.geometry.coordinates = bufferPaths; |
||||||
|
|
||||||
|
handlerReplaceDuplicate('buffer', bufferPolyline); |
||||||
|
} |
||||||
|
} else if (areas.areaType === 'POLYGON') { |
||||||
|
polygon.geometry.coordinates = [paths]; |
||||||
|
geojson.features.push(polygon); |
||||||
|
} |
||||||
|
|
||||||
|
// 포인트 생성
|
||||||
|
paths.forEach((p, i) => handlerCreatePoint(p, i, areas.areaType)); |
||||||
|
handlerReplaceDuplicate('point', ''); |
||||||
|
point.forEach(p => geojson.features.push(p)); |
||||||
|
|
||||||
|
//once로 하면 꼬이는건 해결되는데 지도를 움직이면 이벤트가 사라짐 -> 왜? onMouseDown이 실행된게 아니잖아?
|
||||||
|
//on으로 하면 그 반대 현상 -> 이벤트 다 지워줬는데 왜 down이 두번 잡혀??????
|
||||||
|
//얘만 해결하면 끝인데...
|
||||||
|
if (!mouseDownEve) { |
||||||
|
mapObject.on('mousedown', 'waypoint', onMouseDown); |
||||||
|
setMouseDownEve(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// 기존 마커 제거 후 재 생성
|
||||||
|
handlerRemoveMarker(); |
||||||
|
handlerCreateAllMarker(paths); |
||||||
|
|
||||||
|
mapObject.setPaintProperty('waypoint', 'circle-radius', 8); |
||||||
|
mapObject.getSource('geojson').setData(geojson); |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 새로운 popup 한 개 추가 (coord의 뒤에서 두개의 좌표 이용)
|
||||||
|
const addMileStone = (coord, radius) => { |
||||||
|
const len = coord.length; |
||||||
|
let lngLat = coord; |
||||||
|
let anchor; |
||||||
|
let distance; |
||||||
|
|
||||||
|
if (coord[0].length) { |
||||||
|
if ( |
||||||
|
mapControl.drawType !== 'CIRCLE' || |
||||||
|
props.areaCoordList[0].areaType !== 'CIRCLE' |
||||||
|
) { |
||||||
|
lngLat = handlerGetMidPoint(coord[len - 2], coord[len - 1]); |
||||||
|
anchor = [0, 0]; |
||||||
|
distance = CalculateDistance(coord[len - 2], coord[len - 1]); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if ( |
||||||
|
mapControl.drawType === 'CIRCLE' || |
||||||
|
props.areaCoordList[0].areaType === 'CIRCLE' |
||||||
|
) { |
||||||
|
anchor = [20, 35]; |
||||||
|
distance = radius; |
||||||
|
} else { |
||||||
|
anchor = [0, -10]; |
||||||
|
distance = 'Start'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const popup = new props.mapboxgl.Popup({ |
||||||
|
offset: anchor, |
||||||
|
closeButton: false, |
||||||
|
closeOnClick: false |
||||||
|
}) |
||||||
|
.setLngLat(lngLat) |
||||||
|
.setHTML(handlerGetHtmlContent(distance)) |
||||||
|
.addTo(mapObject); |
||||||
|
}; |
||||||
|
|
||||||
|
// 좌표 기반으로 모든 마커 재 생성
|
||||||
|
const handlerCreateAllMarker = coord => { |
||||||
|
console.log('allCreateMarker'); |
||||||
|
const areas = props.areaCoordList[0]; |
||||||
|
|
||||||
|
if (areas.areaType !== 'CIRCLE') { |
||||||
|
for (let i = 0; i < coord.length; i++) { |
||||||
|
if (i == 0) { |
||||||
|
addMileStone(coord[i], ''); |
||||||
|
} else { |
||||||
|
addMileStone([coord[i], coord[i - 1]], ''); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (areas.areaType === 'POLYGON') { |
||||||
|
addMileStone([coord[0], coord[coord.length - 1]], ''); |
||||||
|
} |
||||||
|
} else { |
||||||
|
addMileStone(coord[0], areas.bufferZone); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
// 모든 마커 삭제
|
||||||
|
const handlerRemoveMarker = () => { |
||||||
|
const ele = document.getElementsByClassName('mapboxgl-popup'); |
||||||
|
const eleArr = Array.from(ele); |
||||||
|
eleArr?.forEach(marker => marker.remove()); |
||||||
|
}; |
||||||
|
|
||||||
|
// 두 좌표 간의 중간 지점 좌표 반환
|
||||||
|
const handlerGetMidPoint = (dis1, dis2) => { |
||||||
|
return [(dis1[0] + dis2[0]) / 2, (dis1[1] + dis2[1]) / 2]; |
||||||
|
}; |
||||||
|
|
||||||
|
// html Content 반환
|
||||||
|
const handlerGetHtmlContent = distance => { |
||||||
|
const text = |
||||||
|
typeof distance === 'number' ? fromMetersToText(distance) : distance; |
||||||
|
|
||||||
|
return ( |
||||||
|
'<div style="display:inline-block;padding:5px;text-align:center;background-color:#fff;border:1px solid #000;font-size:13px;color:#ff0000;"><span>' + |
||||||
|
text + |
||||||
|
'</span></div>' |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
// 미터 반환(m붙여서)
|
||||||
|
const fromMetersToText = meters => { |
||||||
|
meters = meters || 0; |
||||||
|
const text = parseFloat(meters.toFixed(1)) + 'm'; |
||||||
|
return text; |
||||||
|
}; |
||||||
|
|
||||||
|
// geojson에서 중복되는 obj 제거 or 제거 후 새로 생성
|
||||||
|
const handlerReplaceDuplicate = (id, obj) => { |
||||||
|
geojson.features = geojson.features.filter( |
||||||
|
geo => geo.properties?.id !== id |
||||||
|
); |
||||||
|
if (obj !== '') geojson.features.push(obj); |
||||||
|
}; |
||||||
|
|
||||||
|
// geojson에서 원하는 Id의 coord만 추출
|
||||||
|
const handlerGetGeoJsonCoord = id => { |
||||||
|
return geojson.features |
||||||
|
.filter(geo => geo.properties?.id === id) |
||||||
|
.map(geo => geo.geometry.coordinates); |
||||||
|
}; |
||||||
|
|
||||||
|
// circle 360도 좌표 반환
|
||||||
|
const handlerGetCircleCoord = (center, distance) => { |
||||||
|
const options = { |
||||||
|
steps: 360, |
||||||
|
units: 'kilometers' |
||||||
|
}; |
||||||
|
return turf.circle(center, distance / 1000, options).geometry.coordinates; |
||||||
|
}; |
||||||
|
|
||||||
|
// 포인트 생성
|
||||||
|
const handlerCreatePoint = (coord, index, type) => { |
||||||
|
const wayPoint = { |
||||||
|
type: 'Feature', |
||||||
|
geometry: { |
||||||
|
type: 'Point', |
||||||
|
coordinates: coord |
||||||
|
}, |
||||||
|
properties: { id: 'point', index: index, type: type } |
||||||
|
}; |
||||||
|
point.push(wayPoint); |
||||||
|
|
||||||
|
return wayPoint; |
||||||
|
}; |
||||||
|
|
||||||
|
return <InfoModal modal={alertModal} setModal={setAlertModal} />; |
||||||
|
}; |
@ -0,0 +1,255 @@ |
|||||||
|
import 'mapbox-gl/dist/mapbox-gl.css'; |
||||||
|
import mapboxgl from 'mapbox-gl'; |
||||||
|
import MapboxLanguage from '@mapbox/mapbox-gl-language'; |
||||||
|
import { MAPBOX_TOKEN } from '../../configs/constants'; |
||||||
|
import { useEffect, useRef, useState } from 'react'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
import { |
||||||
|
Card, |
||||||
|
CardBody, |
||||||
|
Input, |
||||||
|
InputGroup, |
||||||
|
InputGroupAddon, |
||||||
|
InputGroupText |
||||||
|
} from 'reactstrap'; |
||||||
|
import { Search } from 'react-feather'; |
||||||
|
import { initFlightBas } from '../../modules/basis/flight/models/basisFlightModel'; |
||||||
|
import { |
||||||
|
AREA_COORDINATE_LIST_SAVE, |
||||||
|
AREA_DETAIL_INIT, |
||||||
|
FLIGHT_PLAN_AREA_BUFFER_LIST |
||||||
|
} from '../../modules/basis/flight/actions/basisFlightAction'; |
||||||
|
import { flightPlanAPI } from '../../modules/basis/flight/apis/basisFlightApi'; |
||||||
|
import { LanncDraw } from '../../components/map/mapbox/draw/LanncDraw'; |
||||||
|
import { drawTypeChangeAction } from '../../modules/control/map/actions/controlMapActions'; |
||||||
|
|
||||||
|
export default function LaancAreaMap({ |
||||||
|
centeredModal, |
||||||
|
handlerMapInit, |
||||||
|
mapObject |
||||||
|
}) { |
||||||
|
const dispatch = useDispatch(); |
||||||
|
const mapControl = useSelector(state => state.controlMapReducer); |
||||||
|
const { areaCoordList } = useSelector(state => state.flightState); |
||||||
|
|
||||||
|
const mapContainer = useRef(null); |
||||||
|
const [isMapLoad, setIsMapLoad] = useState(false); |
||||||
|
const [mode, setMode] = useState(); |
||||||
|
const [mapAreaCoordList, setMapAreaCoordList] = useState( |
||||||
|
initFlightBas.initDetail.areaList |
||||||
|
); |
||||||
|
|
||||||
|
const [query, setQuery] = useState(''); |
||||||
|
const [searchRes, setSearchRes] = useState([]); |
||||||
|
const [isSearch, setIsSearch] = useState(false); |
||||||
|
|
||||||
|
const [number, setNumber] = useState(0); |
||||||
|
|
||||||
|
const geojson = { |
||||||
|
type: 'FeatureCollection', |
||||||
|
features: [] |
||||||
|
}; |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
handlerMapInit('detail'); |
||||||
|
|
||||||
|
return () => { |
||||||
|
dispatch(AREA_DETAIL_INIT()); |
||||||
|
}; |
||||||
|
}, []); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setMode(mapControl.drawType); |
||||||
|
}, [mapControl.drawType]); |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
if (areaCoordList && mapObject) { |
||||||
|
if ( |
||||||
|
areaCoordList[0].coordList[0].lat !== 0 && |
||||||
|
areaCoordList[0].coordList[0].lon !== 0 |
||||||
|
) { |
||||||
|
if (number === 0) { |
||||||
|
mapObject.setCenter([ |
||||||
|
areaCoordList[0].coordList[0].lon, |
||||||
|
areaCoordList[0].coordList[0].lat |
||||||
|
]); |
||||||
|
setNumber(number + 1); |
||||||
|
} |
||||||
|
} |
||||||
|
setMapAreaCoordList(areaCoordList); |
||||||
|
} |
||||||
|
}, [areaCoordList, mapObject, number]); |
||||||
|
|
||||||
|
//지역 검색
|
||||||
|
const handlerSearch = async () => { |
||||||
|
const res = await flightPlanAPI.searchArea({ query: query }); |
||||||
|
setIsSearch(true); |
||||||
|
setSearchRes(res.data.items); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerChange = e => { |
||||||
|
const { name, value } = e.target; |
||||||
|
|
||||||
|
if (name == 'searchInput') { |
||||||
|
setQuery(value); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerEnter = e => { |
||||||
|
if (e.key == 'Enter') { |
||||||
|
handlerSearch(); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerCoord = (mapx, mapy) => { |
||||||
|
const numberString = [mapx, mapy]; |
||||||
|
const latlng = []; |
||||||
|
|
||||||
|
numberString.map(coord => { |
||||||
|
let digits = coord.split(''); |
||||||
|
|
||||||
|
if (digits[0] !== '1') { |
||||||
|
digits.splice(2, 0, '.'); |
||||||
|
} else { |
||||||
|
digits.splice(3, 0, '.'); |
||||||
|
} |
||||||
|
|
||||||
|
latlng.push(Number(digits.join(''))); |
||||||
|
}); |
||||||
|
|
||||||
|
setIsSearch(false); |
||||||
|
mapObject.setCenter(latlng); |
||||||
|
mapObject.setZoom(15); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerDrawType = val => { |
||||||
|
dispatch(drawTypeChangeAction(val)); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerInitCoordinates = () => { |
||||||
|
const init = initFlightBas.initDetail.areaList.concat(); |
||||||
|
dispatch(AREA_COORDINATE_LIST_SAVE(init)); |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerCoordinates = areaInfo => { |
||||||
|
const initAreaList = initFlightBas.initDetail.areaList.concat(); |
||||||
|
const coordList = []; |
||||||
|
|
||||||
|
areaInfo.coordinates.forEach((c, idx) => { |
||||||
|
const coord = Object.assign({}, initFlightBas['coord']); |
||||||
|
coord.lat = c.lat; |
||||||
|
coord.lon = c.lon; |
||||||
|
|
||||||
|
coordList.push(coord); |
||||||
|
}); |
||||||
|
|
||||||
|
const areaList = initAreaList.map((area, idx) => { |
||||||
|
return { |
||||||
|
...area, |
||||||
|
bufferZone: areaInfo.bufferZone, |
||||||
|
areaType: areaInfo.areaType, |
||||||
|
coordList: coordList |
||||||
|
}; |
||||||
|
}); |
||||||
|
|
||||||
|
if (areaInfo.areaType === 'LINE' || areaInfo.areaType === 'POLYGON') { |
||||||
|
dispatch(FLIGHT_PLAN_AREA_BUFFER_LIST.request(areaList)); |
||||||
|
} else { |
||||||
|
setMapAreaCoordList(areaList); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handlerConfirm = areaList => { |
||||||
|
if (areaList === undefined) { |
||||||
|
alert('영역을 설정해 주세요.'); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
dispatch(AREA_COORDINATE_LIST_SAVE(areaList)); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Card className='mb-0'> |
||||||
|
<CardBody> |
||||||
|
<div style={{ position: 'relative' }}> |
||||||
|
<div |
||||||
|
id='detail' |
||||||
|
ref={mapContainer} |
||||||
|
style={{ width: '100%', height: '70vh' }} |
||||||
|
> |
||||||
|
{isMapLoad && mapObject ? ( |
||||||
|
<LanncDraw |
||||||
|
mapObject={mapObject} |
||||||
|
mapboxgl={mapboxgl} |
||||||
|
mode={mode} |
||||||
|
areaCoordList={mapAreaCoordList} |
||||||
|
handlerCoordinates={handlerCoordinates} |
||||||
|
handlerInitCoordinates={handlerInitCoordinates} |
||||||
|
handlerConfirm={handlerConfirm} |
||||||
|
geojson={geojson} |
||||||
|
handlerDrawType={handlerDrawType} |
||||||
|
/> |
||||||
|
) : null} |
||||||
|
|
||||||
|
<div className='d-flex search-comp absolute'> |
||||||
|
<div className=''> |
||||||
|
<InputGroup className='search-feather'> |
||||||
|
<InputGroupAddon addonType='prepend'> |
||||||
|
<InputGroupText> |
||||||
|
<Search bssize={14} onClick={handlerSearch} /> |
||||||
|
</InputGroupText> |
||||||
|
</InputGroupAddon> |
||||||
|
<Input |
||||||
|
type='text' |
||||||
|
id='searchInput' |
||||||
|
name='searchInput' |
||||||
|
bssize='sm' |
||||||
|
autoComplete='off' |
||||||
|
placeholder='검색명을 입력하세요.' |
||||||
|
onChange={handlerChange} |
||||||
|
onKeyPress={handlerEnter} |
||||||
|
/> |
||||||
|
</InputGroup> |
||||||
|
<div className='search-result-comp'> |
||||||
|
<ul> |
||||||
|
{searchRes?.length !== 0 && isSearch ? ( |
||||||
|
searchRes?.map(search => { |
||||||
|
const title = search.title |
||||||
|
.replaceAll('<b>', '') |
||||||
|
.replaceAll('</b>', ''); |
||||||
|
|
||||||
|
return ( |
||||||
|
<li |
||||||
|
key={search.mapx + search.mapy} |
||||||
|
onClick={() => |
||||||
|
handlerCoord(search.mapx, search.mapy) |
||||||
|
} |
||||||
|
> |
||||||
|
<a> |
||||||
|
<div className='search-result'> |
||||||
|
<div className='title'> |
||||||
|
<span> |
||||||
|
<strong>{title}</strong> |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
<div className='address'> |
||||||
|
<span>{search.roadAddress}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a> |
||||||
|
</li> |
||||||
|
); |
||||||
|
}) |
||||||
|
) : ( |
||||||
|
<></> |
||||||
|
)} |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</CardBody> |
||||||
|
</Card> |
||||||
|
); |
||||||
|
} |
Loading…
Reference in new issue