diff --git a/apps/wr/app/(map-layout)/map/mainMap/page.tsx b/apps/wr/app/(map-layout)/map/mainMap/page.tsx index 331e487bd..7451ae4b5 100644 --- a/apps/wr/app/(map-layout)/map/mainMap/page.tsx +++ b/apps/wr/app/(map-layout)/map/mainMap/page.tsx @@ -24,7 +24,7 @@ const Map = () => { const fetchData = async () => { try { - const response = await mapService.getLyrStngs(); + const response = await mapService.getLyrStngs('WR_NETWORK'); setTreeData(response); // API 응답 저장 } catch (error) { console.error("데이터 불러오기 실패:", error); diff --git a/apps/wr/app/(map-layout)/service/map.ts b/apps/wr/app/(map-layout)/service/map.ts index 5002afef7..79ba7fe9e 100644 --- a/apps/wr/app/(map-layout)/service/map.ts +++ b/apps/wr/app/(map-layout)/service/map.ts @@ -30,8 +30,8 @@ export type LyrStngProps = { export const mapService = { //레이어 설정 조회 - getLyrStngs: async () => { - const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, { + getLyrStngs: async (lyrGrp : string) => { + const response = await fetch(`${API_URL}/lyrStngs/${lyrGrp}`, { method: 'GET', headers: { 'Accept': 'application/json', diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx new file mode 100644 index 000000000..9c558851c --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/drgtLegend.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React from "react"; +import { Label } from "@common/components/ui/label"; + +export const DRGT_LAYER_TYPES: string[] = [ + // 수정 필요 + 'drought1', + 'drought2', + 'drought3', + 'drought4', + 'drought5', + 'drought6', +]; +// 지정된 순서에 따른 lyr_sn 배열 +export const EXPRS_ORDERED_LAYERS: string[] = [ + // 수정 필요 + '106', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 30년 빈도 + '107', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 50년 빈도 + '108', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 80년 빈도 + '109', // 침수위험지도(tb_ms_sbmrg_dngr_map) - 100년 빈도 + '110', // 도시침수지도(tb_ms_flood_dngr_map) - 30년 빈도 + '111', // 도시침수지도(tb_ms_flood_dngr_map) - 50년 빈도 +]; + +export function DrgtLegend() { + + + return ( + <> +
+
+ +
+
+
+
+
+
+
+
가 뭄 정 보
+
+ + {/* 관심 - 1회 미만 */} + {/* 관심 - 1회 이상 */} + + {/* 주의 - 1회 미만 */} + {/* 주의 - 1회 이상 */} + + {/* 관심+주의 - 1회 미만 */} + {/* 관심+주의 - 1회 이상 */} +
+
+
+
+ + ); +} + +function LegendItem({ color, label }: { color: string; label: string }) { + return ( +
+ + {label} +
+ ); +} \ No newline at end of file diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts new file mode 100644 index 000000000..ab63e7c91 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/median.ts @@ -0,0 +1,24 @@ +import { boundingExtent, getCenter } from 'ol/extent'; +import Feature from 'ol/Feature'; +import Geometry from 'ol/geom/Geometry'; + +export function medianCoordinate2D(feature: Feature) { + // Feature를 입력 받아 중심 좌표를 계산하는 함수 + if (!feature) { + console.warn("유효한 피처가 없습니다."); + return; + } + + console.log(feature) + + const geometry = feature.getGeometry(); + if (!geometry) { + console.warn("피처에 geometry가 없습니다."); + return; + } + + const extent = geometry.getExtent(); + const center = getCenter(extent); + const centerExtent = boundingExtent([center]); + return centerExtent; +} \ No newline at end of file diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx new file mode 100644 index 000000000..a2c843351 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/page.tsx @@ -0,0 +1,88 @@ +"use client"; + +import Gis from "@common/business_components/ui/map/Gis"; +import { useEffect, useRef, useState, useCallback } from "react"; +import { useSearchParams } from "next/navigation"; +import { mapService } from "../../(map-layout)/service"; +import { DrgtLegend, DRGT_LAYER_TYPES, EXPRS_ORDERED_LAYERS } from "./drgtLegend"; +import { SearchBar } from "./searchBar"; + + +export default function MapDrgtPopUp() { + const searchParams = useSearchParams(); + const ctpvCd = searchParams.get('ctpvCd') || ''; + const sggCd = searchParams.get('sggCd') || ''; + const fromDate = searchParams.get('fromDate') || ''; + const toDate = searchParams.get('toDate') || ''; + + const gisMaps = useRef(null); + const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정 + const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + const [showDrgtLegend, setShowDrgtLegend] = useState(false); // DrgtLegend 표시 여부 + const [isPlaying, setIsPlaying] = useState(false); + + // 컴포넌트 마운트 시 1회 실행 + useEffect(() => { + fetchData(); + }, []); + + function fetchData() { + (async () => { + try { + const response = await mapService.getLyrStngs('WR_DROUGHT'); + setTreeData(response); // API 응답 저장 + console.log("Drought layers fetched:", response); + } catch (error) { + console.error("데이터 불러오기 실패:", error); + } + })(); + } + + // WaterDepth 표시 여부를 결정하는 함수 + const handleDrgtLegndVisibility = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + const drgtLegndLayers = checkedNodesData.filter(node => + DRGT_LAYER_TYPES.includes(node.data?.lyrTyNm) + ); + + const hasDrgtLegndLayer = drgtLegndLayers.length > 0; + setShowDrgtLegend(hasDrgtLegndLayer); + }, []); + + // 체크된 레이어 변경 처리 함수 + const handleCheckedLayersChange = useCallback((checkedIds: string[], checkedNodesData: any[]) => { + handleDrgtLegndVisibility(checkedIds, checkedNodesData); + }, [handleDrgtLegndVisibility]); + + + + return ( +
+ } + waterDepth={showDrgtLegend ? DrgtLegend : undefined} + isPlaying={isPlaying} + setIsPlaying={setIsPlaying as any} + targetLayerTypes={DRGT_LAYER_TYPES as any} + orderedLayers={EXPRS_ORDERED_LAYERS as any} + checkInterval={2500} + height={`h-[100vh]`} + droughtDates={{ startYmd: fromDate, endYmd: toDate }} + /> +
+ ); +} diff --git a/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx new file mode 100644 index 000000000..21a98d696 --- /dev/null +++ b/apps/wr/app/(popUp-layout)/mapDrgtPopUp/searchBar.tsx @@ -0,0 +1,173 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { SelectBox } from '@common/business_components/ui' +import { Label, Button } from '@common/components'; +import { type SelectDataType } from "@common/business_components/ui"; +import GisApp from "@common/business_components/ui/map/lib/gis.core"; +import GeoJSON from "ol/format/GeoJSON"; +import Feature from "ol/Feature"; +import Geometry from "ol/geom/Geometry"; +import { medianCoordinate2D } from "./median"; + +interface CtpvSggPrcsFcltSelectBoxProps { + gisMaps: React.RefObject; + ctpvCode: string; + sggCode: string; + fromDate: string; + toDate: string; +} + +const DEFAULT_OPTION: SelectDataType = { val: '0', text: '전체' }; + +export function SearchBar({ gisMaps, ctpvCode, sggCode }: CtpvSggPrcsFcltSelectBoxProps) { + const [ctpvCd, setCtpvCd] = useState(ctpvCode); + const [sggCd, setSggCd] = useState(sggCode); + const [ctpv, setCtpv] = useState([]); + const [sgg, setSgg] = useState([]); + + useEffect(() => { + (async () => { + await fetchSido(); + await fetchSigungu(); + setCtpvCd(ctpvCode); + setSggCd(sggCode); + })(); + }, []); + + function onViewMapClick() { + if (sggCd && sggCd !== '0') { + fetchSggData(); + return; + } + if (ctpvCd && ctpvCd !== '0') fetchCtpvData(); + } + + async function fetchCtpvData() { + try { + const ctpvData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson&cql_filter=mctpv_cd=%27${ctpvCd.trim()}%27&srsName=EPSG:5179`) + .then(res => res.json()); + console.log('시도 데이터', ctpvData.features[0]); + + const format = new GeoJSON(); + const feature: Feature = format.readFeature(ctpvData.features[0], { + dataProjection: "EPSG:5179", // GeoJSON 좌표계 + featureProjection: GisApp.TO_PROJ // 지도 좌표계 + }); + + console.log('feature', feature); + + const extent = medianCoordinate2D(feature); + console.log('GisApp.TO_PROJ', GisApp.TO_PROJ); + console.log('extent', extent) + + handleFeatureFocus(extent); + } + catch (error) { + console.error("시도 data fetching Error:", error); + } + } + + async function fetchSggData() { + try { + const sggData = await fetch(`/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson&cql_filter=sgg_cd=%27${sggCd}%27&srsName=EPSG:5179`) + .then(res => res.json()); + console.log('시군구 데이터', sggData.features[0]); + + + const format = new GeoJSON(); + const feature: Feature = format.readFeature(sggData.features[0], { + dataProjection: "EPSG:5179", // GeoJSON 좌표계 + featureProjection: GisApp.TO_PROJ // 지도 좌표계 + }); + + const extent = medianCoordinate2D(feature); + + handleFeatureFocus(extent); + } + catch (error) { + console.error("시군구 data fetching Error:", error); + } + } + + function handleFeatureFocus(extent: any) { + console.log('ctpv', ctpv); + console.log('sgg', sgg); + const map = gisMaps.current; + if (!map) { + console.warn("지도가 초기화되지 않았습니다."); + return; + } + + map.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 11 + }); + }; + + const fetchSido = async () => { + const url = `/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).then(res => res.json()); + + const list = res.features.map((f: any) => ({ + text: f.properties.mctpv_korn_nm, + val: f.properties.mctpv_cd, + })); + + console.log('시도 데이터', list); + setCtpv(list); + }; + + const fetchSigungu = async () => { + const url = `/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).then(res => res.json()); + + const list = res.features.map((f: any) => ({ + text: f.properties.sgg_korn_nm, + val: f.properties.sgg_cd, + })); + + console.log('시군구 데이터', list); + setSgg(list); + }; + + + + return ( +
+
+ + { + setCtpvCd(String(val)); + setSggCd(String(0)); + }} + selectContentClass="z-[101]" + /> + + String(opt.val).slice(0, 2) === String(ctpvCd).trim()) + ] + } + value={sggCd} + onSelectValue={val => { setSggCd(String(val)) }} + selectContentClass="z-[101]" + /> +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx index be7210f3f..f6881688a 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx @@ -9,7 +9,7 @@ import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/b import { ContentsDiv, DateMonthPicker, downloadExcel, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label, ScrollArea } from "@common/components/ui"; -import { useEffect, useState } from "react"; +import { useEffect, useState, useRef } from "react"; import { toast, Toaster } from "react-hot-toast"; export default function DrgtSttn() { @@ -26,6 +26,7 @@ export default function DrgtSttn() { isOpen: boolean; data: customDrgt[] | undefined; }>({ data: undefined, isOpen: false }); + const popupRef = useRef(null); const cellClick = (index: number) => { if (fromDate && toDate && drgtRes[index]) { @@ -121,6 +122,19 @@ export default function DrgtSttn() { setToDate(formatted); } }, [fromDate, toDate]) + + function onViewMapClick() { + const params = new URLSearchParams({ + ctpvCd: String(objSearch.city), + sggCd: String(objSearch.sgg), + fromDate: fromDate || '', + toDate: toDate || '' + }); + const popupOptions = `width=${window.screen.availWidth},height=${window.screen.availHeight},left=0,top=0,resizable=yes,scrollbars=yes`; + popupRef.current = window.open(`/mapDrgtPopUp?${params.toString()}`, "가뭄 지도 보기 팝업", popupOptions); + } + + return ( <> @@ -136,7 +150,7 @@ export default function DrgtSttn() { selectClass={""} selectArray={code.cityCd || []} onSelectValue={getSelCity} - value={objSearch?.city ||code.cityCd[0]?.val ||""} + value={objSearch?.city || code.cityCd[0]?.val || ""} />
@@ -156,7 +170,7 @@ export default function DrgtSttn() { onChange: (date: string | undefined) => { const year = date ? date.slice(0, 4) : ''; let month = date?.slice(4); - if (month&&month?.length < 2) { + if (month && month?.length < 2) { month = month?.padStart(2, "0"); } const formatted = `${year}${month}`; @@ -172,7 +186,7 @@ export default function DrgtSttn() { onChange: (date: string | undefined) => { const year = date ? date.slice(0, 4) : ''; let month = date?.slice(4); - if (month&&month.length < 2) { + if (month && month.length < 2) { month = month?.padStart(2, "0"); } const formatted = `${year}${month}`; @@ -211,7 +225,7 @@ export default function DrgtSttn() { sggText = sidoText; } - const searchArr:string[] = []; + const searchArr: string[] = []; if (sidoText) searchArr.push(sidoText); if (Array.isArray(sggText)) { searchArr.push(...sggText); @@ -234,7 +248,7 @@ export default function DrgtSttn() { diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index d068f6aa5..c5c7f8c01 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -91,6 +91,8 @@ export function middleware(req: NextRequest) { let queryParams = searchParams.toString(); const typename = searchParams.get("TYPENAME"); console.log("typename ", typename); + const startYmd = searchParams.get("START_YMD") ?? null; + const endYmd = searchParams.get("END_YMD") ?? null; let newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${queryParams ? `?${queryParams}` : ""}`; @@ -116,10 +118,13 @@ export function middleware(req: NextRequest) { typename == "hasudo:tb_wr_rswtr_fclt" || //중수도시설 typename == "hasudo:tb_wr_htdfl_fclt" || //온배수시설 typename == "hasudo:tb_wr_ruse_fclt_dmd_co" || //수요처 - typename == "hasudo:tb_wr_tmpr_dmd_co" //잠재수요처 + typename == "hasudo:tb_wr_tmpr_dmd_co" || //잠재수요처 + typename?.startsWith("hasudo:drought") //가뭄 지도 ) { const url = req.nextUrl.clone(); url.searchParams.set("TYPENAME", typename.replace("hasudo:", "")); + if (startYmd) url.searchParams.set("startYmd", startYmd); + if (endYmd) url.searchParams.set("endYmd", endYmd); queryParams = url.searchParams.toString(); newUrl = `${SEWERAGE_API_URL}/v1/map/geometry${queryParams ? `?${queryParams}` : ""}`; }