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