Browse Source

laanc draw기능 연결중

pull/2/head
junh_eee(이준희) 1 year ago
parent
commit
994ef0c054
  1. 679
      src/components/map/mapbox/draw/LanncDraw.js
  2. 1
      src/containers/basis/flight/laanc/LaancPlanContainer.js
  3. 22
      src/containers/basis/flight/plan/FlightPlanContainer.js
  4. 302
      src/views/laanc/FlightArea.js
  5. 29
      src/views/laanc/FlightPlan.js
  6. 255
      src/views/laanc/LaancAreaMap.js

679
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 (
'<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>'
);
};
// 미터 반환(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 <InfoModal modal={alertModal} setModal={setAlertModal} />;
};

1
src/containers/basis/flight/laanc/LaancPlanContainer.js

@ -109,7 +109,6 @@ const LaancPlanContainer = () => {
}
};
console.log('>>>>', detailData);
// 스텝 1 다음 버튼 이벤트
const handlerNext = () => {
const reg_email =

22
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 클래스로 제어 */}
<Col className='validation on' md='12'>
<p>
<AlertTriangle />기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다. 기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다.
<AlertTriangle />
기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다.
기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다.
기체보험가입이 되어있지 않거나 유효기간이 만료되었습니다.
</p>
</Col>
</Row>
@ -559,7 +567,10 @@ const FlightPlanContainer = () => {
<div className='alert-body'>
<AlertCircle size={15} />{' '}
<span className='ms-1'>사전 결과 승인 대상입니다.</span>
<span className='cancel-txt'>관제권 비행금지 공역을 제외한 지역에서는 주간에 150m미만의 고도에서는 비행승인없이 비행가능합니다. </span>
<span className='cancel-txt'>
관제권 비행금지 공역을 제외한 지역에서는 주간에
150m미만의 고도에서는 비행승인없이 비행가능합니다.{' '}
</span>
</div>
</Alert>
</div>
@ -813,7 +824,7 @@ const FlightPlanContainer = () => {
<div className='pal-card-box'>
<Row>
{/* <Col sm='12' lg='12'>
<Col sm='12' lg='12'>
{user ? (
<>
<FlightPlanGroupGrid
@ -834,7 +845,8 @@ const FlightPlanContainer = () => {
) : (
<></>
)}
</Col> */}
</Col>
<Col sm='12' lg='12'>
{/* {selectGroup.cstmrSno !== 0 ? ( */}
{listSelect.cstmrSno !== 0 ? (

302
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 (
<div className='vertically-centered-modal'>
<div>
<div
id='preview'
ref={mapContainer}
style={{ width: '100%', height: '35vh' }}
></div>
</div>
<Modal
isOpen={centeredModal}
toggle={() => setCenteredModal(!centeredModal)}
@ -23,21 +287,40 @@ const FlightArea = ({ centeredModal, setCenteredModal }) => {
<ModalHeader toggle={() => setCenteredModal(!centeredModal)}>
비행 구역 설정
</ModalHeader>
<ModalBody>지도영역 지도 height 정해주기</ModalBody>
<ModalBody>
<LaancAreaMap
handlerMapInit={handlerMapInit}
centeredModal={centeredModal}
mapObject={mapObject}
/>
</ModalBody>
<ModalFooter>
<div className='laanc-map-btn'>
<div className='laanc-point'>
<Button color='primary'>버튼1</Button>
<Button color='primary'>버튼2</Button>
<Button color='primary'>버튼3</Button>
<Button color='primary'>버튼4</Button>
<Button color='primary' onClick={() => handlerDrawType('LINE')}>
WayPoint
</Button>
<Button color='primary' onClick={() => handlerDrawType('CIRCLE')}>
Circle
</Button>
<Button
color='primary'
onClick={() => handlerDrawType('POLYGON')}
>
Polygon
</Button>
<Button color='primary' onClick={() => handlerDrawType('RESET')}>
초기화
</Button>
</div>
<div>
<Button
color='primary'
onClick={() => setCenteredModal(!centeredModal)}
// onClick={() => setCenteredModal(!centeredModal)}
onClick={handlerSave}
>
닫기
{/* 닫기 */}
저장
</Button>
</div>
</div>
@ -45,5 +328,4 @@ const FlightArea = ({ centeredModal, setCenteredModal }) => {
</Modal>
</div>
);
};
export default FlightArea;
}

29
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 = ({
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input type='select' name='select' size='sm' id='test'>
<Input type='select' name='select' bssize='sm' id='test'>
<option value=''>= 선택 =</option>
<option>상업</option>
<option>비상업</option>
@ -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 = ({
</Col>
<Col className='list-input' md='12'>
<div className='ti'>
비행 구역 정보<Button size='sm'>적용</Button>
비행 구역 정보<Button bssize='sm'>적용</Button>
</div>
<Row>
<Col className='list-input' md='6'>
@ -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 = ({
<Input
type='text'
id='test'
size='sm'
bssize='sm'
placeholder='직접입력 선택 후 활성화'
disabled
/>
@ -313,7 +314,7 @@ const FlightPlan = ({
<Label for='test'>
<span className='necessary'>*</span>
</Label>
<Input type='text' id='test' size='sm' placeholder='' />
<Input type='text' id='test' bssize='sm' placeholder='' />
</FormGroup>
</Col>
<Col className='list-input' md='4'>
@ -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 = ({
</div>
<div>
<div className='ti'>
비행 구역 설정
<Button size='sm' onClick={() => setCenteredModal(!centeredModal)}>
<Button
bssize='sm'
onClick={() => setCenteredModal(!centeredModal)}
>
자세히보기
</Button>
</div>

255
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 (
<Card className='mb-0'>
<CardBody>
<div style={{ position: 'relative' }}>
<div
id='detail'
ref={mapContainer}
style={{ width: '100%', height: '70vh' }}
>
{isMapLoad && mapObject ? (
<LanncDraw
mapObject={mapObject}
mapboxgl={mapboxgl}
mode={mode}
areaCoordList={mapAreaCoordList}
handlerCoordinates={handlerCoordinates}
handlerInitCoordinates={handlerInitCoordinates}
handlerConfirm={handlerConfirm}
geojson={geojson}
handlerDrawType={handlerDrawType}
/>
) : null}
<div className='d-flex search-comp absolute'>
<div className=''>
<InputGroup className='search-feather'>
<InputGroupAddon addonType='prepend'>
<InputGroupText>
<Search bssize={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>
</CardBody>
</Card>
);
}
Loading…
Cancel
Save