Merge branch 'iipbs/sn/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/wr/sttn
@@ -31,6 +31,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.3",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"axios": "1.8.1",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import './lib/css/gis.common.css';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@common/components/ui/tabs';
|
||||
import OlMap from 'ol/Map';
|
||||
import View from 'ol/View';
|
||||
import { defaults as defaultControls, OverviewMap } from 'ol/control';
|
||||
import { defaults } from 'ol/interaction';
|
||||
import * as olProj from 'ol/proj';
|
||||
import proj4 from 'proj4';
|
||||
import { register } from 'ol/proj/proj4';
|
||||
import GisApp from './lib/gis.core';
|
||||
import GisContent from './GisContent';
|
||||
import GisLonLat from './GisLonLat';
|
||||
import GisToolBar from './GisToolBar';
|
||||
import GisTab from './GisTab';
|
||||
import MapToggleBtn from './MapToggleBtn';
|
||||
import FeatureClickEventPopup from '@/map/featureClickEventPopup';
|
||||
|
||||
export const GisContext = createContext({gisCore: null, gisMap: null, treeData:null, checkedLayerIds:null});
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
|
||||
function Gis({ treeData = [], isSearch = false }) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
|
||||
const [featureData,setFeatureData] = useState(null) // 선택된 피쳐 데이터
|
||||
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
|
||||
const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []);
|
||||
const mapInstanceRef = useRef(null); // 맵 생성 여부를 추적할 ref
|
||||
const checkedLayerIds = useRef([]);
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null)
|
||||
};
|
||||
const handleFeatureSelect = (data) => {
|
||||
console.log('check',data)
|
||||
setFeatureData(data); // 선택된 피처 데이터 저장
|
||||
setIsOpen(true); // 팝업 열기
|
||||
};
|
||||
|
||||
// SearchModal이 열릴 때 featureClickEventPopup 닫기
|
||||
useEffect(() => {
|
||||
if (isSearchModalOpen && isOpen) {
|
||||
setIsOpen(false);
|
||||
setFeatureData(null);
|
||||
}
|
||||
}, [isSearchModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
|
||||
|
||||
// EPSG:5179 등록
|
||||
proj4.defs([
|
||||
[
|
||||
'EPSG:5179',
|
||||
'+proj=tmerc +lat_0=38 +lon_0=127.5 +k=0.9996 +x_0=1000000 +y_0=2000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
|
||||
],
|
||||
]);
|
||||
register(proj4);
|
||||
|
||||
//맵 생성
|
||||
const map = new OlMap({
|
||||
layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']],
|
||||
renderer: 'webgl',
|
||||
target: mapRef.current,
|
||||
interactions: defaults({ doubleClickZoom: false }),
|
||||
controls: defaultControls({
|
||||
zoom: false,
|
||||
rotate: false,
|
||||
attributionOptions: {
|
||||
collapsible: false,
|
||||
},
|
||||
}).extend([]),
|
||||
view: new View({
|
||||
projection: GisApp.TO_PROJ,
|
||||
zoom: gisCore.defaults.zoom,
|
||||
center: olProj.transform(
|
||||
[126.85306, 35.571634],
|
||||
GisApp.FROM_PROJ,
|
||||
GisApp.TO_PROJ
|
||||
),
|
||||
minZoom: gisCore.defaults.minZoom,
|
||||
maxZoom: gisCore.defaults.maxZoom,
|
||||
}),
|
||||
});
|
||||
|
||||
//미니맵
|
||||
const overviewMap = new OverviewMap({
|
||||
layers: [GisApp.Module.core.baseMiniMapLayers['base']],
|
||||
view: new View({
|
||||
projection: GisApp.TO_PROJ,
|
||||
minZoom: 9,
|
||||
}),
|
||||
collapsed: false,
|
||||
collapseLabel: '\u00BB',
|
||||
label: '\u00AB',
|
||||
});
|
||||
|
||||
map.addControl(overviewMap);
|
||||
gisCore.__proto__.map = map;
|
||||
|
||||
gisCore.scaleControl();
|
||||
gisCore.changeBaseLayer('base');
|
||||
setMapObject({ map: map });
|
||||
mapInstanceRef.current = map; // 생성 완료 플래그 설정
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
gisCore.deleteAllOverlay();
|
||||
gisCore.deleteAllLayer();
|
||||
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
|
||||
GisApp.layerCode = layerCode;
|
||||
|
||||
//setTreeData(data);
|
||||
const layerItems = getLayerItems(layerCode);
|
||||
//setEditLayerItems(prev => [...layerItems])
|
||||
}, [treeData]);
|
||||
|
||||
const getLayerItems= (layerCode) => {
|
||||
const layerItems = Object.entries(layerCode)
|
||||
.filter(([key, obj], idx) => obj?.updtPsblYn === 'Y')
|
||||
.map(([key, obj]) => ({
|
||||
...obj,
|
||||
name: obj.lyrNm,
|
||||
label: obj.lyrNm,
|
||||
value: obj.lyrSn + '',
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.sortOrdr > b.sortOrdr) return 1;
|
||||
if (a.sortOrdr < b.sortOrdr) return -1;
|
||||
return 0;
|
||||
});
|
||||
return layerItems;
|
||||
};
|
||||
|
||||
if (mapObject.map) {
|
||||
try {
|
||||
gisMap = mapObject.map;
|
||||
} catch (e) {
|
||||
console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider value={{gisCore, gisMap, treeData, checkedLayerIds}}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<GisTab visible={tabVisible} setVisible={setTabVisible} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} />
|
||||
<GisContent onFeatureSelect={handleFeatureSelect}/>
|
||||
{isOpen && featureData && (
|
||||
<FeatureClickEventPopup
|
||||
featureData={featureData}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
/>
|
||||
)}
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} />
|
||||
{/* <GisLonLat/>
|
||||
<GisToolBar/>
|
||||
<MapToggleBtn/> */}
|
||||
</div>
|
||||
</GisContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Gis);
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import GisApp from './lib/gis.core';
|
||||
import { useGisContext } from './Gis';
|
||||
import GisLonLat from './GisLonLat';
|
||||
import GisSelect from './GisSelect';
|
||||
import GisToolBar from './GisToolBar';
|
||||
import './lib/css/gis.edit.css';
|
||||
import MapToggleBtn from './MapToggleBtn';
|
||||
import GisEdit from './GisEdit';
|
||||
|
||||
export default function GisContent({ edit = false, onFeatureSelect }) {
|
||||
const { gisMap, gisCore } = useGisContext();
|
||||
const [visibleComponents, setVisibleComponents] = useState({});
|
||||
const addressResultLayer = useRef(null);
|
||||
const [playShow, setPlayShow] = useState({ show: false });
|
||||
|
||||
//셀렉트 버튼 이벤트
|
||||
// const clickFeature = useCallback(feature => {
|
||||
// if (feature) {
|
||||
// const layer = feature.getLayer();
|
||||
// setVisibleComponents(prevState => ({
|
||||
// ...prevState,
|
||||
// }));
|
||||
// }
|
||||
// }, []);
|
||||
|
||||
const selectRslt = useCallback(feature => {
|
||||
GisApp.Module.layer.highLightFeature(
|
||||
[feature],
|
||||
{
|
||||
id: GisApp.hlLayerId,
|
||||
zIndex: 10,
|
||||
opacity: 1,
|
||||
selectable: true,
|
||||
},
|
||||
{ width: 30 }
|
||||
);
|
||||
clickFeature(feature);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (gisMap) {
|
||||
setVisibleComponents(prevState => ({
|
||||
...prevState,
|
||||
}));
|
||||
}
|
||||
}, [gisMap]);
|
||||
|
||||
if (!gisMap) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{/*<GisTree />*/}
|
||||
<MapToggleBtn />
|
||||
<GisLonLat />
|
||||
<GisToolBar />
|
||||
<GisSelect onFeatureSelect={onFeatureSelect}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import GisApp from './lib/gis.core';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { GisContext, setTree } from './Gis';
|
||||
import { Button } from "@common/components";
|
||||
import { gAlert, gConfirm } from './lib/util';
|
||||
// import { fclApi } from '../../js/apis/fcl/fclApi';
|
||||
|
||||
export default React.memo(function GisEdit() {
|
||||
const { editLayerItems = [], siteSn, locgovCd, checkedLayerIds } = useContext(GisContext);
|
||||
const editModule = GisApp.Module.edit;
|
||||
const ftrIdn = useRef(null);
|
||||
const selectedLyrId = useRef(null);
|
||||
if (editLayerItems.length > 0 && !selectedLyrId.current) {
|
||||
selectedLyrId.current = editLayerItems[0].value;
|
||||
}
|
||||
const [btnEvt, setBtnEvt] = useState({ edit: false, modify: false, add: false, snap: false });
|
||||
const edit = () => {
|
||||
const edit = !btnEvt.edit;
|
||||
if (edit) {
|
||||
setBtnEvt({ ...btnEvt, edit: edit });
|
||||
} else {
|
||||
setBtnEvt({ edit: edit, modify: false, add: false, snap: false });
|
||||
editModule.clearUndoRedoFeature();
|
||||
}
|
||||
editModule.removeInteraction();
|
||||
};
|
||||
|
||||
const modify = () => {
|
||||
const modify = !btnEvt.modify;
|
||||
setBtnEvt({ ...btnEvt, ...{ add: false }, modify });
|
||||
if (modify) {
|
||||
editModule.setModify();
|
||||
} else {
|
||||
editModule.removeModify();
|
||||
}
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
let add = !btnEvt.add;
|
||||
if (add) {
|
||||
if (!checkedLayerIds?.current?.includes(selectedLyrId.current)) {
|
||||
alert('트리에서 레이어 체크 후 추가 가능합니다.');
|
||||
return;
|
||||
}
|
||||
const errMsg = editModule.setDraw(selectedLyrId.current);
|
||||
if (errMsg) {
|
||||
alert(errMsg);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
editModule.removeDraw();
|
||||
}
|
||||
setBtnEvt({ ...btnEvt, ...{ modify: false }, add });
|
||||
};
|
||||
|
||||
const del = () => {
|
||||
editModule.deleteFeature();
|
||||
};
|
||||
|
||||
const undo = () => {
|
||||
GisApp.Module.edit.undo();
|
||||
};
|
||||
|
||||
const redo = () => {
|
||||
GisApp.Module.edit.redo();
|
||||
};
|
||||
|
||||
const snap = () => {
|
||||
const snap = !btnEvt.snap;
|
||||
setBtnEvt({ ...btnEvt, snap });
|
||||
if (snap) {
|
||||
editModule.setSnap();
|
||||
} else {
|
||||
editModule.removeSnap();
|
||||
}
|
||||
};
|
||||
|
||||
const validateEditFeatures = editFeatures => {
|
||||
if (editFeatures.length === 0) {
|
||||
//gAlert('INFO', 'NO_CHANGED_DATA');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
const editFeatureInfo = editModule.getEditFeatureInfo();
|
||||
const editFeatures = Object.values(editFeatureInfo).map(value => ({
|
||||
siteSn,
|
||||
locgovCd,
|
||||
geom: value.geom,
|
||||
flag: value.flag,
|
||||
lyrId: value.lyr_id,
|
||||
}));
|
||||
if (!validateEditFeatures(editFeatures)) {
|
||||
return;
|
||||
}
|
||||
if (!gConfirm('CONFIRM', 'SAVE_QUEST')) return false;
|
||||
// const result = await fclApi.saveEditFeatures(editFeatures);
|
||||
// if (result.code === 200) {
|
||||
// editModule.clearUndoRedo();
|
||||
// }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
//setBtnEvt({ edit: false, modify: false, add: false, snap: false });
|
||||
editModule.setUndoInteraction();
|
||||
editModule.removeInteraction();
|
||||
}, [locgovCd]);
|
||||
if (editLayerItems.length === 0) return;
|
||||
return (
|
||||
<>
|
||||
<div className="edit_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', top: '28px', left: '120px', zIndex: '1' }}>
|
||||
<label htmlFor={'selectLyrId'}></label>
|
||||
<select
|
||||
id={'selectLyrId'}
|
||||
onChange={evt => {
|
||||
const lyrId = evt.target.value;
|
||||
selectedLyrId.current = lyrId + '';
|
||||
setBtnEvt({ ...btnEvt, ...{ add: false } });
|
||||
editModule.setInteractionActive(false);
|
||||
}}
|
||||
>
|
||||
{editLayerItems.map(layerItem => (
|
||||
<option value={layerItem?.lyrSn} key={layerItem?.lyrSn}>
|
||||
{layerItem?.lyrNm}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Button btn={{ type: `${btnEvt.edit ? 'primary' : 'secondary'}`, fnc: edit }}>{`${btnEvt.edit ? ' 편집종료' : '편집하기'}`}</Button>
|
||||
{btnEvt.edit && (
|
||||
<ul>
|
||||
<li>
|
||||
<div className={`btn_edit_slt${btnEvt.modify ? ' active' : ''}`} title="수정" onClick={modify}>
|
||||
<span>수정</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className={`btn_edit_add${btnEvt.add ? ' active' : ''}`} title="추가" onClick={add}>
|
||||
<span>추가</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_edit_del" title="삭제" onClick={del}>
|
||||
<span>삭제</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_edit_prev" title="이전" onClick={undo}>
|
||||
<span>이전</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_edit_next" title="다음" onClick={redo}>
|
||||
<span>다음</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className={`btn_edit_snap${btnEvt.snap ? ' active' : ''}`} title="스냅" onClick={snap}>
|
||||
<span>스냅</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_edit_save" title="저장" onClick={save}>
|
||||
<span>저장</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import GisApp from './lib/gis.core';
|
||||
import { MousePosition } from 'ol/control';
|
||||
import { useContext, useEffect, useRef } from 'react';
|
||||
import { GisContext } from './Gis';
|
||||
export default React.memo(function GisLonLat() {
|
||||
const { gisCore } = useContext(GisContext);
|
||||
const labelLonLatRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!gisCore?.map || !labelLonLatRef.current) return;
|
||||
|
||||
// 기존 MousePosition 제거를 위한 컨트롤 추적
|
||||
let existingMouseControls = gisCore.map
|
||||
.getControls()
|
||||
.getArray()
|
||||
.filter(control => control instanceof MousePosition);
|
||||
|
||||
// 기존 MousePosition 컨트롤 제거
|
||||
existingMouseControls.forEach(control => {
|
||||
gisCore.map.removeControl(control);
|
||||
});
|
||||
|
||||
// 새로운 MousePosition 컨트롤 추가
|
||||
const mousePosition = new MousePosition({
|
||||
coordinateFormat: function (coordinate) {
|
||||
let lng = coordinate[0];
|
||||
let lat = coordinate[1];
|
||||
return `경도 : ${lng.toFixed(3)} 위도 : ${lat.toFixed(3)}`;
|
||||
},
|
||||
projection: GisApp.FROM_PROJ,
|
||||
className: 'custom-mouse-position',
|
||||
target: labelLonLatRef.current,
|
||||
});
|
||||
gisCore.map.addControl(mousePosition);
|
||||
}, [gisCore.map]);
|
||||
return (
|
||||
<>
|
||||
<ul className="map-scale-area">
|
||||
<li id="label_lonlat" ref={labelLonLatRef} />
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function GisSearch() {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<p>범례 영역입니다 (향후 컴포넌트로 교체 가능)</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
||||
import GisApp from './lib/gis.core';
|
||||
import GisOlSelect from './lib/gis.select';
|
||||
import { GisContext } from './Gis';
|
||||
|
||||
export default React.memo(function GisSelect({ onFeatureSelect }) {
|
||||
|
||||
const { gisMap } = useContext(GisContext);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!gisMap) return; // 아직 초기화 안 됐으면 아무것도 안 함
|
||||
|
||||
// const select = new GisOlSelect(gisMap);
|
||||
// GisApp.Module.select = select;
|
||||
|
||||
// select.getSelectedInfo(function (features) {
|
||||
// if (features?.[0]) {
|
||||
// // onFeatureClick?.(features[0]); // props로 넘긴 함수 실행
|
||||
// onFeatureSelect(layer.getProperties()); //피쳐 속성데이터 상위컴포넌트로 전달
|
||||
// }
|
||||
// });
|
||||
// }, [gisMap, onFeatureSelect]);
|
||||
|
||||
const select = new GisOlSelect(gisMap);
|
||||
GisApp.Module.select = select;
|
||||
|
||||
select.getSelectedInfo(function (features) {
|
||||
//clickFeature(features[0]);
|
||||
if(features.length > 0) {
|
||||
const layer = features[0];
|
||||
onFeatureSelect(layer); //피쳐 속성데이터 상위컴포넌트로 전달
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="select_control map_control_div ol-unselectable ol-control" style={{ pointerEvents: 'auto', zIndex: '1' }}>
|
||||
<h3>선,면,다각형을 이용한 시설물 선택 영역</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<div className="sprite_gis icon1 btn_select_one" title="단일선택" onClick={reset}>
|
||||
<span>단일선택</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="sprite_gis icon4 btn_select_cancel" title="새로고침" onClick={reset}>
|
||||
<span>새로고침</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div> */}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@common/components/ui/tabs';
|
||||
import GisTree from './GisTree';
|
||||
import GisSearch from './GisSearch';
|
||||
import { ChevronLeft, ChevronRight, Search } from 'lucide-react';
|
||||
import SearchModal from '@/map/SearchModal';
|
||||
|
||||
|
||||
function GisTab({ visible, setVisible, isSearch, isSearchModalOpen, setIsSearchModalOpen }) {
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`absolute top-0 left-0 w-[250px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-40
|
||||
${visible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0 pointer-events-none'}`}
|
||||
>
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex-1 flex flex-col overflow-hidden">
|
||||
<TabsList className="flex-none mt-4 border-none rounded-none">
|
||||
<TabsTrigger value="layers" className="flex-1 h-10">레이어</TabsTrigger>
|
||||
{isSearch && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
className="h-10 w-10 flex items-center justify-center px-0"
|
||||
>
|
||||
<Search size={18} className="text-gray-700" />
|
||||
</button>
|
||||
// <TabsTrigger value="search" className="flex-1 h-10 flex items-center justify-center">
|
||||
// <Search size={18} />
|
||||
// </TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
|
||||
{/* GisTree는 항상 마운트되며, 탭 상태에 따라 보여주기만 함 */}
|
||||
<div
|
||||
className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${activeTab === 'layers' ? 'block' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
<GisTree />
|
||||
</div>
|
||||
|
||||
{/* GisSearch도 동일하게 처리 */}
|
||||
{/* {isSearch && (
|
||||
<div
|
||||
className={`grow overflow-y-auto p-2 pt-0 border-none shadow-none ${
|
||||
activeTab === 'search' ? 'block' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
<GisSearch />
|
||||
</div>
|
||||
)} */}
|
||||
|
||||
|
||||
{/* 🔍 검색 모달 */}
|
||||
<SearchModal open={isSearchModalOpen} onOpenChange={setIsSearchModalOpen} />
|
||||
</Tabs>
|
||||
|
||||
{/* 접기 버튼 */}
|
||||
<button
|
||||
onClick={() => setVisible(false)}
|
||||
className="absolute top-1/2 right-[-3px] transform -translate-y-1/2 z-50 bg-white border border-l border-gray-300 rounded-l-none rounded-r px-1 py-2 shadow-sm"
|
||||
>
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 열기 버튼 */}
|
||||
{!visible && (
|
||||
<button
|
||||
onClick={() => setVisible(true)}
|
||||
className="absolute top-1/2 left-[-3px] transform -translate-y-1/2 z-50 bg-white border border-r border-gray-300 rounded-r px-1 py-2 shadow-sm"
|
||||
>
|
||||
<ChevronRight size={16} />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(GisTab);
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import GisApp from './lib/gis.core';
|
||||
import GisOlMeasure from './lib/gis.measure';
|
||||
import { useContext } from 'react';
|
||||
import { useGisContext } from './Gis';
|
||||
export default React.memo(function GisToolBar() {
|
||||
const { gisMap } = useGisContext();
|
||||
if (!gisMap) return <></>;
|
||||
const gisMeasuer = new GisOlMeasure(gisMap);
|
||||
GisApp.Module.measure = gisMeasuer;
|
||||
const zoomIn = () => {
|
||||
if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) {
|
||||
gisMap.getView().animate({
|
||||
zoom: gisMap.getView().getZoom() + 1,
|
||||
duration: 250,
|
||||
});
|
||||
}
|
||||
};
|
||||
const zoomOut = () => {
|
||||
if (gisMap.getView().getMaxZoom() > gisMap.getView().getZoom()) {
|
||||
gisMap.getView().animate({
|
||||
zoom: gisMap.getView().getZoom() - 1,
|
||||
duration: 250,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const measure = type => {
|
||||
gisMeasuer.addInteraction(type);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
gisMeasuer.removeFeature();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className="custom-toolbar">
|
||||
<div className="tool-bar">
|
||||
<h3>toolbar 영역</h3>
|
||||
<ul>
|
||||
<li>
|
||||
<div className="btn_control_zoomin" title="확대" onClick={zoomIn}>
|
||||
<span>확대</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_control_zoomout" title="축소" onClick={zoomOut}>
|
||||
<span>축소</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_control_ruler btn_control_measure" title="거리 측정" onClick={() => measure(GisApp.measure.LineString)}>
|
||||
<span>거리측정</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="btn_control_area btn_control_measure" title="면적 측정" onClick={() => measure(GisApp.measure.Polygon)}>
|
||||
<span>면적측정</span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div className="sprite_gis icon4 btn_select_cancel" title="리셋" onClick={() => reset()}>
|
||||
<span>리셋</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
import GisApp from './lib/gis.core';
|
||||
import { util } from './lib/util';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useGisContext } from './Gis';
|
||||
import { DndProvider } from "react-dnd";
|
||||
import {
|
||||
Tree,
|
||||
MultiBackend,
|
||||
getBackendOptions
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components/ui/tabs";
|
||||
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => {} }) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
const [checked, setChecked] = useState([]);
|
||||
const [expanded, setExpanded] = useState([]);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
const nodeData = setNodeValues(treeData);
|
||||
setNodes(nodeData);
|
||||
gisCore.setAllLayersOnMap(false);
|
||||
}
|
||||
}, [treeData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (nodes.length > 0) {
|
||||
const defaultChecked = getDefaultCheckedNodes(nodes);
|
||||
setChecked(defaultChecked);
|
||||
const defaultExpanded = Array.from(new Set([
|
||||
...defaultChecked,
|
||||
...defaultChecked.flatMap((id) => getAncestors(id, nodes))
|
||||
]));
|
||||
setExpanded(defaultExpanded);
|
||||
gisCore.setLayerOnMap(defaultChecked, true);
|
||||
}
|
||||
}, [nodes]);
|
||||
|
||||
const getDefaultCheckedNodes = (nodes, values = []) => {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
if (node.data.initEprssYn === 'Y') {
|
||||
values.push(node.data.lyrSn);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
const getDescendants = (id, all) => {
|
||||
const children = all.filter((n) => n.parent === id);
|
||||
return children.reduce(
|
||||
(acc, child) => acc.concat(child.id, ...getDescendants(child.id, all)),
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
const getAncestors = (id, all) => {
|
||||
const node = all.find((n) => n.id === id);
|
||||
if (!node || node.parent == null) return [];
|
||||
return [node.parent, ...getAncestors(node.parent, all)];
|
||||
};
|
||||
|
||||
const isEveryChildChecked = (parentId, all, checked) => {
|
||||
const children = all.filter((n) => n.parent === parentId);
|
||||
return children.every((c) => checked.includes(c.id));
|
||||
};
|
||||
|
||||
const handleCheck = (id) => {
|
||||
setChecked((prevChecked) => {
|
||||
const isChecked = prevChecked.includes(id);
|
||||
let newChecked = [...prevChecked];
|
||||
if (isChecked) {
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = newChecked.filter((cid) => cid !== id && !descendants.includes(cid));
|
||||
const ancestors = getAncestors(id, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (newChecked.includes(p)) {
|
||||
newChecked = newChecked.filter((cid) => cid !== p);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const descendants = getDescendants(id, nodes);
|
||||
newChecked = Array.from(new Set([...newChecked, id, ...descendants]));
|
||||
const ancestors = getAncestors(id, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (isEveryChildChecked(p, nodes, newChecked)) {
|
||||
newChecked.push(p);
|
||||
}
|
||||
});
|
||||
// setExpanded((prev) => Array.from(new Set([...prev, ...ancestors])));
|
||||
}
|
||||
const allIds = nodes.map((n) => n.id);
|
||||
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
|
||||
gisCore.setLayerOnMap(newChecked, true);
|
||||
gisCore.setLayerOnMap(unchecked, false);
|
||||
return newChecked;
|
||||
});
|
||||
};
|
||||
|
||||
const setNodeValues = (result) => {
|
||||
return result.map((item) => {
|
||||
const src = makeLegendSrc(item);
|
||||
let label = item.lyrNm;
|
||||
return {
|
||||
id: item.lyrSn,
|
||||
text: label,
|
||||
parent: item.upperLyrSn,
|
||||
droppable: !!item.children,
|
||||
data: item,
|
||||
icon: src ? <img src={src} alt={item.lyrNm} /> : '',
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const makeLegendSrc = layerinfo => {
|
||||
if(!layerinfo.gobjTy) return null;
|
||||
|
||||
let src;
|
||||
if (layerinfo.iconNm) {
|
||||
src = GisApp.CONTEXT_LAYER_PATH + layerinfo.iconNm;
|
||||
} else {
|
||||
const ss = makeLegendImage(layerinfo);
|
||||
src = ss.src;
|
||||
}
|
||||
return src;
|
||||
};
|
||||
|
||||
const makeLegendImage = layerinfo => {
|
||||
let image = document.createElement('img');
|
||||
let canvas = document.createElement('canvas');
|
||||
canvas.width = 16;
|
||||
canvas.height = 16;
|
||||
let ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
|
||||
if (layerinfo.gobjTy === 'lineLabelPoligon') {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
} else if (layerinfo.gobjTy === 'line') {
|
||||
ctx.moveTo(1, 15);
|
||||
ctx.lineTo(5, 1);
|
||||
ctx.lineTo(9, 15);
|
||||
ctx.lineTo(15, 1);
|
||||
} else {
|
||||
ctx.moveTo(1, 1);
|
||||
ctx.lineTo(15, 1);
|
||||
ctx.lineTo(15, 15);
|
||||
ctx.lineTo(1, 15);
|
||||
ctx.lineTo(1, 1);
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === 'lineLabelPoligon') {
|
||||
ctx.fillStyle = layerinfo.polgnColor;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (layerinfo.gobjTy === 'line') {
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = layerinfo.lnColor;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
image.src = canvas.toDataURL('image/png');
|
||||
return image;
|
||||
};
|
||||
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DndProvider backend={MultiBackend} options={getBackendOptions()}>
|
||||
<Tree
|
||||
tree={nodes}
|
||||
rootId={null}
|
||||
initialOpen={expanded}
|
||||
onOpenChange={setExpanded}
|
||||
render={(node, { depth, isOpen, onToggle }) => (
|
||||
<div
|
||||
className="flex items-center gap-2 py-1 px-2 hover:bg-gray-100 rounded"
|
||||
style={{ marginInlineStart: depth * 16 + (!node.droppable ? 16 : 0) }}
|
||||
>
|
||||
{node.droppable && (
|
||||
<i onClick={onToggle} className={`diveicon di-arrow-forward mr-2 text-base text-label ${isOpen ? 'rotate-90' : ''}`}
|
||||
title={isOpen ? '닫기' : '열기'}>
|
||||
</i>
|
||||
)}
|
||||
<input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer"/>
|
||||
{node.icon && <span>{node.icon}</span>}
|
||||
<span className="text-[1.3rem] font-medium text-label">{node.text}</span>
|
||||
</div>
|
||||
)}
|
||||
sort={(a, b) => {
|
||||
const aOrder = parseInt(a.data?.sortOrdr ?? '0');
|
||||
const bOrder = parseInt(b.data?.sortOrdr ?? '0');
|
||||
return aOrder - bOrder;
|
||||
}}
|
||||
/>
|
||||
</DndProvider>
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import OLCesium from 'olcs/OLCesium';
|
||||
import olView from 'ol/View.js';
|
||||
import olSourceImageWMS from 'ol/source/ImageWMS.js';
|
||||
import olSourceOSM from 'ol/source/OSM.js';
|
||||
import olLayerImage from 'ol/layer/Image.js';
|
||||
import olLayerTile from 'ol/layer/Tile.js';
|
||||
import olMap from 'ol/Map.js';
|
||||
import { GLOBAL } from '../../js/consts/global';
|
||||
|
||||
const Google3DMap = () => {
|
||||
const mapRef = useRef(null);
|
||||
const [ol3d, setOl3d] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
|
||||
const customProjSource = new olSourceImageWMS({
|
||||
attributions: '© <a href="http://www.geo.admin.ch/internet/geoportal/en/home.html">National parks / geo.admin.ch</a>',
|
||||
crossOrigin: 'anonymous',
|
||||
params: { LAYERS: 'ch.bafu.schutzgebiete-paerke_nationaler_bedeutung' },
|
||||
projection: 'EPSG:21781',
|
||||
url: 'https://wms.geo.admin.ch/',
|
||||
});
|
||||
|
||||
const { Cesium } = window;
|
||||
Cesium.Ion.defaultAccessToken = GLOBAL.CESIUM_ION_TOKEN;
|
||||
|
||||
const ol2d = new olMap({
|
||||
layers: [new olLayerTile({ source: new olSourceOSM() }), new olLayerImage({ source: customProjSource })],
|
||||
target: mapRef.current,
|
||||
view: new olView({
|
||||
center: [860434.6266531206, 6029479.0044273855],
|
||||
zoom: 6,
|
||||
}),
|
||||
});
|
||||
|
||||
const ol3dInstance = new OLCesium({
|
||||
map: ol2d,
|
||||
time: () => Cesium.JulianDate.now(),
|
||||
});
|
||||
|
||||
const scene = ol3dInstance.getCesiumScene();
|
||||
Cesium.createWorldTerrainAsync().then(tp => {
|
||||
scene.terrainProvider = tp;
|
||||
});
|
||||
|
||||
ol3dInstance.setEnabled(true);
|
||||
setOl3d(ol3dInstance);
|
||||
|
||||
return () => {
|
||||
ol3dInstance.setEnabled(false);
|
||||
ol2d.setTarget(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div ref={mapRef} style={{ width: '100%', height: '100vh' }} />;
|
||||
};
|
||||
|
||||
export default Google3DMap;
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Button } from "@common/components";
|
||||
import { GisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
export default React.memo(function MapToggleBtn() {
|
||||
const { gisCore } = useContext(GisContext);
|
||||
const [activeMap, setActiveMap] = useState({ label: '일반', type: 'base' }); // 기본 선택값
|
||||
|
||||
const handleSelect = mapObj => {
|
||||
setActiveMap(mapObj);
|
||||
gisCore.changeBaseLayer(mapObj.type);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mapbtn" style={{ zIndex: 101 }}>
|
||||
<button className="sprite_gis viewer" title="지도선택">지도 선택</button>
|
||||
<ul className="choice_map">
|
||||
<li className={activeMap.type === 'base' ? 'on' : ''}>
|
||||
<button className="map1" id="base" title="일반지도" onClick={() => handleSelect({ label: '일반', type: 'base' })}>
|
||||
<div>일반지도</div>
|
||||
</button>
|
||||
</li>
|
||||
<li className={activeMap.type === 'satellite' ? 'on' : ''}>
|
||||
<button className="map2" id="satelite" title="위성지도" onClick={() => handleSelect({ label: '위성', type: 'satellite' })}>
|
||||
<div>위성지도</div>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,192 @@
|
||||
@charset 'utf-8';
|
||||
|
||||
.edit_control {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 610px;
|
||||
}
|
||||
.edit_control h3 {
|
||||
overflow: hidden;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
.edit_control ul {
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
height: 40px;
|
||||
margin-left: 10px;
|
||||
background: #fff;
|
||||
font-size: 0;
|
||||
border: 1px solid #9aa7b8;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 0px 5px #999;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit_control ul li {
|
||||
display: inline-block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-right: 1px solid #eeeeee;
|
||||
}
|
||||
.edit_control ul li:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
/*background 삭제*/
|
||||
/* .edit_control ul li a {overflow:hidden;width: 40px;height: 40px;background: url(../../images/om/ico_gis_sprite.png) no-repeat;} */
|
||||
.edit_control ul li a {
|
||||
overflow: hidden;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
}
|
||||
.edit_control ul li a span {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
.edit_control select {
|
||||
float: left;
|
||||
min-width: 150px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 0px 5px #999;
|
||||
}
|
||||
|
||||
.edit_control input {
|
||||
float: left;
|
||||
min-width: 150px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 0px 5px #999;
|
||||
}
|
||||
|
||||
.edit_control button {
|
||||
float: left;
|
||||
min-width: 80px;
|
||||
height: 40px;
|
||||
margin-left: 5px;
|
||||
font-size: 13px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit_control button:hover {
|
||||
background: #1357ce;
|
||||
}
|
||||
|
||||
.edit_toolbar {
|
||||
position: absolute;
|
||||
left: 280px;
|
||||
top: 52px;
|
||||
}
|
||||
.btn_edit_slt {
|
||||
background: url(../images/toolbar/btn_edit_slt.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
.btn_edit_slt:hover {
|
||||
background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
.btn_edit_slt.active {
|
||||
background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
.btn_edit_add {
|
||||
background: url(../images/toolbar/btn_edit_add.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
.btn_edit_add:hover {
|
||||
background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
.btn_edit_add.active {
|
||||
background: url(../images/toolbar/btn_edit_add_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_del {
|
||||
background: url(../images/toolbar/btn_edit_del.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_del:hover {
|
||||
background: url(../images/toolbar/btn_edit_del_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_prev {
|
||||
background: url(../images/toolbar/btn_edit_prev.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_prev:hover {
|
||||
background: url(../images/toolbar/btn_edit_prev_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_next {
|
||||
background: url(../images/toolbar/btn_edit_next.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_next:hover {
|
||||
background: url(../images/toolbar/btn_edit_next_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_snap {
|
||||
background: url(../images/toolbar/btn_edit_snap.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_snap:hover {
|
||||
background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_snap.active {
|
||||
background: url(../images/toolbar/btn_edit_snap_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_save {
|
||||
background: url(../images/toolbar/btn_edit_save.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_save:hover {
|
||||
background: url(../images/toolbar/btn_edit_save_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_transform {
|
||||
background: url(../images/toolbar/btn_edit_slt.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_transform:hover {
|
||||
background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.btn_edit_transform.active {
|
||||
background: url(../images/toolbar/btn_edit_slt_on.png) no-repeat center;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@charset 'utf-8';
|
||||
/* 로드뷰 관련 */
|
||||
.roadview-container {width: 100%; height: 100%; z-index:500; display:block;position: absolute; left: 0px;top: 0px;}
|
||||
.roadview-container .roadview-container-1p {width: 310px; height: 190px; bottom: 15px; left: 15px; top: auto; border-radius: 2px; overflow: hidden; z-index: 1000; position: absolute;}
|
||||
.roadview-container .roadview-container-2p {width: 100%; height:100%; float:left; position:relative;}
|
||||
.roadview-container .roadview {position:absolute; top:0; right:0; left:0; bottom:0; z-index:0; background:#999;}
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote,
|
||||
q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: "";
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #e6e6fc;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 50px 0px;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 6px 6px 5px grey;
|
||||
}
|
||||
|
||||
.controls .controls__colors {
|
||||
display: flex;
|
||||
}
|
||||
.controls__colors .controls__color {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 3px 3px 5px grey;
|
||||
}
|
||||
|
||||
.jscolor {
|
||||
color: transparent;
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
border-width: 0px;
|
||||
margin-right: 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 3px 3px 5px grey;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.controls .controls__btns {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.controls__btns button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.controls__btns button {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
padding: 5px 0px;
|
||||
width: 80px;
|
||||
text-align: center;
|
||||
border-radius: 10px;
|
||||
box-shadow: 3px 3px 3px grey;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.controls .controls__range {
|
||||
margin-top: 20px;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
@charset 'utf-8';
|
||||
|
||||
.ol-tooltip {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
opacity: 0.7;
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
.ol-tooltip-measure {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
.ol-tooltip-static {
|
||||
background-color: #ffcc33;
|
||||
color: black;
|
||||
border: 1px solid white;
|
||||
}
|
||||
.ol-tooltip-measure:before,
|
||||
.ol-tooltip-static:before {
|
||||
border-top: 6px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 6px solid transparent;
|
||||
border-left: 6px solid transparent;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -6px;
|
||||
margin-left: -7px;
|
||||
left: 50%;
|
||||
}
|
||||
.ol-tooltip-static:before {
|
||||
border-top-color: #ffcc33;
|
||||
}
|
||||
@@ -0,0 +1,923 @@
|
||||
import MousePosition from 'ol/control/MousePosition';
|
||||
import ScaleLine from 'ol/control/ScaleLine';
|
||||
import Feature from 'ol/Feature';
|
||||
import WKT from 'ol/format/WKT';
|
||||
import LinearRing from 'ol/geom/LinearRing';
|
||||
import Draw from 'ol/interaction/Draw';
|
||||
import TileLayer from 'ol/layer/Tile';
|
||||
import * as olProj from 'ol/proj';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import XYZ from 'ol/source/XYZ';
|
||||
import GeoSJON from 'ol/format/GeoJSON';
|
||||
import { Fill, Stroke, Style } from 'ol/style';
|
||||
import CircleStyle from 'ol/style/Circle';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import { Point } from 'ol/geom';
|
||||
import { boundingExtent } from 'ol/extent';
|
||||
import olSourceImageWMS from 'ol/source/ImageWMS';
|
||||
import { GeoJSON } from 'ol/format';
|
||||
import GisOlEditLayer from './gis.edit';
|
||||
import GisOlLayer from './gis.layer';
|
||||
|
||||
const GisApp = {};
|
||||
/**
|
||||
* GisApp 전역변수 생성
|
||||
*/
|
||||
//레이어 정보
|
||||
GisApp.layerInfos = [];
|
||||
//기본정보
|
||||
//GisApp.GEOSERVER_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/ows';
|
||||
//GisApp.GEOSERVER_WMS_URL = 'http://rsone.iptime.org:10010/geoserver/hasudo/wms';
|
||||
GisApp.GEOSERVER_URL = '/sn/geoserver/hasudo/ows';
|
||||
GisApp.GEOSERVER_WMS_URL = '/sn/geoserver/hasudo/wms';
|
||||
|
||||
//맵 정보
|
||||
GisApp.EMAP_URL = '/emap/';
|
||||
GisApp.EMAP_SATELLITE_URL = '/emap/satellite/';
|
||||
GisApp.VWORLD_URL = 'https://xdworld.vworld.kr/2d/Base/service/{z}/{x}/{y}.png';
|
||||
GisApp.VWORLD_SATELLITE_URL = 'https://xdworld.vworld.kr/2d/Satellite/service/{z}/{x}/{y}.jpeg';
|
||||
GisApp.GOOGLE_API_KEY = 'AIzaSyCf1sgeELFalfPuqlykRKBMj6X9u_DQH6M';
|
||||
GisApp.GOOGLE_ROADMAP_URL = `https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
|
||||
GisApp.GOOGLE_SATELLITE_URL = `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
|
||||
GisApp.GOOGLE_HYBRID_URL = `https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&key=${GisApp.GOOGLE_API_KEY}`;
|
||||
GisApp.GOOGLE_3D_URL = `https://tile.googleapis.com/v1/3dtiles/root.json?key=${GisApp.GOOGLE_API_KEY}`;
|
||||
GisApp.OPENSTREET_URL = '//tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
GisApp.BASE_MAP_TYPES = [];
|
||||
|
||||
GisApp.FROM_PROJ = 'EPSG:4326';
|
||||
GisApp.TO_PROJ = 'EPSG:3857';
|
||||
GisApp.TO_DATA_PROJ = 'EPSG:4326';
|
||||
GisApp.CONTEXT_LAYER_PATH = '/sn/images/map/layericon/';
|
||||
GisApp.layerCode = {};
|
||||
GisApp.hlLayerId = 'LightFeatureList';
|
||||
GisApp.hLayerId = 'highLightFeature';
|
||||
GisApp.wtlkgAnal = 'wtlkgAnal';
|
||||
|
||||
GisApp.Module = {
|
||||
core: null,
|
||||
select: null,
|
||||
measure: null,
|
||||
layer: null,
|
||||
style: null,
|
||||
edit: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* GisApp.layerInfos Set
|
||||
*/
|
||||
GisApp.setLayerInfos = function (layerInfos) {
|
||||
GisApp.layerInfos = layerInfos;
|
||||
};
|
||||
|
||||
/**
|
||||
* GisApp core
|
||||
* @requires gis.layer.js
|
||||
*/
|
||||
GisApp.core = function (options) {
|
||||
GisApp.core.prototype.initialize(options);
|
||||
};
|
||||
|
||||
GisApp.core.prototype = {
|
||||
defaults: {
|
||||
target: '',
|
||||
centerX: 37.2806094,
|
||||
centerY: 127.0032502,
|
||||
zoom: 11,
|
||||
minZoom: 5,
|
||||
maxZoom: 22,
|
||||
},
|
||||
options: {
|
||||
/*1: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.3227711, centerY: 129.1836202, dataProj: 'EPSG:5186' },
|
||||
4: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 35.0140499, centerY: 126.7110757, dataProj: 'EPSG:5186' },
|
||||
14: { zoom: 16, minZoom: 10, maxZoom: 22, centerX: 34.9390499, centerY: 127.6960757, dataProj: 'EPSG:5186' },*/
|
||||
},
|
||||
epsg_5179: null,
|
||||
map: null, // ol.Map
|
||||
view: {}, // ol.view
|
||||
interactions: {}, // interaction 모음 객체
|
||||
selectedFeature: null, // 현재 선택된 feature
|
||||
baseLayers: {},
|
||||
baseMiniMapLayers: {},
|
||||
layers: {},
|
||||
initialize: function (options) {
|
||||
let option = this.options['1'];
|
||||
if (option) {
|
||||
Object.assign(this.defaults, option);
|
||||
GisApp.TO_DATA_PROJ = option.dataProj;
|
||||
}
|
||||
Object.assign(this.defaults, options);
|
||||
GisApp.Module.core = this;
|
||||
GisApp.Module.layer = new GisOlLayer(this.defaults);
|
||||
GisApp.Module.edit = new GisOlEditLayer(this.defaults);
|
||||
|
||||
this.baseMiniMapLayers['base'] = this.getBaseMap('minimap-base');
|
||||
this.baseLayers['base'] = this.getBaseMap('base');
|
||||
this.baseLayers['satellite'] = this.getBaseMap('satellite');
|
||||
this.baseLayers['3d'] = this.getBaseMap('3d');
|
||||
},
|
||||
layerType: {
|
||||
point: 'POINT',
|
||||
line: 'LINE',
|
||||
poligon: 'POLIGON',
|
||||
},
|
||||
chgMngFcltOptions: function (fcltCd) {
|
||||
let option = this.options[fcltCd];
|
||||
Object.assign(this.defaults, option);
|
||||
this.setCenter(Number(this.defaults.centerX), Number(this.defaults.centerY));
|
||||
this.setZoom(15);
|
||||
},
|
||||
getBaseMap: function (_id) {
|
||||
let url = GisApp.VWORLD_URL;
|
||||
if (_id === 'satellite') {
|
||||
url = GisApp.VWORLD_SATELLITE_URL;
|
||||
}
|
||||
let baseMap = new TileLayer({
|
||||
id: _id,
|
||||
visible: true,
|
||||
type: 'base',
|
||||
source: new XYZ({
|
||||
url: url,
|
||||
crossOrigin: 'anonymous',
|
||||
}),
|
||||
zIndex: 0,
|
||||
});
|
||||
return baseMap;
|
||||
},
|
||||
/**
|
||||
* [사용]테이블명으로 레이어 아이디 조회
|
||||
*/
|
||||
getLayerCodeByTbNm: function (_tableNm) {
|
||||
let returnLayerCode = null;
|
||||
for (let i in GisApp.layerCode) {
|
||||
let layerCode = GisApp.layerCode[i];
|
||||
if (layerCode.tableName === _tableNm) {
|
||||
returnLayerCode = layerCode;
|
||||
}
|
||||
}
|
||||
return returnLayerCode;
|
||||
},
|
||||
/**
|
||||
* [사용]베이스 레이어 변경
|
||||
* Parameters:
|
||||
* _title - {String} 베이스 레이어 이름
|
||||
*
|
||||
* Returns:
|
||||
*/
|
||||
changeBaseLayer: function (_id) {
|
||||
this.map.getLayers().forEach(function (layer) {
|
||||
let type = layer.get('type');
|
||||
let id = layer.get('id');
|
||||
if (type == 'base') {
|
||||
if (id == _id) {
|
||||
layer.setVisible(true);
|
||||
} else {
|
||||
layer.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [사용]지도 중심좌표 설정
|
||||
*/
|
||||
setCenter: function (x, y, transYn) {
|
||||
if (!x || !y) return;
|
||||
let view = this.map.getView();
|
||||
|
||||
if (!transYn) {
|
||||
// 값이 없으면
|
||||
let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
|
||||
const style = new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 5,
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,0,0,1)',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(255,0,0,1)',
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
// 빨간색 점 추가 (스타일 적용)
|
||||
let redPoint = new Feature({
|
||||
geometry: new Point(center),
|
||||
});
|
||||
|
||||
let redPointLayer = new VectorLayer({
|
||||
source: new VectorSource({
|
||||
features: [redPoint],
|
||||
}),
|
||||
style: style,
|
||||
});
|
||||
redPointLayer.set('id', 'redPointLayer');
|
||||
// 이전에 만들었던 빨간색 점 레이어 삭제
|
||||
this.deleteLayer('redPointLayer');
|
||||
|
||||
// 새로운 빨간색 점 레이어를 맵에 추가
|
||||
this.map.addLayer(redPointLayer);
|
||||
view.setCenter(center);
|
||||
} else {
|
||||
view.setCenter([Number(x), Number(y)]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* [사용]주소검색 결과 지도이동
|
||||
*/
|
||||
setAddressResult: function (x, y, geom, addressName) {
|
||||
if (!x || !y) return;
|
||||
let view = this.map.getView();
|
||||
let center = olProj.transform([Number(y), Number(x)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
|
||||
|
||||
const style = [
|
||||
new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 5,
|
||||
fill: new Fill({
|
||||
color: 'rgba(255,0,0,1)',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(255,0,0,1)',
|
||||
width: 2,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'blue',
|
||||
width: 3,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(0, 0, 255, 0.1)',
|
||||
}),
|
||||
}),
|
||||
];
|
||||
|
||||
// 빨간색 점 추가 (스타일 적용)
|
||||
let redPoint = new Feature({
|
||||
geometry: new Point(center),
|
||||
});
|
||||
let redArea = new WKT().readFeature(geom, {
|
||||
dataProjection: GisApp.FROM_PROJ,
|
||||
featureProjection: GisApp.TO_PROJ,
|
||||
});
|
||||
|
||||
let redPointLayer = new VectorLayer({
|
||||
source: new VectorSource({
|
||||
features: [redPoint, redArea],
|
||||
}),
|
||||
style: style,
|
||||
});
|
||||
|
||||
redPointLayer.set('id', 'redPointLayer');
|
||||
// 이전에 만들었던 빨간색 점 레이어 삭제
|
||||
this.deleteLayer('redPointLayer');
|
||||
|
||||
// 새로운 빨간색 점 레이어를 맵에 추가
|
||||
this.map.addLayer(redPointLayer);
|
||||
|
||||
view.animate({ center: center, zoom: 19 });
|
||||
return redPointLayer;
|
||||
},
|
||||
/*
|
||||
* [사용]zoom 설정
|
||||
*/
|
||||
setZoom: function (zoom) {
|
||||
if (!zoom) return;
|
||||
this.map.getView().setZoom(zoom);
|
||||
},
|
||||
/*
|
||||
* [사용]zoom 정보 반환
|
||||
*/
|
||||
getZoom: function () {
|
||||
return this.map.getView().getZoom();
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 맵에 추가(외부에서 생성된 layer를 파라미터로 받아 맵에 추가)
|
||||
*/
|
||||
setWfsLayerOnMap: function (layer) {
|
||||
this.deleteLayer(layer.get('id'));
|
||||
this.map.addLayer(layer);
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 생성(지오서버에서 WFS 방식으로 레이어정보 가져와 레이어 생성)
|
||||
*/
|
||||
getWfsLayer: function (layerCodeObj) {
|
||||
return GisApp.Module.layer.getWfsLayer(layerCodeObj);
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 맵에 추가(getWfsLayer 사용하여 레이어 생성)
|
||||
*/
|
||||
addWfsLayer: function (layerCodeObj) {
|
||||
let layer = this.getWfsLayer(layerCodeObj);
|
||||
this.deleteLayer(layer.get('id'));
|
||||
this.map.addLayer(layer);
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 맵에 추가(getWmsLayer 사용하여 레이어 생성)
|
||||
*/
|
||||
addWmsLayer: function (layerCodeObj) {
|
||||
let layer = GisApp.Module.layer.getWmsLayer(layerCodeObj);
|
||||
this.deleteLayer(layer.get('id'));
|
||||
this.map.addLayer(layer);
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 삭제
|
||||
*/
|
||||
deleteLayer: function (layerId) {
|
||||
const map = this.map;
|
||||
if (!layerId) return;
|
||||
map.getLayers().forEach(function (layer) {
|
||||
let deleteLayer = null;
|
||||
if (layer != null && layer.get('id') != null) {
|
||||
let vlayerId = layer.get('id');
|
||||
if (vlayerId) {
|
||||
if (vlayerId === layerId) {
|
||||
deleteLayer = layer;
|
||||
} else if (`${vlayerId}`.includes(`${layerId}`)) {
|
||||
deleteLayer = layer;
|
||||
}
|
||||
if (deleteLayer) {
|
||||
map.removeLayer(deleteLayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 전체 삭제
|
||||
*/
|
||||
deleteAllLayer: function () {
|
||||
const map = this.map;
|
||||
map.getLayers().forEach(function (layer) {
|
||||
if (layer?.get('type') !== 'base') {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
//맵에 보이지 않는 레이어 삭제
|
||||
GisApp.Module.layer?.layers.forEach(function (layer) {
|
||||
map.removeLayer(layer);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [사용]레이어이름으로 레이어 조회 반환
|
||||
*/
|
||||
getLayerById: function (layerId) {
|
||||
if (!layerId) return;
|
||||
let layer = null;
|
||||
this.map.getAllLayers().forEach(function (el) {
|
||||
if (el != null && el?.get('id') != null) {
|
||||
let vlayerId = el?.get('id');
|
||||
if (Number(vlayerId) === Number(layerId)) {
|
||||
layer = el;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (layer != null) {
|
||||
GisApp.Module.layer.layers.forEach(function (el) {
|
||||
if (el != null && el?.get('id') != null) {
|
||||
let vlayerId = el?.get('id');
|
||||
if (Number(vlayerId) === Number(layerId)) {
|
||||
layer = el;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* [사용]오버레이 전체 삭제
|
||||
*/
|
||||
deleteAllOverlay: function () {
|
||||
let map = this.map;
|
||||
let overlays = map.getOverlays();
|
||||
if (overlays != null) {
|
||||
let overlayArray = overlays.getArray();
|
||||
let overlaySize = overlayArray.length;
|
||||
if (overlaySize > 0) {
|
||||
for (let i = overlaySize - 1; i >= 0; i--) {
|
||||
let overlay = overlayArray[i];
|
||||
map.removeOverlay(overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.values(GisApp.Module.style.overlays).forEach(overlay => {
|
||||
if (overlay) {
|
||||
map.removeOverlay(overlay);
|
||||
}
|
||||
});
|
||||
GisApp.Module.style.overlays = {};
|
||||
},
|
||||
/**
|
||||
* [사용]오버레이 아이디로 삭제
|
||||
*/
|
||||
deleteOverlay: function (_id) {
|
||||
let map = this.map;
|
||||
let overlays = map.getOverlays();
|
||||
if (overlays != null) {
|
||||
let overlayArray = overlays.getArray();
|
||||
let overlaySize = overlayArray.length;
|
||||
if (overlaySize > 0) {
|
||||
for (let i = overlaySize - 1; i >= 0; i--) {
|
||||
let overlay = overlayArray[i];
|
||||
let id = overlay.getId();
|
||||
if (id === _id) {
|
||||
map.removeOverlay(overlay);
|
||||
delete GisApp.Module.style.overlays[_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]맵 이벤트 삭제
|
||||
*/
|
||||
removeMapEventFnNm: function (handlerNm, fnNm) {
|
||||
let map = this.map;
|
||||
for (let i in map.listeners_[handlerNm]) {
|
||||
let listener = map.listeners_[handlerNm][i];
|
||||
if (listener.name == fnNm) {
|
||||
map.listeners_[handlerNm].splice(i, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]위경도 -> 시분초 변환
|
||||
*/
|
||||
coordTrans: function (coord) {
|
||||
let str = String(coord); /* String 변환 */
|
||||
let split = str.split('.'); /* 소수점 split */
|
||||
let hour = split[0]; /* 시간 */
|
||||
let minute = parseFloat((coord - parseInt(split[0]).toFixed(6)) * 60).toFixed(6);
|
||||
let min = String(minute).split('.')[0];
|
||||
let second = parseFloat((minute - parseInt(min)) * 60).toFixed(2);
|
||||
return hour + '도 ' + min + '분 ' + second + '초';
|
||||
},
|
||||
/**
|
||||
* [사용]시분초 -> 위경도 변환
|
||||
*/
|
||||
coordTransrate: function (degree, minute, second) {
|
||||
let min = (Number(minute) / 60).toFixed(2);
|
||||
let sec = (Number(second) / 3600).toFixed(4);
|
||||
return degree + Number(min) + Number(sec);
|
||||
},
|
||||
/**
|
||||
* [사용]마우스 좌표 위치
|
||||
*/
|
||||
mousePositionControl: function () {
|
||||
let element = document.getElementById('label_lonlat');
|
||||
let me = this;
|
||||
let mousePosition = new MousePosition({
|
||||
coordinateFormat: function (coordinate) {
|
||||
let text = '';
|
||||
if (element) {
|
||||
let lng = coordinate[0];
|
||||
let lat = coordinate[1];
|
||||
text += '경도 : ' + lng.toFixed(7) + ' ( ' + me.coordTrans(lng) + ' ) ';
|
||||
text += '위도 : ' + lat.toFixed(7) + '( ' + me.coordTrans(lat) + ' )';
|
||||
}
|
||||
return text;
|
||||
},
|
||||
projection: GisApp.FROM_PROJ,
|
||||
className: 'custom-mouse-position',
|
||||
target: document.getElementById('label_lonlat'),
|
||||
});
|
||||
this.map.addControl(mousePosition);
|
||||
},
|
||||
/**
|
||||
* [사용]스케일 표시
|
||||
*/
|
||||
scaleControl: function () {
|
||||
let scale = new ScaleLine({
|
||||
minWidth: 100,
|
||||
units: 'metric',
|
||||
});
|
||||
this.map.addControl(scale);
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 맵에 셋팅
|
||||
*
|
||||
* Parameters:
|
||||
* layerList - {Array} [layerNm : GisApp.layerCode 키]
|
||||
* visible - {Boolean} 레이어 visible 여부
|
||||
*
|
||||
* Returns:
|
||||
*/
|
||||
setLayerOnMap: function (layerIds, visible, addQurryFlter) {
|
||||
for (let i in layerIds) {
|
||||
let id = layerIds[i];
|
||||
let layer = this.getLayerById(id);
|
||||
if (layer) {
|
||||
layer.setVisible(visible);
|
||||
} else if (visible) {
|
||||
const layerCode = GisApp.layerCode[id];
|
||||
const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || '');
|
||||
layerCode['flterSql'] = queryFlter;
|
||||
//layer = this.addWmsLayer(layerCode);
|
||||
layer = this.addWfsLayer(layerCode);
|
||||
}
|
||||
}
|
||||
if (GisApp.Module.select) {
|
||||
this.setSelectInteraction();
|
||||
}
|
||||
if (GisApp.Module.edit.snap) {
|
||||
GisApp.Module.edit.setSnap();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]shp 파일 다운로드
|
||||
*/
|
||||
getShapeLayer: function (option) {
|
||||
GisApp.Module.layer.getShapeLayer(option);
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 셀렉트 가능하게 셋팅
|
||||
*/
|
||||
setSelectInteraction: function () {
|
||||
let layers = this.map.getLayers();
|
||||
if (layers) {
|
||||
for (let i in layers.array_) {
|
||||
let layer = layers.array_[i];
|
||||
if (layer.get('chcYn') === 'Y') {
|
||||
GisApp.Module.select.addLayer(layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]layerInfos(array) To layerCode(Object)
|
||||
*/
|
||||
convertLayerInfosToLayerCode: function (array, key, parentName) {
|
||||
let initialValue = {};
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let item = {};
|
||||
Object.assign(item, array[i]);
|
||||
if (parentName) {
|
||||
item.parentName = parentName;
|
||||
}
|
||||
initialValue[item[key]] = item;
|
||||
|
||||
if (item.children && item.children.length > 0) {
|
||||
let child = this.convertLayerInfosToLayerCode(item.children, key, item.name);
|
||||
Object.assign(initialValue, child);
|
||||
}
|
||||
if (initialValue[item[key]].children) {
|
||||
delete initialValue[item[key]].children;
|
||||
}
|
||||
}
|
||||
return initialValue;
|
||||
},
|
||||
/**
|
||||
* [사용]피처 좌표값 WKT포맷으로 변경
|
||||
*/
|
||||
convertFeatureGeomToWKT: function (feature) {
|
||||
let geometry = feature.getGeometry();
|
||||
const wktGeom = this.convertGeomToWkt(geometry);
|
||||
return wktGeom;
|
||||
},
|
||||
/**
|
||||
* [사용]WKT포맷 Geometry로 변경
|
||||
*/
|
||||
convertWktToGeom: function (wtkGeom) {
|
||||
let format = new WKT();
|
||||
let geom = format.readGeometry(wtkGeom);
|
||||
return geom;
|
||||
},
|
||||
/**
|
||||
* [사용]좌표와 properties의 키값으로 피처 검색
|
||||
*/
|
||||
getFeatureGeomProp: function (geometry, ftrIds, ftrCde) {
|
||||
let returnFeature;
|
||||
this.map.getLayers().forEach(function (layer) {
|
||||
let type = layer.get('type');
|
||||
if (type != 'base') {
|
||||
let source = layer.getSource();
|
||||
|
||||
let type = geometry.getType();
|
||||
let coordinates = geometry.getCoordinates();
|
||||
if (type.toUpperCase() === 'POINT') {
|
||||
coordinates = [coordinates];
|
||||
} else {
|
||||
coordinates = coordinates[0];
|
||||
}
|
||||
let featureFtrIds;
|
||||
let featureFtrCde;
|
||||
for (let i in coordinates) {
|
||||
let coordinate = coordinates[i];
|
||||
let feature = source.getClosestFeatureToCoordinate(coordinate);
|
||||
if (feature) {
|
||||
let properties = feature.getProperties();
|
||||
featureFtrIds = properties['FTR_IDS'];
|
||||
featureFtrCde = properties['FTR_CDE'];
|
||||
if (ftrIds == featureFtrIds && ftrCde == featureFtrCde) {
|
||||
returnFeature = feature;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return returnFeature;
|
||||
},
|
||||
/**
|
||||
* [사용]관리번호로 피처검색
|
||||
*/
|
||||
getFeatureByFtrIdn: function (typeName, ftrIdn) {
|
||||
let returnFeature;
|
||||
this.map.getLayers().forEach(function (layer) {
|
||||
let layerTypeName = layer.get('typeName');
|
||||
if (layerTypeName === typeName) {
|
||||
layer
|
||||
.getSource()
|
||||
.getFeatures()
|
||||
.forEach(feature => {
|
||||
let featureFtrIdn = feature.get('ftrIdn');
|
||||
if (featureFtrIdn === ftrIdn) {
|
||||
returnFeature = feature;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return returnFeature;
|
||||
},
|
||||
/**
|
||||
* [사용]맵 이벤트 추가
|
||||
*/
|
||||
addMapEvent: function (evtNm, id, callback) {
|
||||
if (id) {
|
||||
let evt = this.map.on(evtNm, callback);
|
||||
evt.listener.id = id;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]맵 이벤트 삭제
|
||||
*/
|
||||
removeMapEvent: function (evtNm, id) {
|
||||
if (id) {
|
||||
let listeners = this.map.listeners_[evtNm];
|
||||
for (let i in listeners) {
|
||||
let listenter = listeners[i];
|
||||
if (listenter.id == id) {
|
||||
listeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]마커 그리기 추가
|
||||
*/
|
||||
drawMarker: function (layer, callback) {
|
||||
let map = this.map;
|
||||
let drawSource = layer.getSource();
|
||||
let draw = new Draw({
|
||||
type: 'Point',
|
||||
source: drawSource,
|
||||
condition: function (evt) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
map.addInteraction(draw);
|
||||
draw.on('drawend', function (event) {
|
||||
callback(event);
|
||||
map.removeInteraction(draw);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [사용]피처 삭제
|
||||
*/
|
||||
deleteFeature: function (layerId, featureId) {
|
||||
if (!layerId || !featureId) return;
|
||||
let layer = this.getLayerById(layerId);
|
||||
if (layer != null) {
|
||||
let feature = layer.getSource().getFeatureById(featureId);
|
||||
if (feature != null) {
|
||||
layer.getSource().removeFeature(feature);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [사용]레이어
|
||||
*/
|
||||
fitExtent: function (extent, zoom) {
|
||||
let map = this.map;
|
||||
let mapSize = map.getSize();
|
||||
if (!zoom) {
|
||||
zoom = this.getZoom();
|
||||
}
|
||||
if (extent) {
|
||||
//map.getView().fit(extent, { size: [mapSize[0], mapSize[1]] });
|
||||
this.map.getView().fit(extent, {
|
||||
maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한
|
||||
duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위)
|
||||
});
|
||||
}
|
||||
this.setZoom(zoom);
|
||||
},
|
||||
getGeomLineOfFeature: function (feature) {
|
||||
if (!feature) {
|
||||
return;
|
||||
}
|
||||
let geometry = feature.getGeometry();
|
||||
let type = geometry.getType();
|
||||
if (!(type == 'LineString' || type == 'MultiLineString')) {
|
||||
return;
|
||||
}
|
||||
let geomLienString = geometry;
|
||||
if (type != 'LineString') {
|
||||
//lienString = geometry.getLineString();
|
||||
}
|
||||
return geomLienString;
|
||||
},
|
||||
getCenterGeomLineOfFeature: function (feature) {
|
||||
if (!feature) {
|
||||
return;
|
||||
}
|
||||
let center = [];
|
||||
let geomLienString = this.getGeomLineOfFeature(feature);
|
||||
let coords = geomLienString.getCoordinates();
|
||||
let coordCnt = coords.length;
|
||||
if (coordCnt == 1) {
|
||||
coords = coords[0];
|
||||
}
|
||||
//라인 한개
|
||||
if (coordCnt == 2) {
|
||||
return geomLienString;
|
||||
}
|
||||
//멀티라인
|
||||
let centerCnt = Math.floor(coordCnt / 2);
|
||||
let lineCoordinates = [];
|
||||
lineCoordinates.push(coords[centerCnt - 1]);
|
||||
lineCoordinates.push(coords[centerCnt]);
|
||||
geomLienString = new LinearRing(lineCoordinates);
|
||||
return geomLienString;
|
||||
},
|
||||
getCenterOfLineGeom: function (geom) {
|
||||
let center = [];
|
||||
let extent = geom.getExtent();
|
||||
let x = extent[0] + (extent[2] - extent[0]) / 2;
|
||||
let y = extent[1] + (extent[3] - extent[1]) / 2;
|
||||
center.push(x);
|
||||
center.push(y);
|
||||
return center;
|
||||
},
|
||||
/**
|
||||
* 1 -> 01 두자리수로 변환
|
||||
*/
|
||||
fillZeroToZoomLevel: function (n, digits) {
|
||||
let zero = '';
|
||||
n = n.toString();
|
||||
if (digits > n.length) {
|
||||
for (let i = 0; digits - n.length > i; i++) {
|
||||
zero += '0';
|
||||
}
|
||||
}
|
||||
return zero + n;
|
||||
},
|
||||
convertGeoJsonToFeature: function (data, dataProjection) {
|
||||
let format = new GeoSJON();
|
||||
let feature = format.readFeature(data, {
|
||||
dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
|
||||
featureProjection: GisApp.TO_PROJ,
|
||||
});
|
||||
return feature;
|
||||
},
|
||||
convertGeoJsonToFeatures: function (data, dataProjection) {
|
||||
let format = new GeoSJON();
|
||||
const features = [];
|
||||
for (let obj of data) {
|
||||
let feature = null;
|
||||
try {
|
||||
feature = format.readFeature(obj, {
|
||||
dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
|
||||
featureProjection: GisApp.TO_PROJ,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(obj);
|
||||
}
|
||||
features.push(feature);
|
||||
}
|
||||
return features;
|
||||
},
|
||||
|
||||
/**
|
||||
* [사용]WKT포맷 Geometry로 feature 생성
|
||||
*/
|
||||
convertWktToFeature: function (wtkGeom, dataProjection) {
|
||||
let format = new WKT();
|
||||
let feature = format.readFeature(wtkGeom, {
|
||||
dataProjection: dataProjection || GisApp.TO_DATA_PROJ,
|
||||
featureProjection: GisApp.TO_PROJ,
|
||||
});
|
||||
return feature;
|
||||
},
|
||||
|
||||
/**
|
||||
* [사용]geometry를 WKT포맷으로 변경
|
||||
*/
|
||||
convertGeomToWkt: function (geometry) {
|
||||
let format = new WKT();
|
||||
let wktGeom = format.writeGeometry(geometry, {
|
||||
dataProjection: GisApp.TO_DATA_PROJ,
|
||||
featureProjection: GisApp.TO_PROJ,
|
||||
});
|
||||
return wktGeom;
|
||||
},
|
||||
/**
|
||||
* [사용]레이어 전체 표시 컨트롤
|
||||
*/
|
||||
setAllLayersOnMap: function (visible) {
|
||||
this.map.getLayers().forEach(function (el) {
|
||||
if (el !== null && el.get('id') !== null) {
|
||||
let layerId = el.get('id');
|
||||
if ('base' !== layerId && 'satellite' !== layerId && 'minimap-base' !== layerId) {
|
||||
el.setVisible(visible);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
fitExtentZoomFeatures: function (features, zoom, paddingX = 0, paddingY = 0) {
|
||||
if (!features.length) return;
|
||||
// Feature들의 경계를 계산하여 extent 생성
|
||||
const extent = this.getExtentFeatures(features);
|
||||
if (zoom) {
|
||||
//const paddingX = 0; // X축 이동량 (양수는 오른쪽, 음수는 왼쪽)
|
||||
//const paddingY = -10000; // Y축 이동량 (양수는 아래쪽, 음수는 위쪽)
|
||||
extent[0] -= paddingX;
|
||||
extent[1] -= paddingY;
|
||||
this.fitExtent(extent, zoom);
|
||||
} else {
|
||||
this.map.getView().fit(extent, {
|
||||
maxZoom: 18, // 너무 확대되지 않도록 최대 줌 제한
|
||||
duration: 500, // 부드러운 애니메이션 효과 (밀리초 단위)
|
||||
});
|
||||
}
|
||||
},
|
||||
getExtentFeatures: function (features) {
|
||||
if (!features.length) return;
|
||||
let extent = [];
|
||||
const allCoordinates = features
|
||||
.map(feature => {
|
||||
const geometry = feature.getGeometry();
|
||||
if (!geometry || typeof geometry.getCoordinates !== 'function') {
|
||||
return null; // geometry가 없거나 getCoordinates() 메서드가 없으면 null 반환
|
||||
}
|
||||
return geometry.getCoordinates();
|
||||
})
|
||||
.filter(coords => coords !== null);
|
||||
function extractCoordinatePairs(arr) {
|
||||
let result = [];
|
||||
|
||||
function recursiveFlatten(subArray) {
|
||||
if (!Array.isArray(subArray)) return;
|
||||
|
||||
for (let item of subArray) {
|
||||
if (Array.isArray(item) && item.length === 2 && typeof item[0] === 'number' && typeof item[1] === 'number') {
|
||||
// [숫자, 숫자] 형태의 배열이면 추가
|
||||
result.push(item);
|
||||
} else {
|
||||
// 그렇지 않다면 재귀 호출
|
||||
recursiveFlatten(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recursiveFlatten(arr);
|
||||
return result;
|
||||
}
|
||||
|
||||
const flattenCoord = extractCoordinatePairs(allCoordinates);
|
||||
extent = boundingExtent(flattenCoord);
|
||||
return extent;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* [사용]피처 레이어 검색
|
||||
*/
|
||||
Feature.prototype.getLayer = function () {
|
||||
let map = GisApp.Module.core.map;
|
||||
let this_ = this,
|
||||
layer_;
|
||||
let sameFeature = function (feature) {
|
||||
return this_ === feature ? true : false;
|
||||
};
|
||||
map.getLayers().forEach(function (layer) {
|
||||
let source = layer.getSource();
|
||||
if (source instanceof VectorSource) {
|
||||
let features = source.getFeatures();
|
||||
if (features.length > 0) {
|
||||
let found = features.some(sameFeature);
|
||||
if (found) {
|
||||
layer_ = layer;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return layer_;
|
||||
};
|
||||
|
||||
export default GisApp;
|
||||
@@ -0,0 +1,521 @@
|
||||
import GisApp from './gis.core';
|
||||
import Draw from 'ol/interaction/Draw.js';
|
||||
import Modify from 'ol/interaction/Modify.js';
|
||||
import Collection from 'ol/Collection.js';
|
||||
import Snap from 'ol/interaction/Snap.js';
|
||||
import { click, always } from 'ol/events/condition';
|
||||
import UndoRedo from 'ol-ext/interaction/UndoRedo.js';
|
||||
import Transform from 'ol-ext/interaction/Transform.js';
|
||||
import { gAlert } from './util';
|
||||
import VectorSource from 'ol/source/Vector.js';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
|
||||
const GisOlEditLayer = function (options) {
|
||||
this.initialize(options);
|
||||
};
|
||||
|
||||
GisOlEditLayer.prototype = {
|
||||
defaults: {},
|
||||
editFlag: false,
|
||||
draw: null,
|
||||
modify: null,
|
||||
snap: null,
|
||||
undoredo: null,
|
||||
transform: null,
|
||||
drawType: {
|
||||
point: 'Point',
|
||||
line: 'MultiLineString',
|
||||
poligon: 'MultiPolygon',
|
||||
},
|
||||
selectedLayerId: null, //셀렉트 박스에서 선택된 레이어 ID
|
||||
targetElement: null,
|
||||
editFeatureInfo: {},
|
||||
addFeatureNo: 1,
|
||||
initialize: function (options) {
|
||||
Object.assign(this.defaults, options);
|
||||
},
|
||||
/**
|
||||
* [공통] Interaction 추가
|
||||
*/
|
||||
addInteration: function () {
|
||||
this.setUndoInteraction();
|
||||
},
|
||||
/**
|
||||
* [공통] Interaction 삭제
|
||||
*/
|
||||
removeInteraction: function () {
|
||||
this.removeDraw();
|
||||
this.removeModify();
|
||||
this.removeSnap();
|
||||
this.clearUndoRedoFeature();
|
||||
},
|
||||
/**
|
||||
* [공통] Interaction Active 컨트롤
|
||||
*/
|
||||
setInteractionActive: function (active) {
|
||||
this.setDrawActive(active);
|
||||
this.setModifyActive(active);
|
||||
this.setActiveUndo(active);
|
||||
},
|
||||
/**
|
||||
* [그리기] 기능 추가
|
||||
*/
|
||||
setDraw: function (lyrId) {
|
||||
if (!lyrId) return '레이어를 선택해주십시오.';
|
||||
const core = GisApp.Module.core;
|
||||
const layer = core.getLayerById(lyrId);
|
||||
const layerCode = GisApp.layerCode[lyrId];
|
||||
if (!layer) return '트리에서 레이어 체크 후 추가 가능합니다.';
|
||||
const lyrTy = layerCode.gobjTy;
|
||||
|
||||
//초기화
|
||||
this.removeModify();
|
||||
this.removeTransform();
|
||||
if (GisApp.Module.select) {
|
||||
GisApp.Module.select.setActive(false);
|
||||
}
|
||||
|
||||
const originalSource = layer.getSource();
|
||||
const drawSource = new VectorSource({
|
||||
features: originalSource.getFeatures(), // 기존 feature들 복사
|
||||
});
|
||||
|
||||
// 새로운 임시 레이어 추가 (draw 전용)
|
||||
const drawLayer = new VectorLayer({
|
||||
source: drawSource,
|
||||
minZoom: 1, // 이 레이어만 minZoom을 1로 설정
|
||||
style: layer.getStyle(),
|
||||
});
|
||||
core.map.addLayer(drawLayer);
|
||||
|
||||
//그리기 기능 추가
|
||||
const me = this;
|
||||
|
||||
if (me.draw) {
|
||||
core.map.removeInteraction(me.draw);
|
||||
}
|
||||
const drawType = this.drawType[lyrTy];
|
||||
this.draw = new Draw({
|
||||
type: drawType,
|
||||
source: drawSource,
|
||||
condition: function (evt) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
core.map.addInteraction(this.draw);
|
||||
|
||||
//그리기 기능 이벤트
|
||||
this.draw.on('drawstart', function (event) {});
|
||||
|
||||
const drawFeatureCallback = function (event) {
|
||||
me.setActiveUndo(true);
|
||||
const drawFeature = event.feature;
|
||||
drawFeature.setId(lyrId + '_feature_' + me.addFeatureNo++);
|
||||
|
||||
const properties = {};
|
||||
properties['lyr_id'] = lyrId;
|
||||
drawFeature.setProperties(properties);
|
||||
|
||||
me.setAddFeatureUndo(layer, drawFeature);
|
||||
me.setActiveUndo(false);
|
||||
};
|
||||
|
||||
this.draw.on('drawend', function (event) {
|
||||
drawFeatureCallback(event);
|
||||
});
|
||||
|
||||
/*drawSource.listeners_.addfeature = null;
|
||||
let addfeatureCallback = function(event){
|
||||
}
|
||||
drawSource.on('addfeature', addfeatureCallback);*/
|
||||
},
|
||||
/**
|
||||
* [그리기] 기능 제거
|
||||
*/
|
||||
removeDraw: function () {
|
||||
let core = GisApp.Module.core;
|
||||
core.map.removeInteraction(this.draw);
|
||||
if (GisApp.Module.select) {
|
||||
GisApp.Module.select.setActive(true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [그리기] 기능 활성화
|
||||
*/
|
||||
setDrawActive: function (active) {
|
||||
if (this.draw) {
|
||||
this.draw.setActive(active);
|
||||
if (!active) {
|
||||
/* $('#selectEditLayer').css('display', 'none');
|
||||
$('.btn_edit_add').removeClass('active'); */
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] UndoInteraction 추가
|
||||
*/
|
||||
setUndoInteraction: function () {
|
||||
let core = GisApp.Module.core;
|
||||
if (!this.undoredo) {
|
||||
this.undoredo = new UndoRedo();
|
||||
core.map.addInteraction(this.undoredo);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] UndoInteraction 제거
|
||||
*/
|
||||
removeUndoInteraction: function () {
|
||||
let core = GisApp.Module.core;
|
||||
if (this.undoredo) {
|
||||
core.map.removeInteraction(this.undoredo);
|
||||
this.undoredo = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] UndoInteraction 활성화
|
||||
*/
|
||||
setActiveUndo: function (active) {
|
||||
if (this.undoredo) {
|
||||
this.undoredo.setActive(active);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 피처 추가
|
||||
*/
|
||||
setAddFeatureUndo: function (layer, addFeature) {
|
||||
if (this.undoredo) {
|
||||
let obj = {};
|
||||
obj.target = layer.getSource();
|
||||
obj.feature = addFeature;
|
||||
obj.type = 'addfeature';
|
||||
obj.feature.set('uploadType', 'addFeature');
|
||||
this.undoredo._onAddRemove(obj);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 피처 삭제
|
||||
*/
|
||||
setRemoveFeatureUndo: function (layer, removeFeature) {
|
||||
if (this.undoredo) {
|
||||
let obj = {};
|
||||
obj.target = layer.getSource();
|
||||
obj.feature = removeFeature;
|
||||
obj.type = 'removefeature';
|
||||
if (obj.feature.get('uploadType') !== 'addFeature') {
|
||||
obj.feature.set('uploadType', 'removeFeature');
|
||||
}
|
||||
this.undoredo._onAddRemove(obj);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 이전
|
||||
*/
|
||||
undo: function () {
|
||||
if (this.undoredo) {
|
||||
if (this.undoredo.hasUndo() === 0) {
|
||||
gAlert('INFO', 'NO_PREV_HISTORY');
|
||||
return;
|
||||
}
|
||||
this.setActiveUndo(true);
|
||||
this.undoredo.undo();
|
||||
this.setActiveUndo(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 다음
|
||||
*/
|
||||
redo: function () {
|
||||
if (this.undoredo) {
|
||||
if (this.undoredo.hasRedo() === 0) {
|
||||
gAlert('INFO', 'NO_NEXT_HISTORY');
|
||||
return;
|
||||
}
|
||||
this.setActiveUndo(true);
|
||||
this.undoredo.redo();
|
||||
this.setActiveUndo(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 초기화
|
||||
*/
|
||||
clearUndoRedo: function () {
|
||||
if (this.undoredo) {
|
||||
const undoStack = this.undoredo._undoStack.array_;
|
||||
for (let i in undoStack) {
|
||||
const undo = undoStack[i];
|
||||
if (undo.feature) {
|
||||
undo.feature.set('uploadType', null);
|
||||
}
|
||||
}
|
||||
this.undoredo.clear();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [히스토리] 피처 초기화
|
||||
*/
|
||||
clearUndoRedoFeature: function () {
|
||||
if (this.undoredo) {
|
||||
this.setActiveUndo(true);
|
||||
const undoStack = [...this.undoredo._undoStack.array_];
|
||||
for (let i in undoStack) {
|
||||
const undo = undoStack[i];
|
||||
if (undo.feature) {
|
||||
this.undoredo.undo();
|
||||
}
|
||||
}
|
||||
this.setActiveUndo(false);
|
||||
this.undoredo.clear();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [삭제] 피처 삭제
|
||||
*/
|
||||
deleteFeature: function () {
|
||||
//초기화
|
||||
this.removeDraw();
|
||||
this.removeModify();
|
||||
this.removeTransform();
|
||||
|
||||
//삭제 기능 실행
|
||||
let me = this;
|
||||
let core = GisApp.Module.core;
|
||||
let select = GisApp.Module.select;
|
||||
select.getSelectedFeatures().forEach(function (selectedFeature) {
|
||||
let selectedFeatureId = selectedFeature.getId();
|
||||
let layers = core.map.getLayers();
|
||||
for (let i in layers.array_) {
|
||||
let layer = layers.array_[i];
|
||||
if (layer.get('mdfcnYn') === 'Y' && select.getVisible(layer)) {
|
||||
layer
|
||||
.getSource()
|
||||
.getFeatures()
|
||||
.forEach(function (feature) {
|
||||
let featureId = feature.getId();
|
||||
if (selectedFeatureId === featureId) {
|
||||
me.setActiveUndo(true);
|
||||
feature.setProperties({ lyr_id: layer.get('id') });
|
||||
layer.getSource().removeFeature(feature);
|
||||
me.setRemoveFeatureUndo(layer, feature);
|
||||
me.setActiveUndo(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [수정] 피처 수정 기능 셋팅
|
||||
*/
|
||||
setModify: function () {
|
||||
//초기화
|
||||
this.removeDraw();
|
||||
this.removeTransform();
|
||||
|
||||
//수정기능 추가
|
||||
let me = this;
|
||||
let core = GisApp.Module.core;
|
||||
let select = GisApp.Module.select;
|
||||
let selectedFeatures = select.getSelectedFeatures();
|
||||
|
||||
this.modify = new Modify({
|
||||
features: selectedFeatures,
|
||||
});
|
||||
core.map.addInteraction(this.modify);
|
||||
|
||||
select.selectInteraction.on('change:active', function () {
|
||||
selectedFeatures.forEach(function (each) {
|
||||
selectedFeatures.remove(each);
|
||||
});
|
||||
});
|
||||
|
||||
this.modify.on('modifystart', function (event) {});
|
||||
|
||||
this.modify.on('modifyend', function (event) {
|
||||
let features = event.features.getArray();
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
let feature = features[i];
|
||||
let layer = select.selectInteraction.getLayer(feature);
|
||||
feature.setProperties({ lyr_id: layer.get('id') });
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* [수정] 피처 수정 기능 제거
|
||||
*/
|
||||
removeModify: function () {
|
||||
if (this.modify) {
|
||||
let core = GisApp.Module.core;
|
||||
core.map.removeInteraction(this.modify);
|
||||
this.modify = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [수정] 기능 활성화
|
||||
*/
|
||||
setModifyActive: function (active) {
|
||||
if (this.modify) {
|
||||
this.modify.setActive(active);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [크기조절] 피처 크기조절 기능 셋팅
|
||||
*/
|
||||
setTransform: function () {
|
||||
//초기화
|
||||
this.removeDraw();
|
||||
this.removeModify();
|
||||
|
||||
let me = this;
|
||||
let core = GisApp.Module.core;
|
||||
let select = GisApp.Module.select;
|
||||
|
||||
this.transform = new Transform({
|
||||
enableRotatedTransform: false,
|
||||
addCondition: click,
|
||||
filter: function (f, l) {
|
||||
return f.getGeometry().getType() !== 'Point';
|
||||
},
|
||||
// layers: [vector],
|
||||
hitTolerance: select.defaults.hitTolerance,
|
||||
translateFeature: true,
|
||||
scale: true,
|
||||
rotate: true,
|
||||
keepAspectRatio: always,
|
||||
translate: true,
|
||||
stretch: true,
|
||||
});
|
||||
this.transform.on(['rotateend', 'translateend', 'scaleend'], function (event) {
|
||||
let features = event.features.getArray();
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
let feature = features[i];
|
||||
let layer = feature.getLayer();
|
||||
feature.setProperties({ lyr_id: layer.get('id') });
|
||||
}
|
||||
});
|
||||
|
||||
core.map.addInteraction(this.transform);
|
||||
},
|
||||
/**
|
||||
* [크기조절] 피처 크기조절 기능 제거
|
||||
*/
|
||||
removeTransform: function () {
|
||||
if (this.transform) {
|
||||
let core = GisApp.Module.core;
|
||||
core.map.removeInteraction(this.transform);
|
||||
this.transform = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [크기조절] 피처 크기조절 기능 활성화
|
||||
*/
|
||||
setTransformActive: function (active) {
|
||||
if (this.transform) {
|
||||
this.transform.setActive(active);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [스냅] 기능 추가
|
||||
*/
|
||||
setSnap: function (active) {
|
||||
//초기화
|
||||
this.removeSnap();
|
||||
|
||||
//스냅 기능 추가
|
||||
let core = GisApp.Module.core;
|
||||
let select = GisApp.Module.select;
|
||||
core.map.removeInteraction(this.snap);
|
||||
let features = [];
|
||||
let layers = core.map.getLayers();
|
||||
for (let i in layers.array_) {
|
||||
let layer = layers.array_[i];
|
||||
if (select.getVisible(layer) && layer.get('type') !== 'base') {
|
||||
if (layer.getSource().getFeatures().length > 0) {
|
||||
features = features.concat(layer.getSource().getFeatures());
|
||||
}
|
||||
}
|
||||
}
|
||||
let snapCollection = new Collection(features);
|
||||
this.snap = new Snap({
|
||||
features: snapCollection,
|
||||
pixelTolerance: 30,
|
||||
});
|
||||
core.map.addInteraction(this.snap);
|
||||
},
|
||||
/**
|
||||
* [스냅] 기능 제거
|
||||
*/
|
||||
removeSnap: function () {
|
||||
if (this.snap) {
|
||||
let core = GisApp.Module.core;
|
||||
core.map.removeInteraction(this.snap);
|
||||
this.snap = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [스냅] 스냅에 피처 추가
|
||||
*/
|
||||
addSnap: function (layers) {
|
||||
let me = this;
|
||||
if (me.snap) {
|
||||
for (let i in layers) {
|
||||
let source = layers[i].getSource();
|
||||
source.listeners_.addfeature = null;
|
||||
source.on('addfeature', function (event) {
|
||||
me.snap.addFeature(event.feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* [스냅] 기능 활성화
|
||||
*/
|
||||
setSnapActive: function (active) {
|
||||
if (this.snap) {
|
||||
this.snap.setActive(active);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* [저장] 피처 수정 정보 셋팅
|
||||
*/
|
||||
setEditFeatureInfo: function (flag, feature) {
|
||||
const featureId = feature.getId();
|
||||
const properties = feature.getProperties();
|
||||
|
||||
//신규 추가 후 삭제시에 editFeatureInfo에서 제거
|
||||
if (properties.uploadType === 'addFeature' && flag === 'delete') {
|
||||
delete this.editFeatureInfo[featureId];
|
||||
return;
|
||||
}
|
||||
|
||||
//좌표값 WKT포멧으로 변경
|
||||
const pGeom = GisApp.Module.core.convertFeatureGeomToWKT(feature);
|
||||
const param = {
|
||||
flag,
|
||||
...properties,
|
||||
geom: pGeom,
|
||||
};
|
||||
this.editFeatureInfo[featureId] = param;
|
||||
},
|
||||
getEditFeatureInfo: function () {
|
||||
const undoStack = this.undoredo._undoStack.array_;
|
||||
this.editFeatureInfo = {};
|
||||
for (let i in undoStack) {
|
||||
const undo = undoStack[i];
|
||||
if (undo.feature) {
|
||||
const feature = undo.feature;
|
||||
const properties = feature.getProperties();
|
||||
if (properties.uploadType === 'addFeature') {
|
||||
this.setEditFeatureInfo('insert', feature);
|
||||
} else if (properties.uploadType === 'removeFeature') {
|
||||
this.setEditFeatureInfo('delete', feature);
|
||||
} else if (undo.type === 'changegeometry') {
|
||||
this.setEditFeatureInfo('update', feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.editFeatureInfo;
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlEditLayer;
|
||||
@@ -0,0 +1,557 @@
|
||||
import Feature from 'ol/Feature.js';
|
||||
import GeoJSON from 'ol/format/GeoJSON.js';
|
||||
import Point from 'ol/geom/Point.js';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorImageLayer from 'ol/layer/VectorImage.js';
|
||||
import { all, bbox } from 'ol/loadingstrategy';
|
||||
import { transform } from 'ol/proj';
|
||||
import Cluster from 'ol/source/Cluster.js';
|
||||
import VectorSource from 'ol/source/Vector.js';
|
||||
import VectorTile from 'ol/source/VectorTile.js';
|
||||
import CircleStyle from 'ol/style/Circle.js';
|
||||
import Fill from 'ol/style/Fill.js';
|
||||
import Icon from 'ol/style/Icon.js';
|
||||
import Stroke from 'ol/style/Stroke.js';
|
||||
import Style from 'ol/style/Style.js';
|
||||
import Text from 'ol/style/Text.js';
|
||||
import { createXYZ } from 'ol/tilegrid';
|
||||
import GisApp from './gis.core.js';
|
||||
import GisOlStyle from './gis.style.js';
|
||||
import Tile from 'ol/layer/Tile.js';
|
||||
import TileWMS from 'ol/source/TileWMS.js';
|
||||
import gisCore from './gis.core.js';
|
||||
|
||||
/**
|
||||
* GisApp.layer
|
||||
* @requires gis.core.js
|
||||
* @requires gis.style.js
|
||||
*/
|
||||
const GisOlLayer = function (options) {
|
||||
this.initialize(options);
|
||||
};
|
||||
|
||||
GisOlLayer.prototype = {
|
||||
layers: [],
|
||||
cesiumLayers: [],
|
||||
defaults: {},
|
||||
storageNm: 'hasudo:',
|
||||
defaultLayerOptions: {
|
||||
name: '',
|
||||
zIndex: 2,
|
||||
opacity: 1,
|
||||
flterSql: null,
|
||||
minZoom: '15',
|
||||
},
|
||||
initialize: function (options) {
|
||||
Object.assign(this.defaults, options);
|
||||
this.gisStyle = new GisOlStyle();
|
||||
GisApp.Module.style = this.gisStyle;
|
||||
},
|
||||
gisStyle: null,
|
||||
/**
|
||||
* [사용]빈 레이어 생성
|
||||
*/
|
||||
makeLayer: function (options) {
|
||||
let layerOptions = {
|
||||
...this.defaultLayerOptions,
|
||||
...options,
|
||||
minZoom: options?.minViewSize ? options?.minViewSize : options?.minZoom,
|
||||
zIndex: options?.drawOrdr ? options?.drawOrdr : 1,
|
||||
};
|
||||
let returnLayer = new VectorImageLayer(layerOptions);
|
||||
this.layers.push(returnLayer);
|
||||
return returnLayer;
|
||||
},
|
||||
/**
|
||||
* gisStyle 반환
|
||||
*/
|
||||
getGisStyle: function () {
|
||||
return this.gisStyle;
|
||||
},
|
||||
/**
|
||||
* [사용]WFS 벡터 소스 반환
|
||||
*/
|
||||
getVectorSourceGeoserver: function (options) {
|
||||
const url = GisApp.GEOSERVER_URL;
|
||||
const toProj = GisApp.TO_PROJ;
|
||||
const lyrTyNm = options.lyrTyNm;
|
||||
const params = {};
|
||||
params['TYPENAME'] = this.storageNm + lyrTyNm;
|
||||
params['SERVICE'] = 'WFS';
|
||||
params['VERSION'] = '1.1.0';
|
||||
params['REQUEST'] = 'GetFeature';
|
||||
params['SRS'] = toProj;
|
||||
params['OUTPUTFORMAT'] = 'application/json';
|
||||
if (options?.flterSql && options?.flterSql?.trim() !== 'null') {
|
||||
params['CQL_FILTER'] = options?.flterSql;
|
||||
}
|
||||
const strParams = new URLSearchParams(params).toString();
|
||||
let source = new VectorSource({
|
||||
format: new GeoJSON(),
|
||||
url: function (extent) {
|
||||
return url + '?' + strParams;
|
||||
},
|
||||
strategy: all,
|
||||
});
|
||||
return source;
|
||||
},
|
||||
/**
|
||||
* [사용]WFS 벡터 소스 반환
|
||||
*/
|
||||
getVectorTileSource: function (options) {
|
||||
let url = GisApp.GEOSERVER_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let params = {};
|
||||
params['TYPENAME'] = this.storageNm + options.typeName;
|
||||
params['SERVICE'] = 'WFS';
|
||||
params['VERSION'] = '1.1.0';
|
||||
params['REQUEST'] = 'GetFeature';
|
||||
params['SRS'] = toProj;
|
||||
params['OUTPUTFORMAT'] = 'application/json';
|
||||
let strParams = new URLSearchParams(params).toString();
|
||||
|
||||
let source = new VectorTile({
|
||||
format: new GeoJSON(),
|
||||
|
||||
tileUrlFunction: function (tileCoord, pixelRatio, projection) {
|
||||
let filterStr = '&CQL_FILTER=bbox(GEOM,' + source.getTileGrid().getTileCoordExtent(tileCoord).join(',') + ",'" + projection.getCode() + "')";
|
||||
if (options.flterSql) {
|
||||
filterStr += ' AND ' + options.flterSql;
|
||||
}
|
||||
let paramWithUrl = url + '?' + strParams + filterStr;
|
||||
return paramWithUrl;
|
||||
},
|
||||
tileGrid: createXYZ,
|
||||
});
|
||||
|
||||
return source;
|
||||
},
|
||||
/**
|
||||
* [사용]WFS 레이어 생성
|
||||
*/
|
||||
getWfsLayer: function (option) {
|
||||
option = option || {};
|
||||
//레이어 생성
|
||||
let returnLayer = this.makeLayer(option);
|
||||
//레이어 소스 생성
|
||||
let source = this.getVectorSourceGeoserver(option);
|
||||
returnLayer.setSource(source);
|
||||
//스타일 추가
|
||||
returnLayer.setStyle(this.gisStyle.getLayerStyleFunction(returnLayer, option));
|
||||
returnLayer.set('id', option.id + '');
|
||||
return returnLayer;
|
||||
},
|
||||
/**
|
||||
* [사용]WMS 레이어 생성
|
||||
*/
|
||||
getWmsLayer: function (options) {
|
||||
let layerOptions = Object.assign({}, this.defaultLayerOptions, options);
|
||||
let url = GisApp.GEOSERVER_WMS_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
const lyrTyNm = options.lyrTyNm;
|
||||
let params = {};
|
||||
params['LAYERS'] = this.storageNm + lyrTyNm;
|
||||
params['SERVICE'] = 'WMS';
|
||||
params['VERSION'] = '1.1.1';
|
||||
params['REQUEST'] = 'GetMap';
|
||||
params['SRS'] = toProj;
|
||||
params['FORMAT'] = 'image/png';
|
||||
params['TILED'] = true;
|
||||
const wmsLayer = new Tile({
|
||||
...layerOptions,
|
||||
id: layerOptions?.id,
|
||||
source: new TileWMS({
|
||||
url,
|
||||
params,
|
||||
serverType: 'geoserver',
|
||||
}),
|
||||
});
|
||||
wmsLayer.setZIndex(options.draw0rdr);
|
||||
wmsLayer.setMinZoom(options.minViewSize);
|
||||
this.layers.push(wmsLayer);
|
||||
return wmsLayer;
|
||||
},
|
||||
/**
|
||||
* [사용]WFS 레이어 생성
|
||||
*/
|
||||
getWfsEmptyLayer: function (option) {
|
||||
option = option || {};
|
||||
//레이어 생성
|
||||
let returnLayer = this.makeLayer(option);
|
||||
let vectorSource = new VectorSource({
|
||||
strategy: all,
|
||||
});
|
||||
returnLayer.setSource(vectorSource);
|
||||
//스타일 추가
|
||||
returnLayer.setStyle(this.gisStyle.getLayerStyle(returnLayer, option));
|
||||
returnLayer.set('id', option.id + '');
|
||||
let map = GisApp.Module.core.map;
|
||||
map.addLayer(returnLayer);
|
||||
return returnLayer;
|
||||
},
|
||||
/**
|
||||
* 피처 리스트로 레이어 생성
|
||||
*/
|
||||
getLayerByFeature: function (feature, layerOptions, layerStyle) {
|
||||
if (!layerOptions || !layerOptions.id) {
|
||||
return null;
|
||||
}
|
||||
let vectorSource = new VectorSource({
|
||||
strategy: all,
|
||||
});
|
||||
if (feature) {
|
||||
if (Object.prototype.toString.call(feature).slice(8, -1).indexOf('Object') > -1) {
|
||||
vectorSource.addFeature(feature);
|
||||
} else {
|
||||
vectorSource.addFeatures(feature);
|
||||
}
|
||||
}
|
||||
GisApp.Module.core.deleteLayer(layerOptions.id);
|
||||
layerOptions.source = vectorSource;
|
||||
let layer = new VectorImageLayer(layerOptions);
|
||||
layer.setStyle(layerStyle);
|
||||
layer.set('id', layerOptions.id + '');
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* 피처 하이라이트
|
||||
*/
|
||||
highLightFeature: function (hlFeature, layerOptions, styleOptions, customStyleFunction) {
|
||||
const color = styleOptions.color || 'rgba(135, 116, 243, 0.4)';
|
||||
const width = styleOptions.width || 10;
|
||||
const featureKey = styleOptions.featureKey || '';
|
||||
const fitExtent = layerOptions.fitExtent || 'true';
|
||||
|
||||
const styleFunction = function (feature, resolution) {
|
||||
const featureStyle = feature.getStyle();
|
||||
if (featureStyle) return null;
|
||||
const style = new Style({
|
||||
image: new CircleStyle({
|
||||
fill: new Fill({
|
||||
color: color,
|
||||
}),
|
||||
radius: width,
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: color,
|
||||
width: width,
|
||||
}),
|
||||
});
|
||||
|
||||
const getFeatureText = function (feature) {
|
||||
if (featureKey) {
|
||||
let text = feature.get(featureKey);
|
||||
if (text) return text;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const text = new Text({
|
||||
stroke: new Stroke({
|
||||
color: 'white',
|
||||
width: 2,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'black',
|
||||
}),
|
||||
font: '12px Verdana',
|
||||
text: getFeatureText(feature),
|
||||
});
|
||||
style.setText(text);
|
||||
return style;
|
||||
};
|
||||
let actualStyleFunction = customStyleFunction || styleFunction;
|
||||
GisApp.Module.core.deleteLayer(layerOptions.id);
|
||||
let layer = this.getLayerByFeature(hlFeature, layerOptions, actualStyleFunction);
|
||||
if (hlFeature && hlFeature.length > 0) {
|
||||
let map = GisApp.Module.core.map;
|
||||
map.addLayer(layer);
|
||||
if (fitExtent === 'true') {
|
||||
GisApp.Module.core.fitExtent(layer.getSource().getExtent());
|
||||
}
|
||||
}
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* 마커 레이어 맵에 추가
|
||||
*/
|
||||
addMarkerLayer: function (lat, lon, fromProj, layerOptions) {
|
||||
let p = transform([Number(lon), Number(lat)], fromProj, GisApp.TO_PROJ);
|
||||
let feature = new Feature({
|
||||
geometry: new Point(p),
|
||||
});
|
||||
let layer = this.getMarkerLayer(layerOptions);
|
||||
layer.getSource().addFeature(feature);
|
||||
let map = GisApp.Module.core.map;
|
||||
map.addLayer(layer);
|
||||
GisApp.Module.core.fitExtent(feature.getGeometry().getExtent());
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* 마커 레이어 생성
|
||||
*/
|
||||
getMarkerLayer: function (layerOptions) {
|
||||
let imgSrc = GisApp.CONTEXT_PATH + 'images/marker/marker-icon.png';
|
||||
let style = new Style({
|
||||
image: new Icon({
|
||||
anchor: [0.5, 46],
|
||||
anchorXUnits: 'fraction',
|
||||
anchorYUnits: 'pixels',
|
||||
src: imgSrc,
|
||||
}),
|
||||
});
|
||||
let layer = this.getLayerByFeature(null, layerOptions, style);
|
||||
return layer;
|
||||
},
|
||||
/**
|
||||
* 피처 생성
|
||||
*/
|
||||
getFeatureCoord: function (coord) {
|
||||
let feature = new Feature({
|
||||
geometry: new Point(coord),
|
||||
});
|
||||
return feature;
|
||||
},
|
||||
/**
|
||||
* 피처 생성(lat,lon)
|
||||
*/
|
||||
getFeatureLonLat: function (lat, lon) {
|
||||
let p = transform([Number(lon), Number(lat)], GisApp.FROM_PROJ, GisApp.TO_PROJ);
|
||||
let feature = new Feature({
|
||||
geometry: new Point(p),
|
||||
});
|
||||
return feature;
|
||||
},
|
||||
addPolygonLayer: function (features, option) {
|
||||
const map = GisApp.Module.core.map;
|
||||
option = Object.assign(
|
||||
{
|
||||
gobjTy: 'lineLabelPoligon',
|
||||
minZoom: option?.minZoom || '10',
|
||||
lblAtrbNm: option?.lblAtrbNm || 'fclty_nm',
|
||||
lnWdth: option?.lnWdth || '2',
|
||||
lnColor: option?.lnColor || 'rgba(0, 0, 0, 1)',
|
||||
polgnColor: option?.polgnColor || 'rgba(255, 0, 0, 0.2)',
|
||||
clickPsblYn: option?.clickPsblYn || 'N',
|
||||
fondNm: '18px "맑은 고딕",Malgun Gothic',
|
||||
},
|
||||
option
|
||||
);
|
||||
features[0].set('overLayId', option?.overlayId || null);
|
||||
features[0].set('html', option?.html || null);
|
||||
//레이어 생성
|
||||
let vectorLayer = this.makeLayer(option);
|
||||
//레이어 소스 생성
|
||||
const vectorSource = new VectorSource({});
|
||||
vectorSource.addFeatures(features);
|
||||
vectorLayer.setSource(vectorSource);
|
||||
//스타일 추가
|
||||
vectorLayer.setStyle(this.gisStyle.getLayerStyleFunction(vectorLayer, option));
|
||||
vectorLayer.set('id', option?.id + '');
|
||||
GisApp.Module.core.deleteLayer(option?.id);
|
||||
map.addLayer(vectorLayer);
|
||||
},
|
||||
addLayerByLyrTyNm: function (id, lyrTyNm, addQurryFlter) {
|
||||
const map = GisApp.Module.core.map;
|
||||
const layerCode = GisApp.layerCode;
|
||||
const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm);
|
||||
matched['id'] = id;
|
||||
let layer = GisApp.Module.core.getLayerById(matched?.id);
|
||||
if (layer) {
|
||||
layer.setVisible(true);
|
||||
} else {
|
||||
const queryFlter = layerCode?.flterSql || '' + ' ' + (addQurryFlter || '');
|
||||
matched['flterSql'] = queryFlter;
|
||||
layer = this.getWfsLayer(matched);
|
||||
GisApp.Module.core.deleteLayer(id);
|
||||
map.addLayer(layer);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 클러스터 레이어 맵에 추가
|
||||
*/
|
||||
addClusterLayer: function (features, { layerId, color }) {
|
||||
const map = GisApp.Module.core.map;
|
||||
const vectorSource = new VectorSource({});
|
||||
vectorSource.addFeatures(features);
|
||||
const clusterLayerId = layerId + 'Cluster';
|
||||
const vectorLayerId = layerId + 'Vector';
|
||||
// 클러스터 레이어 생성
|
||||
const clusterLayer = new VectorLayer({
|
||||
id: clusterLayerId,
|
||||
source: new Cluster({
|
||||
distance: 100, // 클러스터링 거리
|
||||
source: vectorSource,
|
||||
geometryFunction: function (feature) {
|
||||
let geometry = feature.getGeometry();
|
||||
let type = geometry.getType();
|
||||
let coordinates;
|
||||
if (type === 'Point') {
|
||||
coordinates = geometry.getCoordinates();
|
||||
} else if (type === 'LineString' || type === 'MultiLineString') {
|
||||
let totalX = 0;
|
||||
let totalY = 0;
|
||||
let totalPoints = 0;
|
||||
geometry.getCoordinates().forEach(line => {
|
||||
line.forEach(point => {
|
||||
totalX += point[0];
|
||||
totalY += point[1];
|
||||
totalPoints++;
|
||||
});
|
||||
});
|
||||
coordinates = [totalX / totalPoints, totalY / totalPoints];
|
||||
}
|
||||
return new Point(coordinates);
|
||||
},
|
||||
}),
|
||||
style: feature => {
|
||||
const clusterSize = feature.get('features').length;
|
||||
return new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 10,
|
||||
fill: new Fill({ color }),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color,
|
||||
width: 10,
|
||||
}),
|
||||
text: new Text({
|
||||
text: clusterSize.toString(),
|
||||
fill: new Fill({
|
||||
color: 'white',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'black',
|
||||
width: 4,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const vectorLayer = new VectorLayer({
|
||||
id: vectorLayerId,
|
||||
zIndex: 1,
|
||||
source: vectorSource,
|
||||
style: new Style({
|
||||
image: new CircleStyle({
|
||||
radius: 10,
|
||||
fill: new Fill({ color }),
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color,
|
||||
width: 10,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
GisApp.Module.core.deleteLayer(clusterLayerId);
|
||||
GisApp.Module.core.deleteLayer(vectorLayerId);
|
||||
map.addLayer(clusterLayer);
|
||||
map.addLayer(vectorLayer);
|
||||
function handleZoomEnd() {
|
||||
const findClusterLayer = map
|
||||
?.getLayers()
|
||||
?.getArray()
|
||||
.find(function (layer) {
|
||||
return layer.get('id') === clusterLayerId;
|
||||
});
|
||||
const findVectorLayer = map
|
||||
?.getLayers()
|
||||
?.getArray()
|
||||
.find(function (layer) {
|
||||
return layer.get('id') === vectorLayerId;
|
||||
});
|
||||
if (!findClusterLayer) {
|
||||
GisApp.Module.core.deleteLayer(vectorLayerId);
|
||||
}
|
||||
if (!findVectorLayer) {
|
||||
GisApp.Module.core.deleteLayer(vectorLayerId);
|
||||
}
|
||||
const zoomLevel = map.getView().getZoom();
|
||||
if (zoomLevel >= 18) {
|
||||
vectorLayer.setVisible(true);
|
||||
clusterLayer.setVisible(false);
|
||||
} else {
|
||||
clusterLayer.setVisible(true);
|
||||
vectorLayer.setVisible(false);
|
||||
}
|
||||
if (!findClusterLayer && !findVectorLayer) {
|
||||
map.getView().un('change:resolution', handleZoomEnd);
|
||||
}
|
||||
}
|
||||
// 줌 이벤트 핸들러 등록
|
||||
map.getView().un('change:resolution', handleZoomEnd);
|
||||
map.getView().on('change:resolution', handleZoomEnd);
|
||||
handleZoomEnd();
|
||||
},
|
||||
setLayersToCesium: function (ol3dInstance) {
|
||||
let me = this;
|
||||
//const { Cesium } = window;
|
||||
const cesiumScene = ol3dInstance.getCesiumScene(); // Cesium Scene 가져오기
|
||||
me.cesiumLayers.forEach(cesiumLayer => {
|
||||
cesiumScene.imageryLayers.remove(cesiumLayer);
|
||||
});
|
||||
const layers = GisApp.Module.core.map
|
||||
.getAllLayers()
|
||||
.filter(layer => layer.get('type') !== 'base')
|
||||
.sort((a, b) => {
|
||||
const orderA = a.getProperties().drawOrdr || 0;
|
||||
const orderB = b.getProperties().drawOrdr || 0;
|
||||
return orderA - orderB; // 낮은 값이 먼저 오도록 정렬
|
||||
});
|
||||
|
||||
layers.forEach(layer => {
|
||||
const properties = layer.getProperties();
|
||||
let cesiumLayer = null;
|
||||
if (properties?.lyrTyNm) {
|
||||
const visible = layer.getVisible();
|
||||
const lyrTyNm = properties.lyrTyNm;
|
||||
// OpenLayers에서 레이어의 가시성 확인
|
||||
const { Cesium } = window;
|
||||
const wmsUrl = GisApp.GEOSERVER_WMS_URL;
|
||||
const wmsProvider = new Cesium.WebMapServiceImageryProvider({
|
||||
url: wmsUrl,
|
||||
layers: GisApp.Module.layer.storageNm + lyrTyNm, // WMS 레이어 이름
|
||||
parameters: {
|
||||
service: 'WMS',
|
||||
version: '1.1.1',
|
||||
request: 'GetMap',
|
||||
format: 'image/png',
|
||||
transparent: true,
|
||||
styles: lyrTyNm,
|
||||
},
|
||||
});
|
||||
cesiumLayer = cesiumScene.imageryLayers.addImageryProvider(wmsProvider);
|
||||
cesiumLayer['properties'] = properties;
|
||||
me.cesiumLayers.push(cesiumLayer);
|
||||
if (properties?.visible) {
|
||||
const zoomLevel = getOpenLayersZoom(cesiumScene);
|
||||
cesiumLayer.show = zoomLevel >= Number(properties?.minZoom);
|
||||
} else {
|
||||
cesiumLayer.show = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (me.cesiumLayers.length > 0) {
|
||||
cesiumScene._moveEndListener = () => {
|
||||
const zoomLevel = getOpenLayersZoom(cesiumScene); // 대략적인 줌 계산
|
||||
me.cesiumLayers.forEach(cesiumLayer => {
|
||||
const properties = cesiumLayer.properties;
|
||||
if (properties?.visible) {
|
||||
console.log('zoomLevel >= minZoom==', properties?.lyrTyNm, zoomLevel >= properties?.minZoom);
|
||||
cesiumLayer.show = zoomLevel >= Number(properties?.minZoom);
|
||||
}
|
||||
});
|
||||
};
|
||||
cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function getOpenLayersZoom(cesiumScene) {
|
||||
const cameraHeight = cesiumScene.camera.positionCartographic.height;
|
||||
const maxResolution = 156543.03392804097; // OpenLayers 기본 해상도 (Google Mercator 기준)
|
||||
const tileSize = 256; // 기본 타일 크기
|
||||
return Math.ceil(Math.log2(maxResolution / (cameraHeight / tileSize)));
|
||||
}
|
||||
|
||||
export default GisOlLayer;
|
||||
@@ -0,0 +1,204 @@
|
||||
import Style from 'ol/style/Style';
|
||||
import VectorLayer from 'ol/layer/Vector';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import Fill from 'ol/style/Fill.js';
|
||||
import Stroke from 'ol/style/Stroke';
|
||||
import CircleStyle from 'ol/style/Circle';
|
||||
import { getLength, getArea } from 'ol/sphere';
|
||||
import Draw from 'ol/interaction/Draw';
|
||||
import Polygon from 'ol/geom/Polygon';
|
||||
import LineString from 'ol/geom/LineString';
|
||||
import { unByKey } from 'ol/Observable';
|
||||
import Overlay from 'ol/Overlay';
|
||||
import GisApp from './gis.core';
|
||||
//import DOMPurify from 'dompurify';
|
||||
const GisOlMeasure = function (options) {
|
||||
GisApp.measure = {};
|
||||
GisApp.measure.helpTooltipElement = null;
|
||||
GisApp.measure.helpTooltip = null;
|
||||
GisApp.measure.sketch = null;
|
||||
GisApp.measure.measureTooltipElement = null;
|
||||
GisApp.measure.measureTooltip = null;
|
||||
GisApp.measure.continueLineMsg = '선 그리기 종료를 위해 마우스를 더블클릭하세요.';
|
||||
GisApp.measure.Polygon = 'Polygon';
|
||||
GisApp.measure.LineString = 'LineString';
|
||||
|
||||
this.initialize(options);
|
||||
};
|
||||
|
||||
GisOlMeasure.prototype = {
|
||||
map: null,
|
||||
draw: null,
|
||||
vector: null,
|
||||
type: null,
|
||||
initialize: function (map) {
|
||||
let me = this;
|
||||
let source = new VectorSource();
|
||||
let vector = new VectorLayer({
|
||||
source: source,
|
||||
style: new Style({
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: '#ffcc33',
|
||||
width: 2,
|
||||
}),
|
||||
image: new CircleStyle({
|
||||
radius: 7,
|
||||
fill: new Fill({
|
||||
color: '#ffcc33',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
me.map = map;
|
||||
me.vector = vector;
|
||||
me.map.addLayer(vector);
|
||||
me.map.removeInteraction(this.draw);
|
||||
},
|
||||
pointerMoveHandler: function (evt) {
|
||||
if (evt.dragging) {
|
||||
return;
|
||||
}
|
||||
let helpMsg = '시작점을 선택하세요';
|
||||
if (GisApp.measure.sketch) {
|
||||
helpMsg = GisApp.measure.continueLineMsg;
|
||||
}
|
||||
if (GisApp.measure.helpTooltipElement) {
|
||||
//GisApp.measure.helpTooltipElement.innerHTML = DOMPurify.sanitize(helpMsg);
|
||||
GisApp.measure.helpTooltipElement.innerHTML = helpMsg;
|
||||
GisApp.measure.helpTooltip.setPosition(evt.coordinate);
|
||||
GisApp.measure.helpTooltipElement.classList.remove('hidden');
|
||||
}
|
||||
},
|
||||
formatLength: function (line) {
|
||||
let length = getLength(line);
|
||||
let output;
|
||||
if (length > 1000) {
|
||||
output = Math.round((length / 1000) * 100) / 100 + ' ' + 'km';
|
||||
} else {
|
||||
output = Math.round(length * 100) / 100 + ' ' + 'm';
|
||||
}
|
||||
return output;
|
||||
},
|
||||
formatArea: function (polygon) {
|
||||
let area = getArea(polygon);
|
||||
let output;
|
||||
if (area > 10000) {
|
||||
output = Math.round((area / 1000000) * 100) / 100 + ' ' + 'km<sup>2</sup>';
|
||||
} else {
|
||||
output = Math.round(area * 100) / 100 + ' ' + 'm<sup>2</sup>';
|
||||
}
|
||||
return output;
|
||||
},
|
||||
addInteraction: function (type) {
|
||||
let me = this;
|
||||
me.map.removeInteraction(me.draw);
|
||||
me.map.on('pointermove', me.pointerMoveHandler);
|
||||
me.type = type;
|
||||
|
||||
let source = me.vector.getSource();
|
||||
me.draw = new Draw({
|
||||
source: source,
|
||||
type: type,
|
||||
style: new Style({
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}),
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(0, 0, 0, 0.5)',
|
||||
lineDash: [10, 10],
|
||||
width: 2,
|
||||
}),
|
||||
image: new CircleStyle({
|
||||
radius: 5,
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(0, 0, 0, 0.7)',
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: 'rgba(255, 255, 255, 0.2)',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
me.map.addInteraction(me.draw);
|
||||
me.createMeasureTooltip();
|
||||
me.createHelpTooltip();
|
||||
|
||||
let listener;
|
||||
me.draw.on('drawstart', function (evt) {
|
||||
let tooltipCoord = evt.coordinate;
|
||||
GisApp.measure.sketch = evt.feature;
|
||||
listener = GisApp.measure.sketch.getGeometry().on('change', function (evt) {
|
||||
let geom = evt.target;
|
||||
let output;
|
||||
if (geom instanceof Polygon) {
|
||||
output = me.formatArea(geom);
|
||||
tooltipCoord = geom.getInteriorPoint().getCoordinates();
|
||||
} else if (geom instanceof LineString) {
|
||||
output = me.formatLength(geom);
|
||||
tooltipCoord = geom.getLastCoordinate();
|
||||
}
|
||||
//GisApp.measure.measureTooltipElement.innerHTML = DOMPurify.sanitize(output);
|
||||
GisApp.measure.measureTooltipElement.innerHTML = output;
|
||||
GisApp.measure.measureTooltip.setPosition(tooltipCoord);
|
||||
});
|
||||
});
|
||||
|
||||
me.draw.on('drawend', function () {
|
||||
GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-static';
|
||||
GisApp.measure.measureTooltip.setOffset([0, -7]);
|
||||
GisApp.measure.sketch = null;
|
||||
// unset tooltip so that a new one can be created
|
||||
GisApp.measure.measureTooltipElement = null;
|
||||
me.createMeasureTooltip();
|
||||
unByKey(listener);
|
||||
me.removeInteraction();
|
||||
});
|
||||
},
|
||||
removeInteraction: function () {
|
||||
this.map.removeInteraction(this.draw);
|
||||
this.map.removeOverlay(GisApp.measure.helpTooltip);
|
||||
},
|
||||
removeFeature: function () {
|
||||
let source = this.vector.getSource();
|
||||
let features = this.vector.getSource().getFeatures();
|
||||
for (let i in features) {
|
||||
let feature = features[i];
|
||||
source.removeFeature(feature);
|
||||
}
|
||||
},
|
||||
createHelpTooltip: function () {
|
||||
let me = this;
|
||||
if (GisApp.measure.helpTooltipElement) {
|
||||
GisApp.measure.helpTooltipElement.parentNode.removeChild(GisApp.measure.helpTooltipElement);
|
||||
}
|
||||
GisApp.measure.helpTooltipElement = document.createElement('div');
|
||||
GisApp.measure.helpTooltipElement.className = 'ol-tooltip hidden';
|
||||
GisApp.measure.helpTooltip = new Overlay({
|
||||
element: GisApp.measure.helpTooltipElement,
|
||||
offset: [15, 0],
|
||||
positioning: 'center-left',
|
||||
});
|
||||
me.map.addOverlay(GisApp.measure.helpTooltip);
|
||||
},
|
||||
createMeasureTooltip: function () {
|
||||
let me = this;
|
||||
if (GisApp.measure.measureTooltipElement) {
|
||||
GisApp.measure.measureTooltipElement.parentNode.removeChild(GisApp.measure.measureTooltipElement);
|
||||
}
|
||||
GisApp.measure.measureTooltipElement = document.createElement('div');
|
||||
GisApp.measure.measureTooltipElement.className = 'ol-tooltip ol-tooltip-measure';
|
||||
GisApp.measure.measureTooltip = new Overlay({
|
||||
id: 'measure',
|
||||
element: GisApp.measure.measureTooltipElement,
|
||||
offset: [0, -15],
|
||||
positioning: 'bottom-center',
|
||||
});
|
||||
me.map.addOverlay(GisApp.measure.measureTooltip);
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlMeasure;
|
||||
@@ -0,0 +1,436 @@
|
||||
import GisApp from './gis.core';
|
||||
import _ from 'lodash';
|
||||
import Style from 'ol/style/Style.js';
|
||||
import { click, mouseOnly, never } from 'ol/events/condition';
|
||||
import DragBox from 'ol/interaction/DragBox.js';
|
||||
import Draw from 'ol/interaction/Draw.js';
|
||||
import Select from 'ol/interaction/Select.js';
|
||||
import VectorLayer from 'ol/layer/Vector.js';
|
||||
import Overlay from 'ol/Overlay.js';
|
||||
import VectorSource from 'ol/source/Vector.js';
|
||||
import CircleStyle from 'ol/style/Circle.js';
|
||||
import Fill from 'ol/style/Fill.js';
|
||||
import Stroke from 'ol/style/Stroke.js';
|
||||
//import DOMPurify from 'dompurify';
|
||||
|
||||
const GisOlSelect = function (map, options) {
|
||||
GisApp.select = {};
|
||||
GisApp.select.helpTooltipElement = null;
|
||||
GisApp.select.helpTooltip = null;
|
||||
GisApp.select.helpMsg = null;
|
||||
this.initialize(map, options);
|
||||
};
|
||||
|
||||
GisOlSelect.prototype = {
|
||||
map: null,
|
||||
defaults: {
|
||||
hitTolerance: 10,
|
||||
useAreaControl: false,
|
||||
},
|
||||
selectInteraction: null,
|
||||
dragBox: null,
|
||||
dragPolygon: null,
|
||||
selectedFeatures: [],
|
||||
selectFeatureFlag: false,
|
||||
initialize: function (map, options) {
|
||||
this.map = map;
|
||||
_.extend(this.defaults, options);
|
||||
this.setInteraction();
|
||||
this.pointerMoveEventHandler();
|
||||
},
|
||||
/**
|
||||
* SELECT 이벤트 등록
|
||||
*/
|
||||
setInteraction: function () {
|
||||
let me = this;
|
||||
let map = this.map;
|
||||
if (this.selectInteraction) {
|
||||
this.removeInteration();
|
||||
}
|
||||
let baseStyle = new Style({
|
||||
image : new CircleStyle({
|
||||
fill : new Fill({
|
||||
color : 'rgba(135, 116, 243, 0.4)',
|
||||
}),
|
||||
radius : 30
|
||||
}),
|
||||
stroke : new Stroke({
|
||||
color : 'rgba(135, 116, 243, 0.4)',
|
||||
width : 10,
|
||||
})
|
||||
});
|
||||
|
||||
this.selectInteraction = new Select({
|
||||
style: function (feature, resolution) {
|
||||
let styles = [];
|
||||
let layer = feature.getLayer();
|
||||
let styleFn = GisApp.Module.style.getLayerStyleFunction(layer, null);
|
||||
|
||||
if (styleFn) {
|
||||
const style = styleFn(feature, resolution);
|
||||
if (Array.isArray(style)) {
|
||||
styles.push(...style);
|
||||
} else {
|
||||
styles.push(style);
|
||||
}
|
||||
}
|
||||
styles.push(baseStyle);
|
||||
return styles;
|
||||
},
|
||||
layers: function (layer) {
|
||||
let clickPsblYn = layer?.get('clickPsblYn');
|
||||
return clickPsblYn === 'Y';
|
||||
},
|
||||
condition: click,
|
||||
hitTolerance: me.defaults.hitTolerance,
|
||||
});
|
||||
|
||||
map.addInteraction(this.selectInteraction);
|
||||
this.selectedFeatures = this.selectInteraction.getFeatures();
|
||||
|
||||
// 선택된 피처 중심으로 지도 이동
|
||||
this.selectInteraction.on('select', function (e) {
|
||||
const feature = e.selected[0];
|
||||
if (feature) {
|
||||
const geometry = feature.getGeometry();
|
||||
if (geometry) {
|
||||
const extent = geometry.getExtent();
|
||||
map.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 16
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* SELECT 피처 초기화
|
||||
*/
|
||||
reset: function () {
|
||||
let me = this;
|
||||
if (GisApp.Module.edit) {
|
||||
GisApp.Module.edit.setDrawActive(false);
|
||||
}
|
||||
if (GisApp.Module.draw) {
|
||||
GisApp.Module.draw.setDrawActive(false);
|
||||
}
|
||||
me.setActive(true);
|
||||
me.removeHelpTooltip();
|
||||
|
||||
let map = this.map;
|
||||
map.removeInteraction(me.dragBox);
|
||||
map.removeInteraction(me.dragPolygon);
|
||||
|
||||
this.selectInteraction.addCondition_ = never;
|
||||
this.selectFeatureFlag = false;
|
||||
|
||||
if (GisApp.Module.measure) {
|
||||
GisApp.Module.measure.removeFeature();
|
||||
GisApp.Module.core.deleteOverlay('measure');
|
||||
}
|
||||
|
||||
GisApp.Module.core.deleteLayer(GisApp.hLayerId);
|
||||
GisApp.Module.core.deleteLayer(GisApp.hlLayerId);
|
||||
|
||||
this.resetSelectedFeature();
|
||||
},
|
||||
/**
|
||||
* 단일 SELECT 이벤트 등록
|
||||
*/
|
||||
setSingleInteration: function () {
|
||||
this.reset();
|
||||
this.selectInteraction.addCondition_ = click;
|
||||
this.selectFeatureFlag = true;
|
||||
},
|
||||
/**
|
||||
* 박스 SELECT 이벤트 등록
|
||||
*/
|
||||
setBoxInteration: function () {
|
||||
//초기화
|
||||
this.reset();
|
||||
this.selectFeatureFlag = true;
|
||||
|
||||
let me = this;
|
||||
let map = this.map;
|
||||
me.dragBox = new DragBox({
|
||||
condition: mouseOnly,
|
||||
});
|
||||
map.addInteraction(this.dragBox);
|
||||
GisApp.select.helpMsg = '직사각형 시작점을 클릭하고 드래그하세요.';
|
||||
me.createHelpTooltip();
|
||||
me.dragBox.on('boxend', function () {
|
||||
let rotation = map.getView().getRotation();
|
||||
let oblique = rotation % (Math.PI / 2) !== 0;
|
||||
let candidateFeatures = oblique ? [] : me.selectedFeatures;
|
||||
let extent = me.dragBox.getGeometry().getExtent();
|
||||
let layers = map.getLayers();
|
||||
for (let i in layers.array_) {
|
||||
let layer = layers.array_[i];
|
||||
if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) {
|
||||
layer.getSource().forEachFeatureIntersectingExtent(extent, function (feature) {
|
||||
candidateFeatures.push(feature);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (oblique) {
|
||||
let anchor = [0, 0];
|
||||
let geometry = me.dragBox.getGeometry().clone();
|
||||
geometry.rotate(-rotation, anchor);
|
||||
let extent$1 = geometry.getExtent();
|
||||
candidateFeatures.forEach(function (feature) {
|
||||
let geometry = feature.getGeometry().clone();
|
||||
geometry.rotate(-rotation, anchor);
|
||||
if (geometry.intersectsExtent(extent$1)) {
|
||||
me.selectedFeatures.push(feature);
|
||||
}
|
||||
});
|
||||
}
|
||||
me.selectInteraction.dispatchEvent({
|
||||
type: 'select',
|
||||
selected: me.selectedFeatures,
|
||||
deselected: [],
|
||||
});
|
||||
map.removeInteraction(me.dragBox);
|
||||
});
|
||||
|
||||
me.dragBox.on('boxstart', function () {
|
||||
me.removeHelpTooltip();
|
||||
me.selectedFeatures.clear();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 폴리곤 SELECT 이벤트 등록
|
||||
*/
|
||||
setPolygonInteration: function () {
|
||||
this.reset();
|
||||
this.selectFeatureFlag = true;
|
||||
|
||||
let me = this;
|
||||
let map = this.map;
|
||||
|
||||
//폴리곤 레이어 생성
|
||||
let polygonSource = new VectorSource({
|
||||
useSpatialIndex: false,
|
||||
});
|
||||
let polygonLayer = new VectorLayer({
|
||||
source: polygonSource,
|
||||
});
|
||||
map.addLayer(polygonLayer);
|
||||
|
||||
//폴리곤 draw 객체 생성
|
||||
me.dragPolygon = new Draw({
|
||||
source: polygonSource,
|
||||
type: 'Polygon',
|
||||
condition: mouseOnly,
|
||||
});
|
||||
map.addInteraction(me.dragPolygon);
|
||||
|
||||
//툴팁 추가
|
||||
GisApp.select.helpMsg = '시작점을 클릭하세요.';
|
||||
me.createHelpTooltip();
|
||||
|
||||
me.dragPolygon.on('drawstart', function () {
|
||||
me.removeHelpTooltip();
|
||||
me.selectedFeatures.clear();
|
||||
});
|
||||
|
||||
me.dragPolygon.on('drawend', function (evt) {
|
||||
setTimeout(function () {
|
||||
let polygon = evt.feature.getGeometry();
|
||||
let layers = map.getLayers();
|
||||
for (let i in layers.array_) {
|
||||
let layer = layers.array_[i];
|
||||
if (layer?.get('clickPsblYn') == 'Y' && me.getVisible(layer)) {
|
||||
let features = layer.getSource().getFeatures();
|
||||
for (let j = 0; j < features.length; j++) {
|
||||
if (polygon.intersectsExtent(features[j].getGeometry().getExtent())) {
|
||||
me.selectedFeatures.push(features[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
me.selectInteraction.dispatchEvent({
|
||||
type: 'select',
|
||||
selected: me.selectedFeatures,
|
||||
deselected: [],
|
||||
});
|
||||
map.removeInteraction(me.dragPolygon);
|
||||
map.removeLayer(polygonLayer);
|
||||
}, 300);
|
||||
});
|
||||
return me.dragPolygon;
|
||||
},
|
||||
/**
|
||||
* 레이어 추가
|
||||
*/
|
||||
addLayer: function (layer) {
|
||||
if (layer) {
|
||||
layer.set('clickPsblYn', 'Y');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 레이어 삭제
|
||||
*/
|
||||
removeLayer: function (layer) {
|
||||
if (layer) {
|
||||
layer.set('clickPsblYn', 'N');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 선택된 객체 초기화
|
||||
*/
|
||||
resetSelectedFeature: function () {
|
||||
const selectedFeatures = this.selectedFeatures;
|
||||
selectedFeatures.forEach(feature => {
|
||||
selectedFeatures.remove(feature);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* SELECT 이벤트 활성화
|
||||
*/
|
||||
setActive: function (active) {
|
||||
this.selectInteraction.setActive(active);
|
||||
},
|
||||
/**
|
||||
* SELECT 이벤트 삭제
|
||||
*/
|
||||
removeInteration: function () {
|
||||
let map = this.map;
|
||||
map.removeInteraction(this.selectInteraction);
|
||||
this.selectInteraction = null;
|
||||
|
||||
//포인터 변경 삭제
|
||||
this.removePointerMoveEventHandler('pointerMoveForSelect');
|
||||
},
|
||||
/**
|
||||
* 피처 위에 마우스 오버시 포인터 변경
|
||||
*/
|
||||
pointerMoveEventHandler: function () {
|
||||
let me = this;
|
||||
let map = this.map;
|
||||
let pointerMoveForSelect = function (evt) {
|
||||
let hit = this.forEachFeatureAtPixel(
|
||||
evt.pixel,
|
||||
function (feature, layer) {
|
||||
let returnFlag = true;
|
||||
if (layer?.get('clickPsblYn') === 'N') {
|
||||
returnFlag = false;
|
||||
}
|
||||
return returnFlag;
|
||||
},
|
||||
{ hitTolerance: me.defaults.hitTolerance }
|
||||
);
|
||||
|
||||
if (hit && me.selectInteraction.getActive()) {
|
||||
this.getTargetElement().style.cursor = 'pointer';
|
||||
} else {
|
||||
this.getTargetElement().style.cursor = '';
|
||||
}
|
||||
};
|
||||
map.on('pointermove', pointerMoveForSelect);
|
||||
},
|
||||
/**
|
||||
* 포인터 이벤트 삭제
|
||||
*/
|
||||
removePointerMoveEventHandler: function (fnNm) {
|
||||
GisApp.Module.core.removeMapEventFnNm('pointermove', fnNm);
|
||||
},
|
||||
/**
|
||||
* 선택된 피처 정보 반환(callback)
|
||||
*/
|
||||
getSelectedInfo: function (callback) {
|
||||
let me = this;
|
||||
this.selectInteraction.on('select', function (e) {
|
||||
if (!e.auto) {
|
||||
let features = me.selectInteraction.getFeatures();
|
||||
if(features.getArray().length == 0) return;
|
||||
let layers = [];
|
||||
for (let i in features.array_) {
|
||||
let feature = features.array_[0];
|
||||
let layer = me.selectInteraction.getLayer(feature);
|
||||
layers.push(layer);
|
||||
}
|
||||
callback(features.array_, layers);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 선택된 피처 정보 반환
|
||||
*/
|
||||
getSelectedFeatures: function () {
|
||||
return this.selectInteraction.getFeatures();
|
||||
},
|
||||
/**
|
||||
* 마지막으로 선택된 피처 정보 반환
|
||||
*/
|
||||
getLastSelectedFeatures: function () {
|
||||
return this.selectInteraction.getFeatures();
|
||||
},
|
||||
/**
|
||||
* 툴팁 생성
|
||||
*/
|
||||
createHelpTooltip: function () {
|
||||
let me = this;
|
||||
let map = this.map;
|
||||
GisApp.select.helpTooltipElement = document.createElement('div');
|
||||
GisApp.select.helpTooltipElement.className = 'ol-tooltip hidden';
|
||||
GisApp.select.helpTooltip = new Overlay({
|
||||
element: GisApp.select.helpTooltipElement,
|
||||
offset: [15, 0],
|
||||
positioning: 'center-left',
|
||||
});
|
||||
map.addOverlay(GisApp.select.helpTooltip);
|
||||
map.on('pointermove', me.pointerMoveTooltipHandler);
|
||||
},
|
||||
/**
|
||||
* 툴팁 삭제
|
||||
*/
|
||||
removeHelpTooltip: function () {
|
||||
if (GisApp.select.helpTooltipElement) {
|
||||
let map = this.map;
|
||||
this.removePointerMoveEventHandler('pointerMoveTooltipHandler');
|
||||
map.removeOverlay(GisApp.measure.helpTooltip);
|
||||
GisApp.select.helpTooltipElement.remove();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 툴팁용 포인터 move 이벤트
|
||||
*/
|
||||
pointerMoveTooltipHandler: function (evt) {
|
||||
if (evt.dragging) {
|
||||
return;
|
||||
}
|
||||
//GisApp.select.helpTooltipElement.innerHTML = DOMPurify.sanitize(GisApp.select.helpMsg);
|
||||
GisApp.select.helpTooltipElement.innerHTML = GisApp.select.helpMsg;
|
||||
GisApp.select.helpTooltip.setPosition(evt.coordinate);
|
||||
GisApp.select.helpTooltipElement.classList.remove('hidden');
|
||||
},
|
||||
getVisible: function (layer) {
|
||||
let maxZoom = layer.getMaxZoom();
|
||||
let minZoom = layer.getMinZoom();
|
||||
let map = this.map;
|
||||
let curZoom = map.getView().getZoom();
|
||||
if (layer.getVisible() && maxZoom > curZoom && minZoom < curZoom) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* 피처 셀렉트
|
||||
*/
|
||||
selectFeature: function (feature, auto) {
|
||||
let me = this;
|
||||
me.selectedFeatures.clear();
|
||||
me.selectedFeatures.push(feature);
|
||||
me.selectInteraction.dispatchEvent({
|
||||
type: 'select',
|
||||
selected: me.selectedFeatures,
|
||||
deselected: [],
|
||||
auto: auto,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlSelect;
|
||||
@@ -0,0 +1,402 @@
|
||||
import GisApp from './gis.core.js';
|
||||
import Style from 'ol/style/Style.js';
|
||||
import Fill from 'ol/style/Fill.js';
|
||||
import Icon from 'ol/style/Icon.js';
|
||||
import Circle from 'ol/style/Circle.js';
|
||||
import Stroke from 'ol/style/Stroke.js';
|
||||
import Text from 'ol/style/Text.js';
|
||||
import Point from 'ol/geom/Point.js';
|
||||
import RegularShape from 'ol/style/RegularShape.js';
|
||||
import Overlay from 'ol/Overlay';
|
||||
import _ from 'lodash';
|
||||
//import DOMPurify from 'dompurify';
|
||||
import { createPortal } from 'react-dom';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import React from 'react';
|
||||
|
||||
const GisOlStyle = function () {
|
||||
this.initialize();
|
||||
};
|
||||
|
||||
GisOlStyle.prototype = {
|
||||
overlays: {},
|
||||
hiddenOverlays: {},
|
||||
initialize: function () {
|
||||
this.layericonPath = GisApp.CONTEXT_LAYER_PATH;
|
||||
},
|
||||
getRGBAToString: function (r, g, b, a) {
|
||||
let rgba = 'rgba({r}, {g}, {b}, {a})';
|
||||
return rgba.replace('{r}', r).replace('{g}', g).replace('{b}', b).replace('{a}', a);
|
||||
},
|
||||
getFillColor: function (layer) {
|
||||
let r = layer.get('fillColorR');
|
||||
let g = layer.get('fillColorG');
|
||||
let b = layer.get('fillColorB');
|
||||
let a = layer.get('fillColorA');
|
||||
return this.getRGBAToString(r, g, b, a);
|
||||
},
|
||||
getFontLineColor: function (layer) {
|
||||
let r = layer.get('fontOutlineColorR');
|
||||
let g = layer.get('fontOutlineColorG');
|
||||
let b = layer.get('fontOutlineColorB');
|
||||
let a = layer.get('fontOutlineColorA');
|
||||
return this.getRGBAToString(r, g, b, a);
|
||||
},
|
||||
getFontFillColor: function (layer) {
|
||||
let r = layer.get('fontColorR');
|
||||
let g = layer.get('fontColorG');
|
||||
let b = layer.get('fontColorB');
|
||||
let a = layer.get('fontColorA');
|
||||
return this.getRGBAToString(r, g, b, a);
|
||||
},
|
||||
point: function (layer, options, feature, resolution) {
|
||||
let symbol = layer.get('iconNm');
|
||||
let maticKey = layer.get('maticKey');
|
||||
let maticVal1 = layer.get('maticVal1');
|
||||
let image;
|
||||
let style = new Style({
|
||||
fill: new Fill(),
|
||||
});
|
||||
|
||||
let text = this.getText(layer, feature, resolution);
|
||||
style.setText(text);
|
||||
|
||||
if (symbol) {
|
||||
let rotation = 0;
|
||||
if (feature) {
|
||||
let angDir = feature.get('angDir');
|
||||
if (angDir) {
|
||||
rotation = (angDir * Math.PI) / 180;
|
||||
}
|
||||
}
|
||||
image = new Icon({
|
||||
crossOrigin: 'anonymous',
|
||||
scale: 1.2,
|
||||
src: this.layericonPath + symbol,
|
||||
rotation: rotation,
|
||||
});
|
||||
} else {
|
||||
image = new Circle({
|
||||
fill: new Fill({
|
||||
color: '#4848B4',
|
||||
}),
|
||||
radius: 5,
|
||||
});
|
||||
}
|
||||
|
||||
const overlayHtml = feature.get('html');
|
||||
if (overlayHtml) {
|
||||
this.setOverlay(layer, feature, overlayHtml);
|
||||
}
|
||||
|
||||
let baseResolution = 20;
|
||||
let minScale = 0.3;
|
||||
let maxScale = 1.5;
|
||||
let scale = baseResolution / resolution > maxScale ? maxScale : baseResolution / resolution;
|
||||
scale = Math.max(minScale, Math.min(maxScale, scale));
|
||||
image.setScale(scale);
|
||||
style.setImage(image);
|
||||
_.extend(style, options);
|
||||
return style;
|
||||
},
|
||||
line: function (layer, options, feature, resolution) {
|
||||
let linewidth = layer.get('lnWdth');
|
||||
let lineColor = layer.get('lnColor');
|
||||
let style = new Style({
|
||||
stroke: new Stroke({
|
||||
color: lineColor,
|
||||
width: linewidth,
|
||||
}),
|
||||
text: new Text(),
|
||||
});
|
||||
|
||||
if (GisApp.Module.toolbar && GisApp.Module.toolbar.defaults.blockDiagramVisible) {
|
||||
let blockDiagramLayerId = GisApp.Module.toolbar.getBlockDiagramLayerId(layer.get('lyrId'));
|
||||
if (blockDiagramLayerId) {
|
||||
style = this.getArrowStyle(feature, lineColor, linewidth);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
poligon: function (layer, options, feature, resolution) {
|
||||
const style = new Style();
|
||||
const color = layer.get('polgnColor');
|
||||
const fill = new Fill({
|
||||
color,
|
||||
});
|
||||
style.setFill(fill);
|
||||
_.extend(style, options);
|
||||
return style;
|
||||
},
|
||||
lineLabelPoligon: function (layer, options, feature, resolution) {
|
||||
let style = new Style();
|
||||
const polgnColor = layer.get('polgnColor');
|
||||
let fill = new Fill({
|
||||
color: polgnColor,
|
||||
});
|
||||
let lnWdth = layer.get('lnWdth');
|
||||
let lineColor = layer.get('lnColor');
|
||||
let lineDash = layer.get('lineDash');
|
||||
let stroke = new Stroke({
|
||||
color: lineColor || '#000000',
|
||||
width: lnWdth,
|
||||
lineDash: lineDash ? [10, 5] : null,
|
||||
});
|
||||
let text = this.getText(layer, feature, resolution);
|
||||
if (text) {
|
||||
style.setText(text);
|
||||
}
|
||||
const overlayHtml = feature.get('html');
|
||||
if (overlayHtml) {
|
||||
this.setOverlay(layer, feature, overlayHtml);
|
||||
}
|
||||
style.setFill(fill);
|
||||
style.setStroke(stroke);
|
||||
_.extend(style, options);
|
||||
return style;
|
||||
},
|
||||
imagePoligon: function (layer, options, feature, resolution) {
|
||||
let style = new Style();
|
||||
let symbol = layer.get('iconNm');
|
||||
let retGeom = feature.getGeometry().getInteriorPoint();
|
||||
let image = new Icon({
|
||||
crossOrigin: 'anonymous',
|
||||
imgSize: [20, 20],
|
||||
src: this.layericonPath + symbol,
|
||||
});
|
||||
|
||||
let scale = 0.5 / resolution > 1 ? 1 : 0.5 / resolution;
|
||||
image.setScale(1);
|
||||
style.setGeometry(retGeom);
|
||||
style.setImage(image);
|
||||
|
||||
_.extend(style, options);
|
||||
return style;
|
||||
},
|
||||
setOverlay: function (layer, feature, overlayHtml) {
|
||||
const layerFeature = layer.getSource().getFeatures()[0];
|
||||
const layerCoordinate = layerFeature ? layerFeature.getGeometry().getCoordinates() : null;
|
||||
|
||||
const properties = feature.getProperties();
|
||||
const overLayId = feature?.get('overLayId');
|
||||
const overLayMinZoom = layer.get('overLayMinZoom') || '12';
|
||||
const overLayOffset = layer?.get('overLayOffset');
|
||||
let findoOverlay = this.overlays[overLayId];
|
||||
if (findoOverlay) {
|
||||
const zoom = GisApp.Module.core.map.getView().getZoom();
|
||||
if (Number(overLayMinZoom) < zoom) {
|
||||
findoOverlay.options.element.style.display = 'block';
|
||||
} else {
|
||||
findoOverlay.options.element.style.display = 'none';
|
||||
}
|
||||
return findoOverlay;
|
||||
}
|
||||
let overlayContainer = document.createElement('div');
|
||||
document.body.appendChild(overlayContainer);
|
||||
|
||||
if (typeof overlayHtml === 'string') {
|
||||
//const sanitizedHtml = DOMPurify.sanitize(overlayHtml);
|
||||
overlayContainer.innerHTML = sanitizedHtml;
|
||||
}
|
||||
// React 컴포넌트일 경우 createPortal() 사용
|
||||
else if (React.isValidElement(overlayHtml)) {
|
||||
ReactDOM.createRoot(overlayContainer).render(createPortal(overlayHtml, overlayContainer));
|
||||
}
|
||||
|
||||
// 오버레이 생성
|
||||
let overlay = new Overlay({
|
||||
element: overlayContainer,
|
||||
positioning: 'bottom-center',
|
||||
stopEvent: false,
|
||||
offset: overLayOffset ? overLayOffset : [0, 0], // 오프셋 설정 (위치 조정)
|
||||
});
|
||||
this.overlays[overLayId] = overlay;
|
||||
|
||||
// feature의 좌표 가져오기
|
||||
let geometry = feature.getGeometry();
|
||||
let coordinate;
|
||||
// Point인 경우
|
||||
if (geometry.getType() === 'Point') {
|
||||
coordinate = geometry.getCoordinates();
|
||||
}
|
||||
// LineString 또는 MultiLineString인 경우
|
||||
else if (geometry.getType() === 'LineString' || geometry.getType() === 'MultiLineString') {
|
||||
coordinate = geometry.getFlatMidpoint(); // 선의 중간 좌표를 가져옴
|
||||
}
|
||||
// Polygon 또는 MultiPolygon인 경우
|
||||
else if (geometry.getType() === 'Polygon' || geometry.getType() === 'MultiPolygon') {
|
||||
coordinate = geometry.getExtent();
|
||||
const center = [(coordinate[0] + coordinate[2]) / 2, (coordinate[1] + coordinate[3]) / 2];
|
||||
coordinate = center;
|
||||
} else {
|
||||
console.error('Unsupported geometry type: ' + geometry.getType());
|
||||
return;
|
||||
}
|
||||
overlay.setPosition(coordinate);
|
||||
GisApp.Module.core.map.addOverlay(overlay);
|
||||
const zoom = GisApp.Module.core.map.getView().getZoom();
|
||||
if (Number(overLayMinZoom) < zoom) {
|
||||
overlay.options.element.style.display = 'block';
|
||||
} else {
|
||||
overlay.options.element.style.display = 'none';
|
||||
}
|
||||
},
|
||||
label: function (layer, options, feature, resolution) {
|
||||
let textBaseline = layer.get('textBaseline') || 'bottom';
|
||||
let textAlign = layer.get('textAlign') || 'center';
|
||||
let fontExtnColor = layer.get('fontExtnColor');
|
||||
let fontExtnWdth = Number(layer.get('fontExtnWdth') || '3.5');
|
||||
let fontIntnColor = layer.get('fontIntnColor');
|
||||
let style = new Style();
|
||||
let prop = feature.getProperties();
|
||||
let label = prop[layer.get('lblAtrbNm')];
|
||||
|
||||
let text = new Text({
|
||||
stroke: new Stroke({
|
||||
width: fontExtnWdth,
|
||||
color: fontExtnColor,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: fontIntnColor,
|
||||
}),
|
||||
font: layer.get('font'),
|
||||
textBaseline: textBaseline,
|
||||
textAlign: textAlign,
|
||||
text: label,
|
||||
});
|
||||
style.setStroke(null);
|
||||
style.setFill(null);
|
||||
style.setText(text);
|
||||
_.extend(style, options);
|
||||
|
||||
return style;
|
||||
},
|
||||
getText: function (layer, feature, resolution) {
|
||||
let textBaseline = layer.get('textBaseline') || 'middle';
|
||||
let textAlign = layer.get('textAlign') || 'center';
|
||||
let fontExtnColor = layer.get('fontExtnColor');
|
||||
let fontExtnWdth = Number(layer.get('fontExtnWdth') || '1');
|
||||
let fontIntnColor = layer.get('fontIntnColor');
|
||||
let minViewSize = layer.get('minViewSize');
|
||||
if (feature == null) {
|
||||
return;
|
||||
}
|
||||
let prop = feature.getProperties();
|
||||
let label = prop[layer.get('lblAtrbNm')];
|
||||
let scaleFactor = 1 / resolution; // 해상도가 낮아질수록 글자 크기 증가
|
||||
const zoom = GisApp.Module.core.map.getView().getZoom();
|
||||
if (zoom < minViewSize - 3) {
|
||||
label = '';
|
||||
}
|
||||
let text = new Text({
|
||||
stroke: new Stroke({
|
||||
color: '#FFFFFF', // 흰색 테두리
|
||||
width: fontExtnWdth,
|
||||
}),
|
||||
fill: new Fill({
|
||||
color: fontIntnColor,
|
||||
width: fontExtnWdth,
|
||||
}),
|
||||
font: `${layer.get('fontNm')}`, // 동적으로 폰트 크기 적용
|
||||
textBaseline: textBaseline,
|
||||
textAlign: textAlign,
|
||||
overflow: true,
|
||||
text: label,
|
||||
offsetY: 20,
|
||||
});
|
||||
|
||||
return text;
|
||||
},
|
||||
getArrowStyle: function (feature, lineColor, linewidth) {
|
||||
let styles = [
|
||||
new Style({
|
||||
stroke: new Stroke({
|
||||
color: lineColor,
|
||||
width: linewidth,
|
||||
}),
|
||||
}),
|
||||
];
|
||||
let geomLienString = GisApp.Module.core.getGeomLineOfFeature(feature);
|
||||
if (geomLienString) {
|
||||
let startCoord = geomLienString.getFirstCoordinate();
|
||||
let endCoord = geomLienString.getLastCoordinate();
|
||||
|
||||
let dx = endCoord[0] - startCoord[0];
|
||||
let dy = endCoord[1] - startCoord[1];
|
||||
let rotation = Math.atan2(dy, dx);
|
||||
|
||||
let centerCoord = GisApp.Module.core.getCenterOfLineFeature(feature);
|
||||
styles.push(
|
||||
new Style({
|
||||
geometry: new Point(centerCoord),
|
||||
image: new RegularShape({
|
||||
fill: new Fill({ color: lineColor }),
|
||||
points: 3,
|
||||
radius: 6,
|
||||
rotation: -rotation,
|
||||
angle: Math.PI / 2,
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
return styles;
|
||||
},
|
||||
//레이어 스타일 반환
|
||||
getLayerStyle: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
if (!gobjTy) return;
|
||||
let style = this[gobjTy](layer, options);
|
||||
return style;
|
||||
},
|
||||
getLayerStyleFunction: function (layer, options) {
|
||||
let gobjTy = layer.get('gobjTy');
|
||||
let styleFunction = function (feature, resolution) {
|
||||
return this[gobjTy](layer, options, feature, resolution);
|
||||
}.bind(this);
|
||||
return styleFunction;
|
||||
},
|
||||
adjustOverlayPosition: function (overlay, coordinate) {
|
||||
const overlays = this.overlays;
|
||||
let adjustedCoordinate = [...coordinate]; // 초기 좌표 설정
|
||||
const minDistance = 100000; // 최소 거리 (필요시 조정)
|
||||
let offset = overlay.getOffset() || [0, 0];
|
||||
|
||||
let closestOverlay = null;
|
||||
let closestDistance = Infinity;
|
||||
|
||||
// 모든 오버레이의 위치를 비교하여 가장 가까운 오버레이 찾기
|
||||
Object.values(overlays).forEach(existingOverlay => {
|
||||
if (existingOverlay) {
|
||||
const existingCoordinate = existingOverlay.getPosition();
|
||||
const existingOffset = existingOverlay.getOffset() || [0, 0];
|
||||
|
||||
// 현재 오버레이의 좌표 가져오기
|
||||
const overlayCoordinate = overlay.getPosition();
|
||||
if (!overlayCoordinate || !existingCoordinate) return;
|
||||
|
||||
// 거리 계산 (유클리드 거리)
|
||||
const distance = Math.sqrt(Math.pow(overlayCoordinate[0] - existingCoordinate[0], 2) + Math.pow(overlayCoordinate[1] - existingCoordinate[1], 2));
|
||||
|
||||
// 가장 가까운 오버레이 업데이트
|
||||
if (distance > 0 && distance < closestDistance) {
|
||||
closestDistance = distance;
|
||||
closestOverlay = existingOverlay;
|
||||
} else {
|
||||
offset = [50, 50];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 가장 가까운 오버레이가 있으면 offset 조정
|
||||
if (closestOverlay && closestDistance < minDistance) {
|
||||
const closestOffset = closestOverlay.getOffset() || [0, 0];
|
||||
// X, Y 좌표를 기존 offset 값의 반대로 설정
|
||||
offset = [-closestOffset[0], -closestOffset[1]];
|
||||
}
|
||||
|
||||
// 최종 offset 적용
|
||||
overlay.setOffset(offset);
|
||||
},
|
||||
};
|
||||
|
||||
export default GisOlStyle;
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,596 @@
|
||||
|
||||
//공통
|
||||
export const util = {
|
||||
getOneToArrayByObjectKey: function (arr, key, value) {
|
||||
let result = {};
|
||||
arr.forEach(item => {
|
||||
if (item[key] === value) {
|
||||
result = item;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
},
|
||||
groupByProperty: (arr, prop, parentProp, group) => {
|
||||
const tree = [];
|
||||
const mappedArr = {};
|
||||
let arrElem;
|
||||
let mappedElem;
|
||||
// 먼저 배열의 노드를 개체에 매핑하고 -> 해시 테이블을 생성
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arrElem = arr[i];
|
||||
if (group && arrElem.mnuGroup !== group) continue;
|
||||
mappedArr[arrElem[prop]] = arrElem;
|
||||
mappedArr[arrElem[prop]].children = [];
|
||||
}
|
||||
|
||||
for (const mnuCd in mappedArr) {
|
||||
if (mappedArr.hasOwnProperty(mnuCd)) {
|
||||
mappedElem = mappedArr[mnuCd];
|
||||
// 요소가 루트 수준에 없으면 자식의 부모 배열에 추가.
|
||||
if (mappedElem[parentProp] && mappedArr[mappedElem[parentProp]]?.children) {
|
||||
mappedArr[mappedElem[parentProp]].children.push(mappedElem);
|
||||
}
|
||||
// 요소가 루트 수준에 있는 경우 첫 번째 수준 요소 배열에 추가
|
||||
else {
|
||||
tree.push(mappedElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree;
|
||||
},
|
||||
getLowestChildren: function (obj) {
|
||||
if (!obj.children || obj.children.length === 0) {
|
||||
return obj;
|
||||
} else {
|
||||
return this.getLowestChildren(obj.children[0]);
|
||||
}
|
||||
},
|
||||
hasClass: (e, clazz) => {
|
||||
return e.target.classList.contains(clazz);
|
||||
},
|
||||
isNull: function (str) {
|
||||
if (typeof str === 'undefined' || str === null || str === 'null' || str === '' || str === 'undefined') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
nvl: function (str, returnVal) {
|
||||
if (this.isNull(str)) {
|
||||
return returnVal;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
isNumeric: function (str) {
|
||||
const pattern = /\d/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isAlpha: function (str) {
|
||||
const pattern = /[a-zA-Z]/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isAlphaNumeric: function (str) {
|
||||
const pattern = /^[a-zA-Z0-9]+$/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isSpecialChar: function (str) {
|
||||
const pattern = /[~!@#$%^&*()_+|<>?:{}]/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isKoreanAplhaNumeric: function (str) {
|
||||
const pattern = /^[ㄱ-ㅎ|ㅏ-ㅣ|가-힣|a-zA-Z0-9]+$/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isValidPassword: function (str) {
|
||||
const pattern = /^(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[!@#$%^&*(),.?":{}|<>])[a-zA-Z0-9!@#$%^&*(),.?":{}|<>]{8,20}$/;
|
||||
return pattern.test(str);
|
||||
},
|
||||
isValidExt: function (fileName, allowedExtensions) {
|
||||
const fileExtension = fileName.split('.').pop().toLowerCase();
|
||||
return allowedExtensions.includes(fileExtension);
|
||||
},
|
||||
isValidAllDate: function (dateString) {
|
||||
const formats = ['YYYY', 'YYYYMM', 'YYYYMMDD'];
|
||||
for (let format of formats) {
|
||||
if (moment(dateString, format, true).isValid()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isEmptyObject: function (param) {
|
||||
return Object.keys(param).length === 0 && param.constructor === Object;
|
||||
},
|
||||
castArrayObjectToList: function (array, key) {
|
||||
var newArray = [];
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
newArray.push(array[i][key]);
|
||||
}
|
||||
return newArray;
|
||||
},
|
||||
arraysMatch: function (arr1, arr2) {
|
||||
// 길이가 같은지 확인
|
||||
if (arr1.length !== arr2.length) return false;
|
||||
|
||||
// 각 요소가 일치하는지 확인
|
||||
return arr1.every((element, index) => element === arr2[index]);
|
||||
},
|
||||
//상위뎁스 객체들 조회
|
||||
findObjectByKeyAndValue: function ({ array, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents = [] }) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let obj = array[i];
|
||||
let siblings = [...array];
|
||||
//siblings.splice(i, 1); // 현재 메뉴를 제거하여 형제 메뉴들만 남김
|
||||
let currentParents = [...parents, { [resultKey]: obj, siblings: siblings }];
|
||||
|
||||
if (obj[findKey] === findValue) {
|
||||
let formattedResult = currentParents.reduce((acc, cur, idx) => {
|
||||
acc[`${resultKey}${idx + 1}`] = { active: cur[resultKey], list: cur.siblings };
|
||||
return acc;
|
||||
}, {});
|
||||
return formattedResult;
|
||||
}
|
||||
if (obj.children) {
|
||||
let result = this.findObjectByKeyAndValue({ array: obj.children, find: { key: findKey, value: findValue }, result: { key: resultKey }, parents: currentParents });
|
||||
if (result !== null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
autoComma(val, returnVal) {
|
||||
if (!val && val !== 0) return returnVal ? returnVal : '';
|
||||
|
||||
// 문자열로 변환하고, 숫자와 소수점만을 허용
|
||||
val = String(val).replace(/[^0-9.-]/g, '');
|
||||
|
||||
// 소수점을 기준으로 숫자를 분리
|
||||
let [integerPart, decimalPart] = val.split('.');
|
||||
|
||||
// 정수 부분에 대해 쉼표 추가
|
||||
integerPart = integerPart.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,');
|
||||
|
||||
// 소수점 이하 숫자가 있다면 다시 합치기
|
||||
return decimalPart ? `${integerPart}.${decimalPart}` : integerPart;
|
||||
},
|
||||
removeComma: function (str) {
|
||||
str = String(str);
|
||||
return str.replace(/[^\d]+/g, '');
|
||||
},
|
||||
removeSpaceAndTrim: function (str) {
|
||||
if (str) {
|
||||
str = String(str);
|
||||
return str.replace(/\s/g, '');
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
},
|
||||
isNotWhiteSpace: function (str) {
|
||||
const hasWhitespace = /\s/.test(str);
|
||||
return !hasWhitespace;
|
||||
},
|
||||
//숫자만 남기고 모두 제거해서 반환
|
||||
retainOnlyNumbers: function (str) {
|
||||
str = String(str);
|
||||
return str.replace(/\D+/g, '');
|
||||
},
|
||||
proxyToObject: function (proxy) {
|
||||
return JSON.parse(JSON.stringify(Object.assign({}, proxy)));
|
||||
},
|
||||
dateFormatter: function ({ input, isHan = false }) {
|
||||
const value = this.retainOnlyNumbers(input);
|
||||
|
||||
if (value.length === 6) {
|
||||
const date = moment(value, 'YYYYMM');
|
||||
return date.format(isHan ? 'YYYY년 MM월' : 'YYYY-MM');
|
||||
} else if (value.length === 8) {
|
||||
const date = moment(value, 'YYYYMMDD');
|
||||
return date.format(isHan ? 'YYYY년 MM월 DD일' : 'YYYY-MM-DD');
|
||||
} else if (value.length === 14) {
|
||||
const date = moment(value, 'YYYYMMDDHHmmss');
|
||||
return date.format(isHan ? 'YYYY년 MM월 DD일 HH시 mm분 ss초' : 'YYYY-MM-DD HH:MM:SS');
|
||||
} else {
|
||||
return input;
|
||||
}
|
||||
},
|
||||
generateYearArray: function (num) {
|
||||
const currentYear = moment().year();
|
||||
if (num <= 1 || isNaN(num)) {
|
||||
return [(currentYear - 2).toString(), (currentYear - 1).toString(), currentYear.toString()];
|
||||
}
|
||||
|
||||
const years = [];
|
||||
for (let i = num - 1; i >= 0; i--) {
|
||||
years.push((currentYear - i).toString());
|
||||
}
|
||||
|
||||
return years;
|
||||
},
|
||||
getFileId: function () {
|
||||
let timeStamp = moment().format('YYYYMMDDHHmmssSSS'); // YYYYMMDDHHmmssSSS 형태로 타임스탬프를 생성
|
||||
return 'CFM_' + timeStamp.slice(-12); // slice(-12)는 문자열의 끝에서 12자리를 추출합니다.
|
||||
},
|
||||
lpad: function (number, pad, length) {
|
||||
let str = number.toString();
|
||||
while (str.length < length) {
|
||||
str = pad + str;
|
||||
}
|
||||
return str;
|
||||
},
|
||||
convertKeysToCamelCase: function (obj) {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
return Object.keys(obj).reduce((acc, key) => {
|
||||
const camelCaseKey = key.replace(/([-_]\w)/g, match => match.replace(/[-_]/, '').toUpperCase());
|
||||
acc[camelCaseKey] = typeof obj[key] === 'object' ? this.convertKeysToCamelCase(obj[key]) : obj[key];
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
blobUrlToBlob: async blobUrl => {
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
return await response.blob();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Blob();
|
||||
}
|
||||
},
|
||||
hexToRgba: hex => {
|
||||
const r = parseInt(hex.substring(1, 3), 16);
|
||||
const g = parseInt(hex.substring(3, 5), 16);
|
||||
const b = parseInt(hex.substring(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b} , 0.5)`;
|
||||
},
|
||||
rgbaToHex: rgba => {
|
||||
/*rgba값을 가져와서 rgb 로 추출 후 hex 값 반환 */
|
||||
if (!rgba) return false;
|
||||
const rgbArray = rgba.match(/\d+/g).slice(0, 3).map(Number);
|
||||
return `#${((1 << 24) | (rgbArray[0] << 16) | (rgbArray[1] << 8) | rgbArray[2]).toString(16).slice(1).toUpperCase()}`;
|
||||
},
|
||||
rgbAddOpacity: (rgb, opacity) => {
|
||||
const rgbValues = rgb.match(/\d+/g); // rgb 값을 배열로 추출
|
||||
const colorWithOpacity = `rgba(${rgbValues[0]}, ${rgbValues[1]}, ${rgbValues[2]}, ${opacity})`; // rgba로 변환
|
||||
return colorWithOpacity;
|
||||
},
|
||||
generateMonthArray: (pSmonth, pEmonth) => {
|
||||
let startYear = Math.floor(pSmonth / 100);
|
||||
let startMonth = pSmonth % 100;
|
||||
let endYear = Math.floor(pEmonth / 100);
|
||||
let endMonth = pEmonth % 100;
|
||||
|
||||
let months = [];
|
||||
|
||||
for (let year = startYear; year <= endYear; year++) {
|
||||
let monthStart = year === startYear ? startMonth : 1;
|
||||
let monthEnd = year === endYear ? endMonth : 12;
|
||||
|
||||
for (let month = monthStart; month <= monthEnd; month++) {
|
||||
let monthString = month < 10 ? `0${month}` : `${month}`;
|
||||
months.push(`${year}${monthString}`);
|
||||
}
|
||||
}
|
||||
|
||||
return months;
|
||||
},
|
||||
findDuplicatesForArr: (arr, key) => {
|
||||
const map = new Map();
|
||||
const duplicates = [];
|
||||
|
||||
arr.forEach(item => {
|
||||
const keyValue = item[key];
|
||||
if (map.has(keyValue)) {
|
||||
if (!duplicates.includes(keyValue)) {
|
||||
duplicates.push(keyValue);
|
||||
}
|
||||
} else {
|
||||
map.set(keyValue, true);
|
||||
}
|
||||
});
|
||||
|
||||
return duplicates;
|
||||
},
|
||||
createHtmlFromJson: (html, data) => {
|
||||
if (!html || util.isEmptyObject(html)) return null;
|
||||
const { classNm, headerKey, rows } = JSON.parse(html);
|
||||
if (!rows) return null;
|
||||
const tableRows = rows
|
||||
.map(row => {
|
||||
const value = data?.[row.key] ?? null;
|
||||
let displayValue;
|
||||
if (typeof value === 'number') {
|
||||
displayValue = util.autoComma(value, 0);
|
||||
} else if (typeof value === 'string') {
|
||||
displayValue = util.nvl(value, '-');
|
||||
} else {
|
||||
displayValue = '-';
|
||||
}
|
||||
return `
|
||||
<tr>
|
||||
<th>${row.th}</th>
|
||||
<td>${displayValue}</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
return `
|
||||
<div class="${classNm}">
|
||||
<table>
|
||||
<tr>
|
||||
<th colspan="2" style="position: relative; text-align: center; background-color: #DEEAF3;">
|
||||
${data?.[headerKey] ?? '-'}
|
||||
</th>
|
||||
</tr>
|
||||
${tableRows}
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* 다이렉트로 import하여 사용하기 위해 export로 수정중
|
||||
*
|
||||
* */
|
||||
|
||||
//객체 배열에서 특정 key의 value인 것을 추출
|
||||
export const getArrayByObjectKey = function (arr, key, value) {
|
||||
return arr.filter(item => item[key] === value);
|
||||
};
|
||||
|
||||
//소수점 1자리를 기준으로 점을 언더스코어로 변경
|
||||
export const numberDotToUnderscore = function (num) {
|
||||
if (Number.isInteger(num)) {
|
||||
return num.toString();
|
||||
} else {
|
||||
return num.toFixed(1).replace('.', '_');
|
||||
}
|
||||
};
|
||||
|
||||
export const stripHTML = function (htmlString) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlString, 'text/html');
|
||||
return doc.body.textContent || '';
|
||||
};
|
||||
|
||||
//URL에서 매개변수 추출
|
||||
export function getParameterByName(name, url = window.location.search) {
|
||||
name = name.replace(/[\[\]]/g, '\\$&');
|
||||
var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
|
||||
results = regex.exec(url);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||
}
|
||||
|
||||
export function dateFormatForDB({ date: isoString, format = 'YYYY-MM-DD HH:MI:SS' }) {
|
||||
if (!isoString) return '';
|
||||
|
||||
let date = new Date(isoString);
|
||||
|
||||
let year = date.getFullYear();
|
||||
let month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
let day = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
let hours = date.getHours().toString().padStart(2, '0');
|
||||
let minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
let seconds = date.getSeconds().toString().padStart(2, '0');
|
||||
|
||||
return (
|
||||
format
|
||||
//
|
||||
.replace('YYYY', year)
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('MI', minutes)
|
||||
.replace('SS', seconds)
|
||||
);
|
||||
}
|
||||
|
||||
//blob 다운로드
|
||||
//blob 다운로드
|
||||
export function downloadFromBlob(res, data) {
|
||||
const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type }));
|
||||
const link = document.createElement('a');
|
||||
|
||||
const contentDisposition = res.headers?.['content-disposition']; // 파일 이름
|
||||
let fileName = data?.fileName || 'export'; //기본 파일 이름
|
||||
|
||||
if (contentDisposition) {
|
||||
const [fileNameMatch] = contentDisposition.split(';').filter(str => str.includes('filename'));
|
||||
if (fileNameMatch) {
|
||||
[, fileName] = fileNameMatch.split('=');
|
||||
// 파일명이 "UTF-8''" 등으로 인코딩되어 있을 수 있으니 decodeURIComponent 추가
|
||||
fileName = decodeURIComponent(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
link.href = url;
|
||||
// 브라우저 인코딩 관련 이슈 방지를 위해 파일명에 대한 추가 decodeURI 처리
|
||||
link.download = decodeURIComponent(fileName).replace(/"/g, '').trim();
|
||||
link.style.display = 'none';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function downloadBlobAsFile(blob, filename = 'export') {
|
||||
// 브라우저에 따라 인코딩 처리 햔재는 파이어폭스만
|
||||
const url = URL.createObjectURL(blob);
|
||||
const encodedFilename = encodeURIComponent(filename);
|
||||
const downloadLink = document.createElement('a');
|
||||
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = decodeURIComponent(encodedFilename);
|
||||
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
|
||||
document.body.removeChild(downloadLink);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function viewFile(res, data) {
|
||||
// Blob URL 생성
|
||||
const url = URL.createObjectURL(new Blob([res.data], { type: res.data.type }));
|
||||
|
||||
let fileType = null;
|
||||
let fileName = data?.fileName || 'export'; // 기본 파일 이름
|
||||
|
||||
// PDF 및 이미지 타입의 MIME 타입 확인
|
||||
if (res.data.type === 'application/pdf') {
|
||||
fileType = 'pdf';
|
||||
} else if (res.data.type.startsWith('image/')) {
|
||||
fileType = 'img';
|
||||
} else if (res.data.type === 'application/octet-stream') {
|
||||
// Content-Disposition 헤더에서 파일 이름 추출
|
||||
const contentDisposition = res.headers?.['content-disposition'];
|
||||
if (contentDisposition) {
|
||||
const fileNameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
||||
if (fileNameMatch && fileNameMatch[1]) {
|
||||
fileName = fileNameMatch[1].replace(/['"]/g, ''); // 파일 이름 추출
|
||||
fileName = decodeURIComponent(fileName); // URL 인코딩된 파일 이름을 디코딩
|
||||
}
|
||||
}
|
||||
|
||||
// 파일 확장자 추출
|
||||
const fileExtension = fileName.split('.').pop().toLowerCase();
|
||||
|
||||
// 확장자를 통해 파일 타입 결정
|
||||
if (fileExtension === 'pdf') {
|
||||
fileType = 'pdf';
|
||||
} else if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp'].includes(fileExtension)) {
|
||||
fileType = 'img';
|
||||
} else {
|
||||
console.error('지원하지 않는 파일 형식입니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// URL과 파일 타입 반환
|
||||
return { url, fileType, fileName };
|
||||
}
|
||||
|
||||
export function getToday() {
|
||||
return getDate('today');
|
||||
}
|
||||
|
||||
export function getDate(type, diff) {
|
||||
const date = new Date();
|
||||
|
||||
switch (type) {
|
||||
case 'year':
|
||||
date.setFullYear(date.getFullYear() + diff);
|
||||
break;
|
||||
case 'month':
|
||||
date.setMonth(date.getMonth() + diff);
|
||||
break;
|
||||
case 'day':
|
||||
date.setDate(date.getDate() + diff);
|
||||
break;
|
||||
case 'today':
|
||||
date.setDate(date.getDate());
|
||||
break;
|
||||
default:
|
||||
console.log('잘못된 타입입니다. 올바른 타입: 년, 월, 일');
|
||||
return;
|
||||
}
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
//promise 처리
|
||||
export const byPromise = function (actions) {
|
||||
return actions.reduce((promise, action) => {
|
||||
return promise.then(async () => {
|
||||
if (typeof action === 'function') {
|
||||
return await action();
|
||||
} else {
|
||||
return await action;
|
||||
}
|
||||
});
|
||||
}, Promise.resolve());
|
||||
};
|
||||
|
||||
export const getMessage = (msgGroup, msgKey, innerText, fix) => {
|
||||
let resultString = GLOBAL.MESSAGE[msgGroup][msgKey];
|
||||
const prefix = fix?.prefix || '';
|
||||
const suffix = fix?.suffix || '';
|
||||
|
||||
if (innerText) {
|
||||
for (const [key, value] of Object.entries(innerText)) {
|
||||
const regex = new RegExp(`\\$\{${key}\}`, 'g');
|
||||
resultString = resultString.replace(regex, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultString) resultString = resultString.replace(/\\n/g, '\n');
|
||||
return resultString ? `${prefix}${resultString}${suffix}` : '안내 메시지 로딩 오류 - 관리자에게 문의하세요.';
|
||||
};
|
||||
|
||||
// global.js의 message를 읽어오는 alert
|
||||
export const gAlert = (msgGroup, msgKey, innerText, fix) => {
|
||||
alert(getMessage(msgGroup, msgKey, innerText, fix));
|
||||
};
|
||||
|
||||
// message를 confirm
|
||||
export const gConfirm = (msgGroup, msgKey, innerText, fix) => {
|
||||
return window.confirm(getMessage(msgGroup, msgKey, innerText, fix));
|
||||
};
|
||||
|
||||
// message를 함수 리턴
|
||||
export const gMessage = getMessage;
|
||||
|
||||
// message를 save후 응답까지 처리
|
||||
export const gSaveCommonConfirm = async ({ message, api = async () => {}, param = {}, success, fail, prefix }) => {
|
||||
try {
|
||||
if (!gConfirm('CONFIRM', `${message}_QUEST`, null, { prefix })) return;
|
||||
|
||||
const result = await api(param, { isAlert: false });
|
||||
|
||||
if (result?.code !== 200) return;
|
||||
|
||||
const rep = message === 'UPLOAD' ? result.data.data : result.data;
|
||||
|
||||
if (rep > 0) {
|
||||
gAlert('INFO', `${message}_COMPLETE`);
|
||||
await success?.();
|
||||
} else {
|
||||
gAlert('ERROR', `${message}_FAIL`);
|
||||
await fail?.();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('오류 발생:', error); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
|
||||
}
|
||||
};
|
||||
|
||||
export const nullToValue = (text, { value = '-', prefix = '', suffix = '' } = {}) => {
|
||||
if (!text || text === value) {
|
||||
return value;
|
||||
}
|
||||
return `${prefix}${text}${suffix}`;
|
||||
};
|
||||
|
||||
export const filterArrayOfObjectsByKeys = (arrayOfObjects, keys) => {
|
||||
return arrayOfObjects.map(obj => {
|
||||
return keys.reduce((filteredObj, key) => {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
filteredObj[key] = obj[key];
|
||||
}
|
||||
return filteredObj;
|
||||
}, {});
|
||||
});
|
||||
};
|
||||
|
||||
export const isMobile = () => {
|
||||
const width = window.innerWidth > 0 ? window.innerWidth : window.screen.width;
|
||||
|
||||
return width < GLOBAL.MOBILE_SIZE;
|
||||
};
|
||||
@@ -5,5 +5,5 @@ function SearchDiv ({children}:{ children: React.ReactNode; }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export { SearchDiv };
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
export default function SearchIcon() {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6 text-gray-700"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 21l-4.35-4.35M17 11A6 6 0 105 11a6 6 0 0012 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import UiPageNations from "./uiPageNations";
|
||||
|
||||
type UiTableProps = {
|
||||
headName: React.ReactNode | undefined;
|
||||
tableData: { [key: string]: string | number | boolean | object }[];
|
||||
headlist: UiHeadProps[];
|
||||
pageSize: number;
|
||||
total: number;
|
||||
children: React.ReactNode | undefined;
|
||||
cellClick: ((index: number | undefined, headId: string) => void) | undefined;
|
||||
onPageChange: (page: number) => void;
|
||||
className?: string;
|
||||
resetPageNo?: boolean;
|
||||
cur?: number;
|
||||
}
|
||||
|
||||
type UiHeadProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
align?: string;
|
||||
}
|
||||
|
||||
export function UiTable2({ tableData, headlist, pageSize, total, children, headName, cellClick, onPageChange, className = '', resetPageNo = false, cur }: UiTableProps) {
|
||||
const [idx, setIdx] = useState<number>(-1);
|
||||
|
||||
const [curState, setCurState] = useState<number>(cur ?? 1);
|
||||
useEffect(() => {
|
||||
setCurState(cur ?? 1);
|
||||
}, [cur]);
|
||||
|
||||
useEffect(() => {
|
||||
if (resetPageNo) {
|
||||
setCurState(1);
|
||||
onPageChange(1);
|
||||
}
|
||||
}, [onPageChange, resetPageNo]);
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurState(page);
|
||||
onPageChange(page);
|
||||
}
|
||||
|
||||
const handleCellClick = (index: number, headId: string) => {
|
||||
if (cellClick) {
|
||||
cellClick(index, headId);
|
||||
setIdx(index);
|
||||
}
|
||||
};
|
||||
|
||||
const startIdx = (curState - 1) * pageSize;
|
||||
const endIdx = startIdx + pageSize;
|
||||
const pageData = tableData.slice(startIdx, endIdx);
|
||||
|
||||
return (
|
||||
<>
|
||||
{headName ? (
|
||||
<div className="card-header">
|
||||
<h3 className="text-2xl font-medium text-label">{headName}</h3>
|
||||
</div>
|
||||
) : null}
|
||||
<div className={`card !m-[1.2rem] !p-0r w-full relative ${className}`}>
|
||||
<div>
|
||||
<Table variant="secondary" className="w-full">
|
||||
{/* 스크롤 관련 div 제거 */}
|
||||
<TableHeader className="bg-white">
|
||||
{children ? (
|
||||
<>{children}</>
|
||||
) : (
|
||||
<TableRow>
|
||||
{headlist?.map((head) => (
|
||||
<TableHead key={head.id}
|
||||
className="min-w-[120px] max-w-[180px] w-[150px]
|
||||
text-center whitespace-nowrap overflow-hidden
|
||||
text-ellipsis bg-white">
|
||||
{head.title}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableHeader>
|
||||
<TableBody className='text-center'>
|
||||
{pageData.map((el: { [key: string]: string | number | boolean | object }, index) => (
|
||||
<TableRow key={index + "row"}>
|
||||
{headlist?.map((head) => (
|
||||
<TableCell
|
||||
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${cellClick && idx === index ? '!font-[1000] cursor-pointer' :
|
||||
cellClick ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={() => handleCellClick(startIdx + index, head.id)}
|
||||
key={head.id + index}
|
||||
>
|
||||
{typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{/* 페이지네이션을 sticky 하단에 고정 */}
|
||||
{total > 0 && (
|
||||
<div className="sticky left-0 right-0 bottom-0 bg-white z-20 border-t border-gray-200">
|
||||
<UiPageNations total={total} cur={curState} pageSize={pageSize} onPageChange={handlePageChange} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -30,5 +30,5 @@
|
||||
"**/*.mjs",
|
||||
"**/*.jsx",
|
||||
"**/*.json"
|
||||
]
|
||||
, "../../../apps/sn/app/map/SearchModal.tsx" ]
|
||||
}
|
||||
|
||||