지도 작업 중1
@@ -233,6 +233,7 @@ const Map = () => {
|
||||
<>
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
height='h-screen'
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
"use client";
|
||||
import { useDmStore } from "@/app/store";
|
||||
import {
|
||||
SearchDiv,
|
||||
SearchFormLeft,
|
||||
SelectBox,
|
||||
type SelectDataType,
|
||||
} from "@common/business_components/ui";
|
||||
@@ -48,6 +46,7 @@ export function SearchBar({
|
||||
},
|
||||
[Common.selectSigunData]
|
||||
);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
switch (type) {
|
||||
@@ -62,78 +61,58 @@ export function SearchBar({
|
||||
[initializeSigunData]
|
||||
);
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("map.ruseFcltList ", map.ruseFcltList);
|
||||
// }, [map.ruseFcltList]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="grid grid-cols-4 gap-4 ">
|
||||
<div className="col-span-4">
|
||||
<SearchDiv>
|
||||
<SearchFormLeft>
|
||||
<div className="col-span-10 flex flex-row">
|
||||
<SelectBox
|
||||
className="col-span-2 w-1/6 h-full text-2xl"
|
||||
label="시도"
|
||||
selectClass={""}
|
||||
selectArray={Common.selectSidoData || []}
|
||||
onSelectValue={(val) =>
|
||||
onSelectValue(val.toString(), "ctpvCd")
|
||||
}
|
||||
value={sidoValue || Common.selectSidoData[0]?.val || ""}
|
||||
/>
|
||||
<SelectBox
|
||||
className="col-span-2 w-1/6 h-full text-2xl ml-10"
|
||||
label="시군구"
|
||||
selectClass={""}
|
||||
selectArray={filteredSigunData || []}
|
||||
onSelectValue={(val) =>
|
||||
onSelectValue(val.toString(), "sggCd")
|
||||
}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<div className="col-span-6 flex flex-row items-center min-w-[27rem] ml-10">
|
||||
<Label className="text-xl justify-self-center mt-2 mx-3">
|
||||
하수재이용시설
|
||||
</Label>
|
||||
<SearchInputText
|
||||
label={"시설명"}
|
||||
initValue={undefined}
|
||||
setValue={(searchNm?: string) => {
|
||||
let sggOrCity = "";
|
||||
if (searchNm) {
|
||||
if (sigunValue === "0" || sigunValue === "") {
|
||||
sggOrCity = sidoValue;
|
||||
} else {
|
||||
sggOrCity = sigunValue;
|
||||
}
|
||||
findRuseFclt(sggOrCity, searchNm);
|
||||
}
|
||||
}}
|
||||
setRtnData={(rtnData: any) => {
|
||||
setHasuPk(rtnData);
|
||||
}}
|
||||
searchList={map.ruseFcltList || []}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button
|
||||
color="sky"
|
||||
className="mt-2"
|
||||
size={"sm"}
|
||||
onClick={() => {
|
||||
onApply();
|
||||
}}
|
||||
>
|
||||
적용
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SearchFormLeft>
|
||||
</SearchDiv>
|
||||
</div>
|
||||
<div className="flex justify-center w-full px-4 py-3">
|
||||
<div className="flex items-center w-full max-w-screen-lg rounded-xl bg-white shadow-md px-6 py-4 space-x-3">
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="w-[200px]"
|
||||
label="시도"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={Common.selectSidoData || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue || Common.selectSidoData[0]?.val || ""}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="w-[200px]"
|
||||
label="시군구"
|
||||
selectClass="rounded-lg bg-white"
|
||||
selectArray={filteredSigunData || []}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<Label className="text-gray-600">하수재이용시설</Label>
|
||||
<SearchInputText
|
||||
label="시설명"
|
||||
initValue={undefined}
|
||||
setValue={(searchNm?: string) => {
|
||||
let sggOrCity = "";
|
||||
if (searchNm) {
|
||||
if (sigunValue === "0" || sigunValue === "") {
|
||||
sggOrCity = sidoValue;
|
||||
} else {
|
||||
sggOrCity = sigunValue;
|
||||
}
|
||||
findRuseFclt(sggOrCity, searchNm);
|
||||
}
|
||||
}}
|
||||
setRtnData={(rtnData: any) => {
|
||||
setHasuPk(rtnData);
|
||||
}}
|
||||
searchList={map.ruseFcltList || []}
|
||||
/>
|
||||
<Button
|
||||
color="sky"
|
||||
className="ml-2 text-white"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
onApply();
|
||||
}}
|
||||
>
|
||||
적용
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 837 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 923 B |
@@ -3,6 +3,7 @@
|
||||
import Gis from "@common/business_components/ui/map/Gis";
|
||||
import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { SearchBar } from "./searchBar/searchBar";
|
||||
|
||||
interface FeatureData {
|
||||
fid?: string;
|
||||
@@ -123,22 +124,13 @@ const Map = () => {
|
||||
setIsSearchModalOpen(false);
|
||||
};
|
||||
|
||||
// onCheckedLayersChange handler
|
||||
const handleCheckedLayersChange = (
|
||||
checkedIds: string[],
|
||||
checkedNodesData: unknown[]
|
||||
) => {
|
||||
// Handle checked layers change if needed
|
||||
console.log("Checked layers changed:", checkedIds, checkedNodesData);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-container">
|
||||
<Gis
|
||||
treeData={treeData}
|
||||
isSearch={false}
|
||||
gisMaps={gisMaps}
|
||||
isMiniMap={true}
|
||||
isMiniMap={false}
|
||||
searchView={null}
|
||||
detailView={null}
|
||||
onFeatureSelect={onFeatureSelect}
|
||||
@@ -148,9 +140,9 @@ const Map = () => {
|
||||
setIsSearchModalOpen={setIsSearchModalOpen}
|
||||
isOpen={isOpen}
|
||||
isSearchModalOpen={isSearchModalOpen}
|
||||
onCheckedLayersChange={handleCheckedLayersChange}
|
||||
onCheckedLayersChange={undefined}
|
||||
imgVisble={false}
|
||||
searchBar={undefined}
|
||||
searchBar={(props:any) => <SearchBar onFeatureSelect={onFeatureSelect} gisMaps={gisMaps} />}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface SearchBarProps {
|
||||
onFeatureSelect: (feature: any) => void;
|
||||
gisMaps: React.RefObject<any>;
|
||||
}
|
||||
|
||||
export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
|
||||
const [sidoValue, setSidoValue] = useState("0");
|
||||
const [sigunValue, setSigunValue] = useState("0");
|
||||
const [sidoData, setSidoData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
|
||||
const [sigunData, setSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
|
||||
const [facilityData, setFacilityData] = useState<SelectDataType[]>([{ text: "선택", val: "0" }]);
|
||||
const [facilityValue, setFacilityValue] = useState("0");
|
||||
|
||||
const fetchSido = async () => {
|
||||
const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd`;
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
|
||||
const list = json.features.map((f: any) => ({
|
||||
text: f.properties.mctpv_korn_nm,
|
||||
val: f.properties.mctpv_cd,
|
||||
}));
|
||||
setSidoData([{ text: "선택", val: "0" }, ...list]);
|
||||
};
|
||||
|
||||
const fetchSigun = async () => {
|
||||
const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm`;
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
|
||||
const list = json.features.map((f: any) => ({
|
||||
text: f.properties.sgg_korn_nm,
|
||||
val: f.properties.sgg_cd,
|
||||
}));
|
||||
setSigunData(list);
|
||||
};
|
||||
|
||||
const fetchFacilitiesBySggNm = async (sggNm: string) => {
|
||||
const encodedSggNm = encodeURIComponent(sggNm);
|
||||
const url = `http://rsone.iptime.org:10010/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_il_swage_prcs_fclt&outputFormat=application/json&cql_filter=sgg_nm='${encodedSggNm}'`;
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
|
||||
const list = json.features.map((f: any) => ({
|
||||
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
|
||||
val: f.id, // f.properties.swtrmt_fclt_cd 도 가능
|
||||
}));
|
||||
setFacilityData([{ text: "선택", val: "0" }, ...list]);
|
||||
setFacilityValue("0");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchSido();
|
||||
fetchSigun();
|
||||
}, []);
|
||||
|
||||
const initializeSigunData = useCallback(
|
||||
(sidoCd: string) => {
|
||||
if (sidoCd === "0") {
|
||||
setFilteredSigunData([{ text: "선택", val: "0" }]);
|
||||
setSigunValue("0");
|
||||
return;
|
||||
}
|
||||
const prefix = sidoCd.substring(0, 2);
|
||||
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
|
||||
setFilteredSigunData([{ text: "선택", val: "0" }, ...filtered]);
|
||||
setSigunValue("0");
|
||||
},
|
||||
[sigunData]
|
||||
);
|
||||
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
if (type === "ctpvCd") {
|
||||
setSidoValue(val);
|
||||
initializeSigunData(val);
|
||||
setFacilityData([{ text: "선택", val: "0" }]);
|
||||
} else if (type === "sggCd") {
|
||||
setSigunValue(val);
|
||||
const sggNm = sigunData.find((item) => item.val === val)?.text || "";
|
||||
if (sggNm) fetchFacilitiesBySggNm(sggNm);
|
||||
} else if (type === "fcltCd") {
|
||||
setFacilityValue(val);
|
||||
}
|
||||
},
|
||||
[initializeSigunData, sigunData]
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
if (!gisMaps.current || facilityValue === "0") return;
|
||||
|
||||
const layers = gisMaps.current.getLayers().getArray();
|
||||
for (const layer of layers) {
|
||||
const source = layer.getSource?.();
|
||||
const feature = source?.getFeatureById?.(facilityValue);
|
||||
if (feature) {
|
||||
onFeatureSelect(feature);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
alert("해당 처리시설을 지도에서 찾을 수 없습니다.");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-4">
|
||||
<div className="col-span-4">
|
||||
<div className="card card-border flex">
|
||||
<div className="col-span-10 flex flex-row">
|
||||
<SelectBox
|
||||
labelClass="w-[40px]"
|
||||
className="mr-10 w-[200px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시도"
|
||||
selectArray={sidoData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[65px]"
|
||||
className="mr-10 w-[200px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="시군구"
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<SelectBox
|
||||
labelClass="w-[140px]"
|
||||
className="mr-3 w-[250px]"
|
||||
selectClass="rounded-lg bg-white"
|
||||
label="하수처리시설"
|
||||
selectArray={facilityData}
|
||||
onSelectValue={(val) => onSelectValue(val.toString(), "fcltCd")}
|
||||
value={facilityValue}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button
|
||||
color="primary"
|
||||
className="mx-2 mt-1 ml-10"
|
||||
size={"sm"}
|
||||
onClick={handleSearch}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 837 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 923 B |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 810 B |
|
After Width: | Height: | Size: 896 B |
|
After Width: | Height: | Size: 837 B |
|
After Width: | Height: | Size: 790 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 923 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 919 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 810 B |
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 837 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 923 B |
@@ -1,321 +1,356 @@
|
||||
import { Search } from 'lucide-react';
|
||||
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 { register } from 'ol/proj/proj4';
|
||||
import proj4 from 'proj4';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import GisMapToggle from './GisMapToggle';
|
||||
import GisSelect from './GisSelect';
|
||||
import GisToolBar from './GisToolBar';
|
||||
import GisTree from './GisTree';
|
||||
import './lib/css/gis.common.css';
|
||||
import GisApp from './lib/gis.core';
|
||||
import { Search } from "lucide-react";
|
||||
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 { register } from "ol/proj/proj4";
|
||||
import proj4 from "proj4";
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import GisMapToggle from "./GisMapToggle";
|
||||
import GisSelect from "./GisSelect";
|
||||
import GisToolBar from "./GisToolBar";
|
||||
import GisTree from "./GisTree";
|
||||
import "./lib/css/gis.common.css";
|
||||
import GisApp from "./lib/gis.core";
|
||||
|
||||
export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null });
|
||||
export const GisContext = createContext({
|
||||
gisCore: null,
|
||||
gisMap: null,
|
||||
treeData: null,
|
||||
checkedLayerIds: null,
|
||||
});
|
||||
export const useGisContext = () => useContext(GisContext);
|
||||
export const searchUpperPpls = async (fcltMngNo) => {
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || '';
|
||||
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
|
||||
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
|
||||
method: "GET",
|
||||
});
|
||||
|
||||
const res = await fetch(`${BASE_URL}/v1/fcltmhl/upperPpls/${fcltMngNo}`, {
|
||||
method: "GET",
|
||||
});
|
||||
let data = null;
|
||||
if (res.ok) {
|
||||
try {
|
||||
// 응답이 비어있는지 확인
|
||||
const contentLength = res.headers.get("content-length");
|
||||
if (contentLength === "0")
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
let data = null;
|
||||
if (res.ok) {
|
||||
try {
|
||||
// 응답이 비어있는지 확인
|
||||
const contentLength = res.headers.get('content-length');
|
||||
if (contentLength === '0') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
|
||||
const text = await res.text();
|
||||
if (!text || text.trim() === "")
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
// 응답 텍스트를 먼저 가져와서 빈 문자열인지 확인
|
||||
const text = await res.text();
|
||||
if (!text || text.trim() === '') alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
// JSON 파싱 시도
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error("JSON parsing error:", error);
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
}
|
||||
}
|
||||
|
||||
// JSON 파싱 시도
|
||||
data = JSON.parse(text);
|
||||
} catch (error) {
|
||||
console.error('JSON parsing error:', error);
|
||||
alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
}
|
||||
}
|
||||
const select = GisApp.Module.select;
|
||||
const gisMap = GisApp.Module.core?.map;
|
||||
let foundFeature = null;
|
||||
|
||||
const select = GisApp.Module.select;
|
||||
data.forEach((item) => {
|
||||
const pplMngNo = item.ppl_mng_no.trim();
|
||||
|
||||
const gisMap = GisApp.Module.core?.map;
|
||||
gisMap
|
||||
.getLayers()
|
||||
.getArray()
|
||||
.filter((e) => e.get("lyrTyNm") == "tb_ms_swage_ppl")
|
||||
.forEach((layer) => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
for (const f of features) {
|
||||
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let foundFeature = null;
|
||||
if (foundFeature) select.selectMultiFeature(foundFeature, true);
|
||||
});
|
||||
|
||||
data.forEach(item => {
|
||||
const pplMngNo = item.ppl_mng_no.trim();
|
||||
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
gisMap.getLayers().getArray().filter(e => e.get("lyrTyNm") == 'tb_ms_swage_ppl').forEach(layer => {
|
||||
const source = layer.getSource?.();
|
||||
if (source?.getFeatures) {
|
||||
const features = source.getFeatures();
|
||||
for (const f of features) {
|
||||
if (f.values_.fclt_mng_no.trim() === pplMngNo) {
|
||||
foundFeature = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let geometry = foundFeature?.getGeometry();
|
||||
if (geometry) {
|
||||
let extent = geometry.getExtent();
|
||||
|
||||
if (foundFeature) select.selectMultiFeature(foundFeature, true);
|
||||
});
|
||||
|
||||
if (!foundFeature) alert(`해당 맨홀의 상류관로를 찾을 수 없습니다.`);
|
||||
|
||||
let geometry = foundFeature?.getGeometry();
|
||||
if (geometry) {
|
||||
let extent = geometry.getExtent();
|
||||
|
||||
gisMap.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 14,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
gisMap.getView().fit(extent, {
|
||||
duration: 300,
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 14,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function Gis({
|
||||
isOpen, //우측 상세 레이어 팝업
|
||||
gisMaps,
|
||||
isSearchModalOpen,
|
||||
treeData = [],
|
||||
isSearch = false,
|
||||
isMiniMap = false,
|
||||
setIsOpen,
|
||||
setFeatureData,
|
||||
featureData,
|
||||
imgVisble = false,
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar
|
||||
isOpen, //우측 상세 레이어 팝업
|
||||
gisMaps,
|
||||
isSearchModalOpen,
|
||||
treeData = [],
|
||||
isSearch = false,
|
||||
isMiniMap = false,
|
||||
setIsOpen,
|
||||
setFeatureData,
|
||||
featureData,
|
||||
imgVisble = false,
|
||||
searchView: SearchView,
|
||||
detailView: DetailView,
|
||||
onFeatureSelect,
|
||||
setIsSearchModalOpen,
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
height:height="h-[calc(100vh-10rem)]",
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [layerNm, setLayerNm] = useState('')
|
||||
// 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);
|
||||
const checkedLayerIds = useRef([]);
|
||||
const layerNmRef = useRef('')
|
||||
const mapRef = useRef();
|
||||
const [mapObject, setMapObject] = useState({});
|
||||
const [tabVisible, setTabVisible] = useState(true);
|
||||
const [layerNm, setLayerNm] = useState("");
|
||||
// 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);
|
||||
const checkedLayerIds = useRef([]);
|
||||
const layerNmRef = useRef("");
|
||||
|
||||
// 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가
|
||||
// const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]);
|
||||
// const facilityOverlaysRef = useRef([]);
|
||||
// 체크된 레이어 상태 관리
|
||||
const [checkedLayers, setCheckedLayers] = useState([]);
|
||||
const [checkedLayersData, setCheckedLayersData] = useState([]);
|
||||
// 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가
|
||||
// const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]);
|
||||
// const facilityOverlaysRef = useRef([]);
|
||||
// 체크된 레이어 상태 관리
|
||||
const [checkedLayers, setCheckedLayers] = useState([]);
|
||||
const [checkedLayersData, setCheckedLayersData] = useState([]);
|
||||
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null)
|
||||
};
|
||||
let gisMap = null;
|
||||
const closePopup = () => {
|
||||
setIsOpen(false); // 팝업 닫기
|
||||
setFeatureData(null);
|
||||
};
|
||||
|
||||
// Feature Click Event
|
||||
const handleFeatureSelect = useCallback((data) => {
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
}, [mapInstanceRef]);
|
||||
// Feature Click Event
|
||||
const handleFeatureSelect = useCallback(
|
||||
(data) => {
|
||||
onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출
|
||||
},
|
||||
[mapInstanceRef],
|
||||
);
|
||||
|
||||
// 지도 생성 및 초기화
|
||||
useEffect(() => {
|
||||
if (!mapRef.current) return;
|
||||
if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단
|
||||
// 지도 생성 및 초기화
|
||||
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);
|
||||
// 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);
|
||||
|
||||
// OpenLayers 지도 객체 생성
|
||||
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(
|
||||
[127.4875, 37.4916],
|
||||
GisApp.FROM_PROJ,
|
||||
GisApp.TO_PROJ
|
||||
),
|
||||
minZoom: gisCore.defaults.minZoom,
|
||||
maxZoom: gisCore.defaults.maxZoom,
|
||||
}),
|
||||
});
|
||||
// OpenLayers 지도 객체 생성
|
||||
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(
|
||||
[127.4875, 37.4916],
|
||||
GisApp.FROM_PROJ,
|
||||
GisApp.TO_PROJ,
|
||||
),
|
||||
minZoom: gisCore.defaults.minZoom,
|
||||
maxZoom: gisCore.defaults.maxZoom,
|
||||
}),
|
||||
});
|
||||
|
||||
gisMaps.current = map
|
||||
gisMaps.current = map;
|
||||
|
||||
// 미니맵 추가
|
||||
if (isMiniMap) {
|
||||
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);
|
||||
}
|
||||
// 미니맵 추가
|
||||
if (isMiniMap) {
|
||||
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; // 생성 완료 플래그 설정
|
||||
// 스케일 컨트롤 및 베이스 레이어 설정
|
||||
gisCore.__proto__.map = map;
|
||||
gisCore.scaleControl();
|
||||
gisCore.changeBaseLayer("base");
|
||||
setMapObject({ map: map });
|
||||
mapInstanceRef.current = map; // 생성 완료 플래그 설정
|
||||
}, []);
|
||||
|
||||
}, []);
|
||||
// treeData 변경 시 레이어 코드 생성
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
|
||||
// treeData 변경 시 레이어 코드 생성
|
||||
useEffect(() => {
|
||||
if (!treeData || treeData.length === 0) return;
|
||||
// 기존 오버레이 및 레이어 모두 삭제
|
||||
gisCore.deleteAllOverlay();
|
||||
gisCore.deleteAllLayer();
|
||||
|
||||
// 기존 오버레이 및 레이어 모두 삭제
|
||||
gisCore.deleteAllOverlay();
|
||||
gisCore.deleteAllLayer();
|
||||
// 트리 데이터를 레이어 코드 객체로 변환
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, "lyrSn");
|
||||
GisApp.layerCode = layerCode; // 전역 레이어 코드 설정
|
||||
|
||||
// 트리 데이터를 레이어 코드 객체로 변환
|
||||
const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn');
|
||||
GisApp.layerCode = layerCode; // 전역 레이어 코드 설정
|
||||
//setTreeData(data);
|
||||
// 편집 가능한 레이어 목록 생성
|
||||
//const layerItems = getLayerItems(layerCode);
|
||||
//setEditLayerItems(prev => [...layerItems])
|
||||
}, [treeData]);
|
||||
|
||||
//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;
|
||||
};
|
||||
|
||||
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); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
|
||||
}
|
||||
}
|
||||
|
||||
if (mapObject.map) {
|
||||
try {
|
||||
gisMap = mapObject.map;
|
||||
} catch (e) {
|
||||
console.error('오류 발생:', e); // 시큐어코딩 관련 수정 : 오류 정보를 로그로 출력
|
||||
}
|
||||
}
|
||||
// 체크된 레이어 정보를 처리하는 함수
|
||||
const handleCheckedLayersChange = useCallback(
|
||||
(checkedIds, checkedNodesData) => {
|
||||
setTimeout(() => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
}, 1000);
|
||||
},
|
||||
[onCheckedLayersChange],
|
||||
);
|
||||
|
||||
// 체크된 레이어 정보를 처리하는 함수
|
||||
const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => {
|
||||
setTimeout(() => {
|
||||
setCheckedLayers(checkedIds);
|
||||
setCheckedLayersData(checkedNodesData);
|
||||
onCheckedLayersChange?.(checkedIds, checkedNodesData);
|
||||
}, 1000);
|
||||
}, [onCheckedLayersChange]);
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider
|
||||
value={{
|
||||
gisCore,
|
||||
gisMap: mapInstanceRef.current,
|
||||
treeData,
|
||||
checkedLayerIds,
|
||||
}}
|
||||
>
|
||||
<div className={`relative flex ${height}`}>
|
||||
<div
|
||||
ref={mapRef}
|
||||
className="gis-map relative flex-1"
|
||||
style={{ height: "100%", padding: 0 }}
|
||||
>
|
||||
<GisTree
|
||||
visible={tabVisible}
|
||||
setVisible={setTabVisible}
|
||||
onCheckedChange={handleCheckedLayersChange}
|
||||
onLayerIconClick={(layerNm) => {
|
||||
layerNmRef.current = layerNm;
|
||||
setLayerNm(layerNm);
|
||||
setIsOpen(true);
|
||||
const dummyFeature = {
|
||||
getProperties: () => ({}), // 최소 구조
|
||||
};
|
||||
onFeatureSelect(dummyFeature);
|
||||
}}
|
||||
imageVisible={imgVisble}
|
||||
/>
|
||||
|
||||
return (
|
||||
<>
|
||||
<GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}>
|
||||
<div className="h-[calc(100vh-10rem)] flex relative">
|
||||
<div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} >
|
||||
<GisTree
|
||||
visible={tabVisible}
|
||||
setVisible={setTabVisible}
|
||||
onCheckedChange={handleCheckedLayersChange}
|
||||
onLayerIconClick={(layerNm) => {
|
||||
layerNmRef.current = layerNm
|
||||
setLayerNm(layerNm); setIsOpen(true); const dummyFeature = {
|
||||
getProperties: () => ({}), // 최소 구조
|
||||
};
|
||||
onFeatureSelect(dummyFeature);
|
||||
}}
|
||||
imageVisible={imgVisble}
|
||||
/>
|
||||
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{isOpen && !isSearchModalOpen && (
|
||||
<DetailView
|
||||
featureData={featureData || { getProperties: () => ({}) }}
|
||||
seletectedLayerNm={layerNm}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* SearchView가 존재할 때만 렌더링 */}
|
||||
{SearchView && (
|
||||
<SearchView
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* 시도/시군구 조회 영역 */}
|
||||
{SearchBar && (
|
||||
<div
|
||||
className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded"
|
||||
style={{ maxWidth: "calc(100% - 2rem)" }}
|
||||
>
|
||||
<SearchBar />
|
||||
</div>
|
||||
)}
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */}
|
||||
{isOpen && !isSearchModalOpen && (
|
||||
<DetailView
|
||||
featureData={featureData || { getProperties: () => ({}) }}
|
||||
seletectedLayerNm={layerNm}
|
||||
onClose={closePopup} // 닫기 콜백 전달
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* SearchView가 존재할 때만 렌더링 */}
|
||||
{SearchView && (
|
||||
<SearchView
|
||||
open={isSearchModalOpen}
|
||||
onOpenChange={setIsSearchModalOpen}
|
||||
onFeatureSelect={handleFeatureSelect}
|
||||
/>
|
||||
)}
|
||||
{/* 시도/시군구 조회 영역 */}
|
||||
{SearchBar && (
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 bg-gray-300 rounded " style={{ maxWidth: 'calc(100% - 2rem)' }}>
|
||||
<SearchBar/>
|
||||
</div>
|
||||
)}
|
||||
<GisToolBar />
|
||||
<GisMapToggle />
|
||||
<GisSelect onFeatureSelect={handleFeatureSelect} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isSearch && (
|
||||
<div className="absolute right-8 top-[305px] z-20">
|
||||
<button
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
className="overflow-hidden w-[40px] h-[40px] bg-white
|
||||
border border-[#9aa7b8]
|
||||
rounded
|
||||
shadow-[0_0_5px_#999]
|
||||
flex items-center justify-center
|
||||
p-0
|
||||
"
|
||||
>
|
||||
<Search size={24} className="text-gray-700" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</GisContext.Provider>
|
||||
</>
|
||||
);
|
||||
{isSearch && (
|
||||
<div className="absolute right-8 top-[305px] z-20">
|
||||
<button
|
||||
onClick={() => setIsSearchModalOpen(true)}
|
||||
className="flex h-[40px] w-[40px] items-center justify-center overflow-hidden rounded border border-[#9aa7b8] bg-white p-0 shadow-[0_0_5px_#999]"
|
||||
>
|
||||
<Search size={24} className="text-gray-700" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</GisContext.Provider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Gis);
|
||||
|
||||
@@ -196,7 +196,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
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
|
||||
className={`absolute top-0 left-0 w-[250px] h-full 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'}`}
|
||||
>
|
||||
<div
|
||||
|
||||