junh_eee(이준희)
1 year ago
5 changed files with 599 additions and 32 deletions
@ -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> |
||||||
|
); |
||||||
|
}; |
@ -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} />; |
||||||
|
}; |
Loading…
Reference in new issue