Browse Source

mapbox 비행계획서 지도 적용

pull/2/head
junh_eee(이준희) 1 year ago
parent
commit
963b561e6c
  1. 3
      package.json
  2. 395
      src/components/basis/flight/plan/FlightPlanAreaMapBox.js
  3. 183
      src/components/map/mapbox/draw/MapBoxDraw.js
  4. 42
      src/components/map/naver/draw/FlightPlanDraw.js
  5. 8
      src/containers/basis/flight/plan/FlightPlanAreaContainer.js

3
package.json

@ -13,6 +13,7 @@
"@fullcalendar/timegrid": "5.7.2", "@fullcalendar/timegrid": "5.7.2",
"@fullcalendar/timeline": "5.7.2", "@fullcalendar/timeline": "5.7.2",
"@hookform/resolvers": "1.3.4", "@hookform/resolvers": "1.3.4",
"@mapbox/mapbox-gl-draw": "^1.4.2",
"@mapbox/mapbox-gl-language": "1.0.1", "@mapbox/mapbox-gl-language": "1.0.1",
"@stomp/stompjs": "^6.1.0", "@stomp/stompjs": "^6.1.0",
"@turf/buffer": "^6.5.0", "@turf/buffer": "^6.5.0",
@ -62,7 +63,7 @@
"nouislider-react": "3.3.8", "nouislider-react": "3.3.8",
"postcss-rtl": "1.5.0", "postcss-rtl": "1.5.0",
"prismjs": "1.19.0", "prismjs": "1.19.0",
"proj4": "^2.8.0", "proj4": "^2.9.0",
"prop-types": "15.7.2", "prop-types": "15.7.2",
"react": "17.0.1", "react": "17.0.1",
"react-apexcharts": "1.3.6", "react-apexcharts": "1.3.6",

395
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 (
<Card className='mb-0'>
<CardBody>
<div className='search-cont search-info pd-0'>
<div className='cont-ti mb-1 d-flex justify-content-between align-items-sm-center align-items-start flex-sm-row'>
<div>
<h4>지도 영역</h4>
</div>
</div>
</div>
<div style={{ position: 'relative' }}>
<div
id='map'
ref={mapContainer}
style={{ width: '100%', height: '60vh' }}
>
{/* 여기에 draw 하는 파일 들어와야 함 */}
{mapObject ? (
<MapBoxDraw
mapObject={mapObject}
mapboxgl={mapboxgl}
mode={mode}
isDone={props.isDone}
isDisabled={props.isDisabled}
dragSize={dragSize}
pastDragCircle={pastDragCircle}
setDragCircle={setDragCircle}
/>
) : null}
<div className='d-flex search-comp'>
<div className=''>
<InputGroup className='search-feather'>
<InputGroupAddon addonType='prepend'>
<InputGroupText>
<Search size={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>
{isMapLoad ? (
<FeatureAirZone map={mapObject} mapboxgl={mapboxgl} />
) : null}
<div className='d-flex align-items-center mt-2'>
<Button.Ripple
className='mr-1'
color='primary'
onClick={e => handlerDrawType('LINE')}
disabled={props.isDisabled || props.isDone}
>
WayPoint
</Button.Ripple>
<Button.Ripple
className='mr-1'
color='primary'
onClick={e => handlerDrawType('CIRCLE')}
disabled={props.isDisabled || props.isDone}
>
Circle
</Button.Ripple>
<Button.Ripple
className='mr-1'
color='primary'
onClick={e => handlerDrawType('POLYGON')}
disabled={props.isDisabled || props.isDone}
>
Polygon
</Button.Ripple>
<Button.Ripple
color='primary'
className='mr-1'
onClick={e => handlerDrawType('RESET')}
disabled={props.isDisabled || props.isDone}
>
초기화
</Button.Ripple>
{areaCoordList ? (
areaCoordList[0].coordList[0].lat ? (
<Button.Ripple color='primary' onClick={handler}>
날씨
</Button.Ripple>
) : null
) : (
<></>
)}
</div>
</CardBody>
</Card>
);
};

183
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 =
'<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>';
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 <InfoModal modal={alertModal} setModal={setAlertModal} />;
};

42
src/components/map/naver/draw/FlightPlanDraw.js

@ -90,15 +90,8 @@ export const FlightPlanDraw_init = props => {
}; };
const drawInit = () => { const drawInit = () => {
if (mapControl.drawType === 'LINE') { const drawType = mapControl.drawType;
onClickButton('LINE'); onClickButton(drawType);
} else if (mapControl.drawType === 'CIRCLE') {
onClickButton('CIRCLE');
} else if (mapControl.drawType === 'POLYGON') {
onClickButton('POLYGON');
} else if (mapControl.drawType === 'RESET') {
onClickReset('RESET');
}
}; };
const onClickButton = newMode => { const onClickButton = newMode => {
@ -109,8 +102,6 @@ export const FlightPlanDraw_init = props => {
return; return;
} }
// mode = newMode;
startMode(newMode); startMode(newMode);
}; };
@ -153,22 +144,17 @@ export const FlightPlanDraw_init = props => {
if (pastClickEve) { if (pastClickEve) {
naver.maps.Event.removeListener(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); onClickPolyline(e);
}); } else if (mode === 'POLYGON') {
setClickEve(Eve.clickEve);
} else if (mode === 'POLYGON') {
Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) {
onClickPolygon(e); onClickPolygon(e);
}); } else if (mode === 'CIRCLE') {
setClickEve(Eve.clickEve);
} else if (mode === 'CIRCLE') {
Eve.clickEve = naver.maps.Event.addListener(map, 'click', function (e) {
onClickCircle(e); onClickCircle(e);
}); }
setClickEve(Eve.clickEve); });
} if (mode === 'CIRCLE') setClickEve(Eve.clickEve);
}; };
const removeListener = () => { const removeListener = () => {
@ -559,12 +545,6 @@ export const FlightPlanDraw_init = props => {
props.handleCoordinates(areaInfo); props.handleCoordinates(areaInfo);
}; };
const onClickReset = () => {
if (mapControl.drawType === 'RESET') {
clearMode('RESET');
}
};
const createMarker = data => { const createMarker = data => {
distanceMarker.push( distanceMarker.push(
new naver.maps.Marker({ new naver.maps.Marker({
@ -899,6 +879,8 @@ export const FlightPlanDraw_init = props => {
if (circle) { if (circle) {
midPoint = coord; midPoint = coord;
} else { } else {
console.log(dis1.y + dis2.y, 'y더한 값');
console.log((dis1.y + dis2.y) / 2, 'y나눈 값');
midPoint = new naver.maps.LatLng( midPoint = new naver.maps.LatLng(
(dis1.y + dis2.y) / 2, (dis1.y + dis2.y) / 2,
(dis1.x + dis2.x) / 2 (dis1.x + dis2.x) / 2

8
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 FlightPlanAreaMap from '../../../../components/basis/flight/plan/FlightPlanAreaMap';
import { drawTypeChangeAction } from '../../../../modules/control/map/actions/controlMapActions'; import { drawTypeChangeAction } from '../../../../modules/control/map/actions/controlMapActions';
import FlightPlanAreaDetailContainer from './FlightPlanAreaDetailContainer'; import FlightPlanAreaDetailContainer from './FlightPlanAreaDetailContainer';
import { FlightPlanAreaMapBox } from '../../../../components/basis/flight/plan/FlightPlanAreaMapBox';
const FlightPlanAreaContainer = ({ handleModal, isDone, isDisabled }) => { const FlightPlanAreaContainer = ({ handleModal, isDone, isDisabled }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -44,7 +45,12 @@ const FlightPlanAreaContainer = ({ handleModal, isDone, isDisabled }) => {
isDone={isDone} isDone={isDone}
isDisabled={isDisabled} isDisabled={isDisabled}
/> />
) : null} ) : // <FlightPlanAreaMapBox
// handleConfirm={handleConfirm}
// isDone={isDone}
// isDisabled={isDisabled}
// />
null}
</Col> </Col>
<Col md={6} lg={6}> <Col md={6} lg={6}>
<FlightPlanAreaDetailContainer <FlightPlanAreaDetailContainer

Loading…
Cancel
Save