diff --git a/src/components/map/mapbox/draw/LanncDraw.js b/src/components/map/mapbox/draw/LanncDraw.js new file mode 100644 index 0000000..5c81fc0 --- /dev/null +++ b/src/components/map/mapbox/draw/LanncDraw.js @@ -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 ( + '
' + + text + + '
' + ); + }; + + // 미터 반환(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 ; +}; diff --git a/src/containers/basis/flight/laanc/LaancPlanContainer.js b/src/containers/basis/flight/laanc/LaancPlanContainer.js index ad7a914..5d2b309 100644 --- a/src/containers/basis/flight/laanc/LaancPlanContainer.js +++ b/src/containers/basis/flight/laanc/LaancPlanContainer.js @@ -109,7 +109,6 @@ const LaancPlanContainer = () => { } }; - console.log('>>>>', detailData); // 스텝 1 다음 버튼 이벤트 const handlerNext = () => { const reg_email = diff --git a/src/containers/basis/flight/plan/FlightPlanContainer.js b/src/containers/basis/flight/plan/FlightPlanContainer.js index b9c244f..091241a 100644 --- a/src/containers/basis/flight/plan/FlightPlanContainer.js +++ b/src/containers/basis/flight/plan/FlightPlanContainer.js @@ -25,7 +25,12 @@ import { GROUP_LIST } from '../../../../modules/basis/group/actions/basisGroupAction'; import FlightPlanGroupGrid from '../../../../components/basis/flight/plan/FlightPlanGroupGrid'; -import { AlertCircle, CheckCircle, XCircle, AlertTriangle } from 'react-feather'; +import { + AlertCircle, + CheckCircle, + XCircle, + AlertTriangle +} from 'react-feather'; const initSearchData = { schFltStDt: moment() @@ -546,7 +551,10 @@ const FlightPlanContainer = () => { {/* validation은 on 클래스로 제어 */}

- 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. + + 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. + 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. + 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다.

@@ -559,7 +567,10 @@ const FlightPlanContainer = () => {
{' '} 사전 결과 미 승인 대상입니다. - 관제권 및 비행금지 공역을 제외한 지역에서는 주간에 150m미만의 고도에서는 비행승인없이 비행가능합니다. + + 관제권 및 비행금지 공역을 제외한 지역에서는 주간에 + 150m미만의 고도에서는 비행승인없이 비행가능합니다.{' '} +
@@ -813,7 +824,7 @@ const FlightPlanContainer = () => {
- {/* + {user ? ( <> { ) : ( <> )} - */} + + {/* {selectGroup.cstmrSno !== 0 ? ( */} {listSelect.cstmrSno !== 0 ? ( diff --git a/src/views/laanc/FlightArea.js b/src/views/laanc/FlightArea.js index 5a1c2f3..cbce9d0 100644 --- a/src/views/laanc/FlightArea.js +++ b/src/views/laanc/FlightArea.js @@ -1,4 +1,12 @@ +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, Row, Col, Button, @@ -11,10 +19,266 @@ import { Label, Input } from 'reactstrap'; +import { initFlightBas } from '../../modules/basis/flight/models/basisFlightModel'; +import { + AREA_DETAIL_INIT, + AREA_DETAIL_LIST_SAVE +} from '../../modules/basis/flight/actions/basisFlightAction'; +import { drawTypeChangeAction } from '../../modules/control/map/actions/controlMapActions'; +import LaancAreaMap from './LaancAreaMap'; + +export default function FlightArea({ centeredModal, setCenteredModal }) { + const dispatch = useDispatch(); + const mapControl = useSelector(state => state.controlMapReducer); + const { areaCoordList } = useSelector(state => state.flightState); + + const mapContainer = useRef(null); + const [mapObject, setMapObject] = useState([null, 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 buildingLayer = { + id: 'add-3d-buildings', + source: 'composite', + 'source-layer': 'building', + filter: ['==', 'extrude', 'true'], + type: 'fill-extrusion', + minzoom: 15, + paint: { + 'fill-extrusion-color': '#aaa', + + // Use an 'interpolate' expression to + // add a smooth transition effect to + // the buildings as the user zooms in. + 'fill-extrusion-height': [ + 'interpolate', + ['linear'], + ['zoom'], + 15, + 0, + 15.05, + ['get', 'height'] + ], + 'fill-extrusion-base': [ + 'interpolate', + ['linear'], + ['zoom'], + 15, + 0, + 15.05, + ['get', 'min_height'] + ], + 'fill-extrusion-opacity': 0.6 + } + }; + const terrainLayer = { + type: 'raster-dem', + url: 'mapbox://mapbox.mapbox-terrain-dem-v1' + // url: 'mapbox://mapbox.mapbox-street-v1', + // tileSize: 512, + // maxZoom: 16, + }; + const geojson = { + type: 'FeatureCollection', + features: [] + }; + + useEffect(() => { + // mapBoxMapInit(); + handlerMapInit('preview'); + + return () => { + dispatch(AREA_DETAIL_INIT()); + }; + }, []); + + const handlerDrawType = val => { + dispatch(drawTypeChangeAction(val)); + }; + + const handlerSave = async () => { + const areaDetail = areaCoordList; + const resultAreaDetail = areaDetail.map(area => { + return { + ...area, + coordList: areaDetail[0].coordList + }; + }); + + // const { data } = await axios.post( + // `api/bas/flight/airspace/contains`, + // resultAreaDetail + // ); + + // if (data.result) { + // setAlertModal({ + // isOpen: true, + // title: '우회 여부 확인', + // desc: '경로상에 비행 금지된 구역이 있습니다.\n우회하여 경로 설정해주시기 바랍니다.' + // }); + + // return false; + // } + + dispatch(AREA_DETAIL_LIST_SAVE(resultAreaDetail)); + setCenteredModal(false); + // handleModal({ type: 'area', isOpne: false }); + }; + + const handlerMapInit = mapType => { + mapboxgl.accessToken = MAPBOX_TOKEN; + + const map = new mapboxgl.Map({ + container: mapType, // container ID + style: 'mapbox://styles/mapbox/streets-v12', // style URL + center: [126.612647, 37.519893], // starting position [lng, lat] + // zoom: !areaCoordList ? 14 : bufferZoom.bufferzoom, // starting zoom + zoom: 15, + antialias: true, + attributionControl: false + }); + + const language = new MapboxLanguage(); + map.addControl(language); + + map.on('style.load', () => { + const layers = map.getStyle().layers; + + const labelLayerId = layers.find( + layer => layer.type === 'symbol' && layer.layout['text-field'] + ).id; + + // 지형 3d start + map.addSource('mapbox-dem', terrainLayer); + map.setTerrain({ source: 'mapbox-dem', exaggeration: 1 }); + map.addLayer(buildingLayer, labelLayerId); + + //김포 3d 공역 + // map.addLayer({ + // id: 'route', + // type: 'custom', + // renderingMode: '3d', + // onAdd: function () { + // for (let i = 0; i < gimPo.features.length; i++) { + // let line; + // const options = { + // path: gimPo.features[i].geometry.coordinates + // }; + // let lineGeometry = options.path; + // line = tb.line({ + // geometry: lineGeometry, + // width: gimPo.features[i].properties['stroke-width'], + // color: gimPo.features[i].properties.stroke + // }); + // tb.add(line); + // } + // }, + // render: function () { + // tb.update(); + // } + // }); + + //mapDraw layer + map.addSource('geojson', { + type: 'geojson', + data: geojson + }); + map.addLayer({ + id: 'waypoint', + type: 'circle', + source: 'geojson', + paint: { + 'circle-radius': 5, + 'circle-color': '#ffffff', + 'circle-stroke-color': '#000000', + 'circle-stroke-width': 1 + }, + filter: ['in', '$type', 'Point'] + }); + map.addLayer({ + id: 'guideline', + type: 'line', + source: 'geojson', + layout: { + 'line-cap': 'round', + 'line-join': 'round' + }, + paint: { + 'line-color': '#283046', + 'line-width': 2, + 'line-opacity': 0.5, + 'line-dasharray': [5, 5] + }, + filter: ['==', ['get', 'id'], 'guideline'] + }); + map.addLayer({ + id: 'polyline', + type: 'line', + source: 'geojson', + layout: { + 'line-cap': 'round', + 'line-join': 'round' + }, + paint: { + 'line-color': '#283046', + 'line-width': 2 + }, + filter: ['in', ['get', 'id'], ['literal', ['polyline', 'outline']]] + }); + map.addLayer({ + id: 'polygon', + type: 'fill', + source: 'geojson', + layout: {}, + paint: { + 'fill-color': '#8a1c05', + 'fill-opacity': 0.5, + 'fill-outline-color': '#000000' + }, + //polygon, circle에 사용 + filter: ['in', '$type', 'Polygon'] + }); + map.addLayer({ + id: 'buffer', + type: 'line', + source: 'geojson', + layout: { + 'line-cap': 'round', + 'line-join': 'round' + }, + paint: { + 'line-color': '#283046', + 'line-width': 1, + 'line-dasharray': [5, 5] + }, + filter: ['==', ['get', 'id'], 'buffer'] + }); + // setMapObject(map); + mapType === 'preview' + ? setMapObject(prev => [map, prev[1]]) + : setMapObject(prev => [prev[0], map]); + setIsMapLoad(true); + }); + }; -const FlightArea = ({ centeredModal, setCenteredModal }) => { return (
+
+
+
setCenteredModal(!centeredModal)} @@ -23,21 +287,40 @@ const FlightArea = ({ centeredModal, setCenteredModal }) => { setCenteredModal(!centeredModal)}> 비행 구역 설정 - 지도영역 지도 height 정해주기 + + +
- - - - + + + +
@@ -45,5 +328,4 @@ const FlightArea = ({ centeredModal, setCenteredModal }) => {
); -}; -export default FlightArea; +} diff --git a/src/views/laanc/FlightPlan.js b/src/views/laanc/FlightPlan.js index c40830b..d6ac496 100644 --- a/src/views/laanc/FlightPlan.js +++ b/src/views/laanc/FlightPlan.js @@ -40,6 +40,7 @@ import { } from '../../modules/control/map/actions/controlMapActions'; import { useHistory } from 'react-router-dom'; import FlightArea from './FlightArea'; + const FlightPlan = ({ handleChange, handlerNext, @@ -82,7 +83,7 @@ const FlightPlan = ({ type='text' id='memberName' name='memberName' - size='sm' + bssize='sm' placeholder='' value={user.memberName} disabled @@ -95,7 +96,7 @@ const FlightPlan = ({ - + @@ -172,7 +173,7 @@ const FlightPlan = ({ id='fltPurpose' name='fltPurpose' value={data.fltPurpose} - bsSize='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -201,7 +202,7 @@ const FlightPlan = ({
- 비행 구역 정보 + 비행 구역 정보
@@ -215,7 +216,7 @@ const FlightPlan = ({ name='fltElev' // defaultValue={data.email || ''} value={data.areaList[0].fltElev} - bsSize='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -240,7 +241,7 @@ const FlightPlan = ({ name='bufferZone' // defaultValue={data.email || ''} value={data.areaList[0].bufferZone} - bsSize='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -265,7 +266,7 @@ const FlightPlan = ({ name='fltMethod' // defaultValue={data.email || ''} value={data.areaList[0].fltMethod} - bsSize='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -297,7 +298,7 @@ const FlightPlan = ({ @@ -313,7 +314,7 @@ const FlightPlan = ({ - + @@ -326,7 +327,7 @@ const FlightPlan = ({ id='arcrftTypeCd' name='arcrftTypeCd' value={data.arcrftList[0].arcrftTypeCd} - size='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -355,7 +356,7 @@ const FlightPlan = ({ name='groupNm' // defaultValue={data.email || ''} value={data.arcrftList[0].groupNm} - bsSize='sm' + bssize='sm' onChange={e => { const { name, value } = e.target; handleChange({ @@ -375,8 +376,10 @@ const FlightPlan = ({
- 비행 구역 설정 -
diff --git a/src/views/laanc/LaancAreaMap.js b/src/views/laanc/LaancAreaMap.js new file mode 100644 index 0000000..7440052 --- /dev/null +++ b/src/views/laanc/LaancAreaMap.js @@ -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 ( + + +
+
+ {isMapLoad && mapObject ? ( + + ) : null} + +
+
+ + + + + + + + +
+
    + {searchRes?.length !== 0 && isSearch ? ( + searchRes?.map(search => { + const title = search.title + .replaceAll('', '') + .replaceAll('', ''); + + return ( +
  • + handlerCoord(search.mapx, search.mapy) + } + > + +
    +
    + + {title} + +
    +
    + {search.roadAddress} +
    +
    +
    +
  • + ); + }) + ) : ( + <> + )} +
+
+
+
+
+
+
+
+ ); +}