diff --git a/package.json b/package.json index f15f57f9..bc99e88d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@fullcalendar/timegrid": "5.7.2", "@fullcalendar/timeline": "5.7.2", "@hookform/resolvers": "1.3.4", + "@mapbox/mapbox-gl-draw": "^1.4.2", "@mapbox/mapbox-gl-language": "1.0.1", "@stomp/stompjs": "^6.1.0", "@turf/buffer": "^6.5.0", @@ -62,7 +63,7 @@ "nouislider-react": "3.3.8", "postcss-rtl": "1.5.0", "prismjs": "1.19.0", - "proj4": "^2.8.0", + "proj4": "^2.9.0", "prop-types": "15.7.2", "react": "17.0.1", "react-apexcharts": "1.3.6", diff --git a/src/components/basis/flight/plan/FlightPlanAreaMapBox.js b/src/components/basis/flight/plan/FlightPlanAreaMapBox.js new file mode 100644 index 00000000..88f33c80 --- /dev/null +++ b/src/components/basis/flight/plan/FlightPlanAreaMapBox.js @@ -0,0 +1,395 @@ +import { useEffect, useRef, useState } from 'react'; +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 * as turf from '@turf/turf'; +import proj4 from 'proj4/dist/proj4'; +import { + Card, + CardBody, + Button, + Input, + InputGroup, + InputGroupAddon, + InputGroupText, + Modal, + ModalHeader, + ModalBody, + ModalFooter +} from 'reactstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import { initFlightBas } from '../../../../modules/basis/flight/models/basisFlightModel'; +import { AREA_DETAIL_INIT } from '../../../../modules/basis/flight/actions/basisFlightAction'; +import { flightPlanAPI } from '../../../../modules/basis/flight/apis/basisFlightApi'; +import { Search } from 'react-feather'; +import { FeatureAirZone } from '../../../map/mapbox/feature/FeatureAirZone'; +import { drawTypeChangeAction } from '../../../../modules/control/map/actions/controlMapActions'; +import { MapBoxDraw } from '../../../map/mapbox/draw/MapBoxDraw'; + +export const FlightPlanAreaMapBox = props => { + const dispatch = useDispatch(); + const mapControl = useSelector(state => state.controlMapReducer); + const { areaCoordList } = useSelector(state => state.flightState); + + const mapContainer = useRef(null); + const [mapObject, setMapObject] = useState(); + 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 [dragSize, setDragSize] = useState(70); + const [pastDragCircle, setDragCircle] = useState([]); + + const [number, setNumber] = useState(0); + + const [formModal, setFormModal] = useState(false); + + useEffect(() => { + mapBoxMapInit(); + + return () => { + dispatch(AREA_DETAIL_INIT()); + }; + }, []); + + const mapBoxMapInit = () => { + const bufferZoom = {}; + if (areaCoordList) { + if ( + areaCoordList[0].bufferZone >= 0 && + areaCoordList[0].bufferZone < 2000 + ) { + bufferZoom.bufferzoom = 13; + } else if ( + areaCoordList[0].bufferZone >= 2000 && + areaCoordList[0].bufferZone < 5000 + ) { + bufferZoom.bufferzoom = 12; + } else if ( + areaCoordList[0].bufferZone >= 5000 && + areaCoordList[0].bufferZone <= 9000 + ) { + bufferZoom.bufferzoom = 11; + } else { + bufferZoom.bufferzoom = 10; + } + } + + mapboxgl.accessToken = MAPBOX_TOKEN; + + const map = new mapboxgl.Map({ + container: 'map', // container ID + style: 'mapbox://styles/mapbox/streets-v12', // style URL + center: [126.612647, 37.519893], // starting position [lng, lat] + zoom: !areaCoordList ? 11 : bufferZoom.bufferzoom, // starting zoom + antialias: true + }); + + const language = new MapboxLanguage(); + map.addControl(language); + + map.on('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', { + type: 'raster-dem', + url: 'mapbox://mapbox.mapbox-terrain-dem-v1', + tileSize: 512, + maxZoom: 16 + }); + map.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 }); + map.addLayer({ + id: 'contour-labels', + type: 'symbol', + source: { + type: 'vector', + url: 'mapbox://mapbox.mapbox-terrain-v2' + }, + 'source-layer': 'contour', + layout: { + visibility: 'visible', + 'symbol-placement': 'line', + 'text-field': ['concat', ['to-string', ['get', 'ele']], 'm'] + }, + paint: { + 'icon-color': '#877b59', + 'icon-halo-width': 1, + 'text-color': '#877b59', + 'text-halo-width': 1 + } + }); + map.addLayer({ + id: 'sky', + type: 'sky', + paint: { + 'sky-type': 'atmosphere', + 'sky-atmosphere-sun': [0.0, 90.0], + 'sky-atmosphere-sun-intensity': 15 + } + }); + // 지형 3d end + // 등고선 start + map.addLayer({ + id: 'contours', + type: 'line', + source: { + type: 'vector', + url: 'mapbox://mapbox.mapbox-terrain-v2' + }, + 'source-layer': 'contour', + layout: { + visibility: 'visible', + 'line-join': 'round', + 'line-cap': 'round' + }, + paint: { + 'line-color': '#877b59', + 'line-width': 1 + } + }); + // 등고선 end + // 3d building + map.addLayer( + { + 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 + } + }, + labelLayerId + ); + // 3d building + setMapObject(map); + setIsMapLoad(true); + }); + }; + + 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 handler = () => { + setFormModal(!formModal); + }; + + const handlerDrawType = val => { + dispatch(drawTypeChangeAction(val)); + }; + + return ( + + +
+
+
+

지도 영역

+
+
+
+ +
+
+ {/* 여기에 draw 하는 파일 들어와야 함 */} + {mapObject ? ( + + ) : null} +
+
+ + + + + + + + +
+
    + {searchRes?.length !== 0 && isSearch ? ( + searchRes?.map(search => { + const title = search.title + .replaceAll('', '') + .replaceAll('', ''); + + return ( +
  • + handlerCoord(search.mapx, search.mapy) + } + > + +
    +
    + + {title} + +
    +
    + {search.roadAddress} +
    +
    +
    +
  • + ); + }) + ) : ( + <> + )} +
+
+
+
+
+
+ + {isMapLoad ? ( + + ) : null} + +
+ handlerDrawType('LINE')} + disabled={props.isDisabled || props.isDone} + > + WayPoint + + handlerDrawType('CIRCLE')} + disabled={props.isDisabled || props.isDone} + > + Circle + + handlerDrawType('POLYGON')} + disabled={props.isDisabled || props.isDone} + > + Polygon + + handlerDrawType('RESET')} + disabled={props.isDisabled || props.isDone} + > + 초기화 + + {areaCoordList ? ( + areaCoordList[0].coordList[0].lat ? ( + + 날씨 + + ) : null + ) : ( + <> + )} +
+
+
+ ); +}; diff --git a/src/components/map/mapbox/draw/MapBoxDraw.js b/src/components/map/mapbox/draw/MapBoxDraw.js new file mode 100644 index 00000000..84ca6ab0 --- /dev/null +++ b/src/components/map/mapbox/draw/MapBoxDraw.js @@ -0,0 +1,183 @@ +import { useDispatch, useSelector } from 'react-redux'; +import { InfoModal } from '../../../modal/InfoModal'; +import * as turf from '@turf/turf'; +import { useEffect, useState } from 'react'; +import MapboxDraw from '@mapbox/mapbox-gl-draw'; + +export const MapBoxDraw = props => { + const dispatch = useDispatch(); + const mapControl = useSelector(state => state.controlMapReducer); + const mapboxgl = props.mapboxgl; + const mapObject = props.mapObject; + const isDone = props.isDone; + const isDisabled = props.isDisabled; + + const [pastPolyline, setPastPolyline] = useState(); + const [pastBuffer, setBuffer] = useState(); + const [pastPolygon, setPastPolygon] = useState(); + const [pastCircle, setCircle] = useState([]); + + const pastDragCircle = props.pastDragCircle; + const setDragCircle = props.setDragCircle; + + const [pastClickEvent, setPastClickEvent] = useState(); + const [pastMarker, setPastMarker] = useState([]); + + const [figure, setFigure] = useState(); + + const [areaDetail, setAreaDetail] = useState(); + + const [alertModal, setAlertModal] = useState({ + isOpen: false, + title: '', + desc: '' + }); + + const [radiusCircle, setRadiusCircle] = useState(); + + let mode = props.mode; + + let areaInfo; + let lastDistance; + + let polyline; + let guideline; + let bufferPolygon; + + let polygon; + + let circle; + let radiusline; + + let event = { + clickEvent: '', + mousedownEvent: '', + rightClickEvent: '' + }; + + let dragCircle = []; + let dragCircleEvent = []; + + let distanceMarker = []; + + useEffect(() => { + setRadiusCircle(props.dragSize); + }, [props.dragSize]); + + useEffect(() => { + drawInit(); + }, [mapControl.drawType]); + + const drawInit = () => { + const drawType = mapControl.drawType; + handlerButtonClick(drawType); + }; + + //그리기 타입 선택 + const handlerButtonClick = newMode => { + handlerClearMode(mode); + + if (mode === newMode) { + mode = null; + return; + } + handlerStartMode(newMode); + }; + + const handlerClearMode = () => { + // if (pastPolyline) { + // pastP + // } + }; + + const handlerStartMode = mode => { + if (!mode) return; + + if (pastClickEvent) { + mapObject.removeEventListener(pastClickEvent); + } + + // event.clickEvent = mapObject.addEventListener('click', function (e) { + // if (mode === 'LINE') { + // onClickPolyline(e); + // } + // else if (mode === 'POLYGON') { + // onClickPolygon(e); + // } else if (mode === 'CIRCLE') { + // onClickCircle(e); + // } + // }); + if (mode === 'CIRCLE') setPastClickEvent(event.clickEvent); + }; + + const removeListener = () => { + mapObject.removeEventListener(event.clickEvent); + mapObject.removeEventListener(pastClickEvent); + setPastClickEvent(); + + mapObject.removeEventListener(event.mousedownEvent); + mapObject.removeEventListener(event.rightClickEvent); + if (!circle) $(document).off('mousemove.measure'); + }; + + // const handlerFinishDraw = () => { + // removeListener(); + + // if(polyline) { + // if(guideline) { + // guideline + // } + // } + // } + + const onClickPolyline = e => { + const coord = e.coord; + + console.log(coord, '>>>coord'); + }; + + const addMileStone = (coord, text) => { + const content = + '
' + + text + + '
'; + let midPoint; + let anchor; + + if (text === 'Start') { + midPoint = coord; + // anchor = new naver.maps.Point(45, 35); + // anchor = turf.point(45, 35); + anchor = [45, 35]; + } else { + const dis1 = coord[coord.length - 2]; + const dis2 = coord[coord.length - 1]; + if (circle) { + midPoint = coord; + } else { + midPoint = turf.point((dis1.y + dis2.y) / 2, (dis1.x + dis2.x) / 2); + } + // anchor = turf.point(20, 35); + anchor = [20, 35]; + } + + const popup = new props.mapboxgl.Popup({ + offset: anchor, + closeButton: false, + closeOnClick: false, + closeOnMove: false + }) + .setLngLat(midPoint) + .setHTML(content) + .addTo(props.map); + }; + + //거리재기 + const fromMetersToText = meters => { + meters = meters || 0; + const text = parseFloat(meters.toFixed(1)) + 'm'; + return text; + }; + + return ; +}; diff --git a/src/components/map/naver/draw/FlightPlanDraw.js b/src/components/map/naver/draw/FlightPlanDraw.js index 68a30c95..d3043fda 100644 --- a/src/components/map/naver/draw/FlightPlanDraw.js +++ b/src/components/map/naver/draw/FlightPlanDraw.js @@ -90,15 +90,8 @@ export const FlightPlanDraw_init = props => { }; const drawInit = () => { - if (mapControl.drawType === 'LINE') { - onClickButton('LINE'); - } else if (mapControl.drawType === 'CIRCLE') { - onClickButton('CIRCLE'); - } else if (mapControl.drawType === 'POLYGON') { - onClickButton('POLYGON'); - } else if (mapControl.drawType === 'RESET') { - onClickReset('RESET'); - } + const drawType = mapControl.drawType; + onClickButton(drawType); }; const onClickButton = newMode => { @@ -109,8 +102,6 @@ export const FlightPlanDraw_init = props => { return; } - // mode = newMode; - startMode(newMode); }; @@ -153,22 +144,17 @@ export const FlightPlanDraw_init = props => { if (pastClickEve) { naver.maps.Event.removeListener(pastClickEve); } - if (mode === 'LINE') { - Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) { + + Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) { + if (mode === 'LINE') { onClickPolyline(e); - }); - setClickEve(Eve.clickEve); - } else if (mode === 'POLYGON') { - Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) { + } else if (mode === 'POLYGON') { onClickPolygon(e); - }); - setClickEve(Eve.clickEve); - } else if (mode === 'CIRCLE') { - Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) { + } else if (mode === 'CIRCLE') { onClickCircle(e); - }); - setClickEve(Eve.clickEve); - } + } + }); + if (mode === 'CIRCLE') setClickEve(Eve.clickEve); }; const removeListener = () => { @@ -559,12 +545,6 @@ export const FlightPlanDraw_init = props => { props.handleCoordinates(areaInfo); }; - const onClickReset = () => { - if (mapControl.drawType === 'RESET') { - clearMode('RESET'); - } - }; - const createMarker = data => { distanceMarker.push( new naver.maps.Marker({ @@ -899,6 +879,8 @@ export const FlightPlanDraw_init = props => { if (circle) { midPoint = coord; } else { + console.log(dis1.y + dis2.y, 'y더한 값'); + console.log((dis1.y + dis2.y) / 2, 'y나눈 값'); midPoint = new naver.maps.LatLng( (dis1.y + dis2.y) / 2, (dis1.x + dis2.x) / 2 diff --git a/src/containers/basis/flight/plan/FlightPlanAreaContainer.js b/src/containers/basis/flight/plan/FlightPlanAreaContainer.js index 014932de..f52705d0 100644 --- a/src/containers/basis/flight/plan/FlightPlanAreaContainer.js +++ b/src/containers/basis/flight/plan/FlightPlanAreaContainer.js @@ -5,6 +5,7 @@ import * as Actions from '../../../../modules/basis/flight/actions/basisFlightAc import FlightPlanAreaMap from '../../../../components/basis/flight/plan/FlightPlanAreaMap'; import { drawTypeChangeAction } from '../../../../modules/control/map/actions/controlMapActions'; import FlightPlanAreaDetailContainer from './FlightPlanAreaDetailContainer'; +import { FlightPlanAreaMapBox } from '../../../../components/basis/flight/plan/FlightPlanAreaMapBox'; const FlightPlanAreaContainer = ({ handleModal, isDone, isDisabled }) => { const dispatch = useDispatch(); @@ -44,7 +45,12 @@ const FlightPlanAreaContainer = ({ handleModal, isDone, isDisabled }) => { isDone={isDone} isDisabled={isDisabled} /> - ) : null} + ) : // + null}