목록에서 시설 클릭시 화면으로 이동 기능 추가
This commit is contained in:
@@ -275,7 +275,6 @@ const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void,
|
||||
// API에서 상세 데이터 가져오기
|
||||
if (apiEndpoint) {
|
||||
try {
|
||||
console.log(`API 호출: ${apiEndpoint}`);
|
||||
const result = await getData(apiEndpoint);
|
||||
|
||||
if (result) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Gis, { useGisContext } from "@common/business_components/ui/map/Gis";
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import { getData } from "../../components/lib/api";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
@@ -43,7 +43,6 @@ const Map = () => {
|
||||
const [showWaterDepth, setShowWaterDepth] = useState(false); // WaterDepth 표시 여부
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
@@ -96,6 +95,7 @@ const Map = () => {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 3. 피처 선택
|
||||
if (foundFeature) {
|
||||
let geometry = foundFeature.getGeometry();
|
||||
@@ -138,31 +138,33 @@ const Map = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={true}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={SearchModal}
|
||||
detailView={FeatureClickEventPopup}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
|
||||
waterDepth={showWaterDepth ? WaterDepth : undefined}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying as any}
|
||||
targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any}
|
||||
orderedLayers={ORDERED_LAYERS as any}
|
||||
checkInterval={2500}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={true}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={false}
|
||||
searchView={SearchModal}
|
||||
detailView={FeatureClickEventPopup}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
setIsOpen={setIsOpen}
|
||||
setFeatureData={setFeatureData}
|
||||
featureData={featureData}
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
searchBar={<ProcSearchBar gisMaps={gisMaps} facilityTypes={facilityTypes} />}
|
||||
waterDepth={showWaterDepth ? WaterDepth : undefined}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying as any}
|
||||
targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any}
|
||||
orderedLayers={ORDERED_LAYERS as any}
|
||||
checkInterval={2500}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableH
|
||||
import ReportTab from './reportTab';
|
||||
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
|
||||
import useStore from '../../../store/useStore';
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
|
||||
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
|
||||
|
||||
@@ -184,7 +185,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
size: '10',
|
||||
sort: 'string',
|
||||
});
|
||||
console.log('basicType', basicType);
|
||||
|
||||
if (basicType === 'tb_ms_swage_prcs_zone') cqlFilters = zoneCqlFilters(); // 하수처리구역
|
||||
else if (basicType === 'tb_il_swage_prcs_fclt') cqlFilters = facilityCqlFilters(); // 하수처리시설
|
||||
@@ -195,10 +195,7 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
|
||||
if (cqlFilters.length > 0) queryParams.append('cqlFiter', cqlFilters.join(' and '));
|
||||
|
||||
console.log('API URL:', `${API_URL}/v1/fclts?${queryParams.toString()}`);
|
||||
const res = await axios.get(`${API_URL}/v1/fclts?${queryParams.toString()}`);
|
||||
console.log('필터링된 API 결과(개수):', res.data.count);
|
||||
console.log('필터링된 API 결과(데이터):', res.data.data);
|
||||
|
||||
// 🎯 각 아이템을 개별적으로 포맷팅하면서 일련번호 추가
|
||||
const dataWithRowNumber = (res.data.data || []).map((item: any, index: number) => {
|
||||
@@ -217,7 +214,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
|
||||
setTableData(dataWithRowNumber);
|
||||
setTableCount(res.data.count || []);
|
||||
console.log('tableCount 상태 업데이트 후:', res.data.count); // tableCount 로그 추가
|
||||
|
||||
} else if (activeTab === 'operation') {
|
||||
const RequestDTO: Record<string, string> = {
|
||||
@@ -247,13 +243,9 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
|
||||
const params = new URLSearchParams(RequestDTO).toString();
|
||||
|
||||
console.log('API URL:', `/v1/bizInfo?${params}`);
|
||||
const res = await getData(`v1/bizInfo?${params}`);
|
||||
console.log('필터링된 API 결과(개수):', res.totalElements);
|
||||
console.log('필터링된 API 결과(데이터):', res.content);
|
||||
|
||||
const mappedContent = (res.content || []).map((item: any, index: number) => {
|
||||
console.log("item:", item);
|
||||
const formattedItem = formatDisplayData(item);
|
||||
const matched = BIZ_TY_CD.find(code => code.lwpoCd === item.bizTypeCd);
|
||||
return {
|
||||
@@ -456,7 +448,12 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
// 기본정보 테이블 cellClick 핸들러
|
||||
const handleBasicCellClick = (index?: number | undefined, headId?: String) => {
|
||||
if (typeof index === "number") {
|
||||
onFeatureSelect(tableData[index % 10]); // 10은 page size 하드 코딩한 숫자
|
||||
|
||||
const row = tableData[index];
|
||||
const layerNm = row.lyrTypeNm;
|
||||
const fid = row.fid;
|
||||
const core = GisApp.Module.layer as any;
|
||||
const r = core.showSearchResultsFromGeoJSON(layerNm, fid);
|
||||
onOpenChange(false); // SearchModal 닫기
|
||||
}
|
||||
};
|
||||
@@ -480,10 +477,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
RequestDTO.workTypes = JSON.parse(workType.replace(/'/g, '"'));
|
||||
|
||||
const res = await getData(`v1/bizInfo/fclts?${new URLSearchParams(RequestDTO).toString()}`);
|
||||
console.log('운영정보 cellClick 데이터:', res);
|
||||
console.log('운영정보 cellClick.data:', res.data);
|
||||
console.log('운영정보 length:', res.data.length);
|
||||
|
||||
const mappedData = (res.data || []).map((item: any, detailIndex: number) => {
|
||||
const formattedItem = formatDisplayData(item);
|
||||
|
||||
@@ -497,8 +490,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
|
||||
};
|
||||
});
|
||||
|
||||
console.log('운영정보 cellClick mappedData:', mappedData);
|
||||
|
||||
setDetailTableData(mappedData);
|
||||
setDetailTableCount(res.count || 0);
|
||||
} catch (error) {
|
||||
|
||||
@@ -34,7 +34,10 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"tus-js-client": "^4.3.1",
|
||||
"zod": "^3.24.2",
|
||||
"zustand": "^5.0.3"
|
||||
"zustand": "^5.0.3",
|
||||
"ol": "^7.3.0",
|
||||
"ol-ext": "^4.0.11",
|
||||
"proj4": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
|
||||
@@ -21,6 +21,7 @@ import GisToolBar from "./GisToolBar";
|
||||
import GisTree from "./GisTree";
|
||||
import "./lib/css/gis.common.css";
|
||||
import GisApp from "./lib/gis.core";
|
||||
import LoadingOverlay from '@/components/ui/LoadingBar'
|
||||
|
||||
export const GisContext = createContext({
|
||||
gisCore: null,
|
||||
@@ -149,6 +150,7 @@ function Gis({
|
||||
// 체크된 레이어 상태 관리
|
||||
const [checkedLayers, setCheckedLayers] = useState([]);
|
||||
const [checkedLayersData, setCheckedLayersData] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true) //로딩 상태
|
||||
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
@@ -216,12 +218,16 @@ function Gis({
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// 📌 재마운트 시 target 없으면 다시 지정
|
||||
// 재마운트 시 target 없으면 다시 지정
|
||||
if (!map.getTarget()) {
|
||||
map.setTarget(mapRef.current);
|
||||
}
|
||||
}
|
||||
|
||||
map.on('loadstart', function () {
|
||||
setIsLoading(false)
|
||||
});
|
||||
|
||||
const select = GisApp.Module.select;
|
||||
if (select) {
|
||||
if (select.selectInteraction) {
|
||||
@@ -316,9 +322,9 @@ function Gis({
|
||||
const handleCheckedLayersChange = useCallback(
|
||||
(checkedIds, checkedNodesData) => {
|
||||
setTimeout(() => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
}, 1000);
|
||||
},
|
||||
[onCheckedLayersChange],
|
||||
@@ -332,6 +338,7 @@ function Gis({
|
||||
|
||||
return (
|
||||
<>
|
||||
<LoadingOverlay isLoading={isLoading ?? false} />
|
||||
<GisContext.Provider
|
||||
value={{
|
||||
gisCore,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -650,7 +650,7 @@ GisApp.core.prototype = {
|
||||
*/
|
||||
getFeatureGeomProp: function (geometry, ftrIds, ftrCde) {
|
||||
let returnFeature;
|
||||
this.map.getLayers().forEach(function (layer) {
|
||||
this.map.getLayers().getArray().some(function (layer) {
|
||||
let type = layer.get('type');
|
||||
if (type != 'base') {
|
||||
let source = layer.getSource();
|
||||
@@ -673,6 +673,7 @@ GisApp.core.prototype = {
|
||||
featureFtrCde = properties['FTR_CDE'];
|
||||
if (ftrIds == featureFtrIds && ftrCde == featureFtrCde) {
|
||||
returnFeature = feature;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -685,7 +686,7 @@ GisApp.core.prototype = {
|
||||
*/
|
||||
getFeatureByFtrIdn: function (typeName, ftrIdn) {
|
||||
let returnFeature;
|
||||
this.map.getLayers().forEach(function (layer) {
|
||||
this.map.getLayers().getArray().some(function (layer) {
|
||||
let layerTypeName = layer.get('typeName');
|
||||
if (layerTypeName === typeName) {
|
||||
layer
|
||||
@@ -695,12 +696,16 @@ GisApp.core.prototype = {
|
||||
let featureFtrIdn = feature.get('ftrIdn');
|
||||
if (featureFtrIdn === ftrIdn) {
|
||||
returnFeature = feature;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return returnFeature;
|
||||
},
|
||||
getFeatureByFeatureId: function (featureId) {
|
||||
return GisApp.Module.layer.getFeatureById(featureId);
|
||||
},
|
||||
/**
|
||||
* [사용]맵 이벤트 추가
|
||||
*/
|
||||
|
||||
@@ -33,6 +33,7 @@ const GisOlLayer = function (options) {
|
||||
GisOlLayer.prototype = {
|
||||
layers: [],
|
||||
cesiumLayers: [],
|
||||
searchLayer: null,
|
||||
defaults: {},
|
||||
storageNm: 'hasudo:',
|
||||
defaultLayerOptions: {
|
||||
@@ -76,7 +77,7 @@ GisOlLayer.prototype = {
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let lyrTyNm = options.lyrTyNm;
|
||||
let typename = this.storageNm + lyrTyNm;
|
||||
|
||||
|
||||
const params = {};
|
||||
params['TYPENAME'] = typename;
|
||||
params['SERVICE'] = 'WFS';
|
||||
@@ -92,11 +93,11 @@ GisOlLayer.prototype = {
|
||||
const [minX, minY, maxX, maxY] = extent;
|
||||
const bboxFilter = `BBOX(geom, ${minX}, ${minY}, ${maxX}, ${maxY}, '${toProj}')`;
|
||||
const filter = options?.flterSql && options.flterSql && options.flterSql.trim() !== ''
|
||||
? `(${options.flterSql}) AND ${bboxFilter}`
|
||||
: bboxFilter;
|
||||
? `(${options.flterSql}) AND ${bboxFilter}`
|
||||
: bboxFilter;
|
||||
|
||||
params['CQL_FILTER'] = filter;
|
||||
|
||||
|
||||
const strParams = new URLSearchParams(params).toString();
|
||||
return url + '?' + strParams;
|
||||
},
|
||||
@@ -107,13 +108,13 @@ GisOlLayer.prototype = {
|
||||
/**
|
||||
* [사용]WFS 벡터 소스 반환
|
||||
*/
|
||||
getVectorTileSource: function (options) {
|
||||
getVectorSourceAll: function (options) {
|
||||
let url = GisApp.GEOSERVER_URL;
|
||||
let toProj = GisApp.TO_PROJ;
|
||||
let lyrTyNm = options.lyrTyNm;
|
||||
let typename = this.storageNm + lyrTyNm;
|
||||
let filter = options?.flterSql && options.flterSql && options.flterSql.trim() !== '';
|
||||
|
||||
|
||||
let params = {};
|
||||
params['TYPENAME'] = typename;
|
||||
params['SERVICE'] = 'WFS';
|
||||
@@ -150,12 +151,12 @@ GisOlLayer.prototype = {
|
||||
let cmncNm = option.cmncNm;
|
||||
|
||||
try {
|
||||
if(cmncNm == 'bbox') {
|
||||
if (cmncNm == 'bbox') {
|
||||
source = this.getVectorSourceBbox(option);
|
||||
}else {
|
||||
source = this.getVectorTileSource(option);
|
||||
} else {
|
||||
source = this.getVectorSourceAll(option);
|
||||
}
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.warn(option);
|
||||
}
|
||||
|
||||
@@ -548,7 +549,7 @@ GisOlLayer.prototype = {
|
||||
cesiumLayer['properties'] = properties;
|
||||
me.cesiumLayers.push(cesiumLayer);
|
||||
if (properties?.visible) {
|
||||
const zoomLevel = getOpenLayersZoom(cesiumScene);
|
||||
const zoomLevel = this.getOpenLayersZoom(cesiumScene);
|
||||
cesiumLayer.show = zoomLevel >= Number(properties?.minZoom);
|
||||
} else {
|
||||
cesiumLayer.show = false;
|
||||
@@ -557,7 +558,7 @@ GisOlLayer.prototype = {
|
||||
});
|
||||
if (me.cesiumLayers.length > 0) {
|
||||
cesiumScene._moveEndListener = () => {
|
||||
const zoomLevel = getOpenLayersZoom(cesiumScene); // 대략적인 줌 계산
|
||||
const zoomLevel = this.getOpenLayersZoom(cesiumScene); // 대략적인 줌 계산
|
||||
me.cesiumLayers.forEach(cesiumLayer => {
|
||||
const properties = cesiumLayer.properties;
|
||||
if (properties?.visible) {
|
||||
@@ -569,22 +570,121 @@ GisOlLayer.prototype = {
|
||||
cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener);
|
||||
}
|
||||
},
|
||||
getFeatureById: function(featureId){
|
||||
this.layers
|
||||
.getArray()
|
||||
.forEach(layer => {
|
||||
const source = layer.getSource();
|
||||
const feature = source.getFeatureById(String(featureId).trim());
|
||||
if (feature) return feature;
|
||||
});
|
||||
getFeatureById: function (featureId) {
|
||||
let returnFeature;
|
||||
this.layers.getArray().some(function (layer) {
|
||||
const source = layer.getSource();
|
||||
|
||||
if (source instanceof VectorSource) {
|
||||
const feature = source.getFeatureById(featureId);
|
||||
if (feature) {
|
||||
returnFeature = feature;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return returnFeature;
|
||||
},
|
||||
getOpenLayersZoom: function (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)));
|
||||
},
|
||||
ensureSearchLayer: function () {
|
||||
let layer = this.searchLayer;
|
||||
|
||||
if (!layer) {
|
||||
layer = new VectorLayer({
|
||||
source: new VectorSource(),
|
||||
zIndex: 9999
|
||||
});
|
||||
let map = GisApp.Module.core.map;
|
||||
map.addLayer(layer);
|
||||
}
|
||||
return layer;
|
||||
},
|
||||
clearSearchLayer: function () {
|
||||
const layer = this.ensureSearchLayer();
|
||||
layer.getSource().clear(true);
|
||||
},
|
||||
showSearchResultsFromGeoJSON: function (lyrTyNm, featureId) {
|
||||
const layer = this.ensureSearchLayer();
|
||||
|
||||
// 이전 검색결과는 깨끗이
|
||||
const prev = layer.getSource();
|
||||
if (prev) prev.clear(true);
|
||||
|
||||
//레이어 타입 설정(point, line 등. 이걸 style에서 사용)
|
||||
const layerCode = GisApp.layerCode;
|
||||
const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm);
|
||||
layer.set("gobjTy", matched.gobjTy);
|
||||
|
||||
// 새 소스 만들기
|
||||
const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId);
|
||||
|
||||
// 1) 로드 완료시 extent 맞추기
|
||||
const onLoadEnd = () => {
|
||||
const feats = source.getFeatures();
|
||||
if (feats && feats.length > 0) {
|
||||
const select = GisApp.Module.select;
|
||||
select.selectFeature(feats[0], false);
|
||||
} else {
|
||||
console.warn('[search-layer] GeoJSON에 피처가 없습니다.');
|
||||
}
|
||||
// 한번만 쓰고 이벤트 정리
|
||||
source.un('featuresloadend', onLoadEnd);
|
||||
source.un('featuresloaderror', onLoadError);
|
||||
};
|
||||
|
||||
const onLoadError = (e) => {
|
||||
console.error('[search-layer] GeoJSON 로드 실패:', e);
|
||||
source.un('featuresloadend', onLoadEnd);
|
||||
source.un('featuresloaderror', onLoadError);
|
||||
};
|
||||
|
||||
// 이벤트를 먼저 걸고 → setSource
|
||||
source.once('featuresloadend', onLoadEnd);
|
||||
source.once('featuresloaderror', onLoadError);
|
||||
|
||||
// 레이어에 소스 적용(이때부터 로딩 시작)
|
||||
layer.setSource(source);
|
||||
|
||||
// 필요시 명시적으로 재요청
|
||||
source.refresh();
|
||||
},
|
||||
/**
|
||||
* 피쳐 ID로 지오서버에서 검색해서 피처의 벡터소스를 리턴
|
||||
* @param {} lyrTyNm
|
||||
* @param {*} featureId
|
||||
* @returns
|
||||
*/
|
||||
getFeatureSourceFromGeoserver: function (lyrTyNm, featureId) {
|
||||
|
||||
const typeName = this.storageNm + lyrTyNm;
|
||||
const srsName = GisApp.TO_PROJ;
|
||||
const baseUrl = GisApp.GEOSERVER_URL;
|
||||
const source = new VectorSource({
|
||||
format: new GeoJSON(),
|
||||
url: () => {
|
||||
const params = new URLSearchParams({
|
||||
SERVICE: 'WFS',
|
||||
VERSION: '1.1.0',
|
||||
REQUEST: 'GetFeature',
|
||||
TYPENAME: typeName,
|
||||
SRSNAME: srsName,
|
||||
OUTPUTFORMAT: 'application/json',
|
||||
FEATUREID: featureId,
|
||||
});
|
||||
return `${baseUrl}?${params.toString()}`;
|
||||
},
|
||||
strategy: all,
|
||||
});
|
||||
|
||||
return source;
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user