물재이용 지도보기

This commit is contained in:
yschoi
2025-08-03 14:29:11 +09:00
parent 71aacf4c76
commit d45ce537a3
7 changed files with 351 additions and 28 deletions
@@ -2,6 +2,7 @@
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
import { GEOSERVER_URL } from "@/src/shared/config/env";
interface SearchBarProps {
onFeatureSelect: (feature: any) => void;
@@ -18,7 +19,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
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 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);
const json = await res.json();
@@ -30,7 +31,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
};
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 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);
const json = await res.json();
@@ -43,7 +44,7 @@ export function SearchBar({ onFeatureSelect, gisMaps }: SearchBarProps) {
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 url = `/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();
+2
View File
@@ -10,6 +10,8 @@ NEXT_PUBLIC_CLAIM_NAME = Authorization
NEXT_PUBLIC_AUTH_USER_ID = token-id
NEXT_PUBLIC_ACCESS_TOKEN = access-token
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
+29 -18
View File
@@ -5,6 +5,7 @@ import { useEffect, useRef, useState, useCallback } from "react";
import { mapService } from "../service";
import NewWindowPopup from "../newWindowPopup";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import { SearchBar } from "../searchBar/searchBar";
const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
@@ -61,24 +62,28 @@ const Map = () => {
// 2. data가 OpenLayers Feature 인스턴스인지 확인
let foundFeature: any = null;
console.log(data)
console.log(data);
//console.log(data.getId())
let featureId = data.fid;
const layerName = data.layerNm;
if (layerName === "재이용시설") {
// 재이용시설
featureId = `${layerName}.${data.searchPk}`;
} else if ( //빗물이용시설, 중수도시설
} else if (
//빗물이용시설, 중수도시설
layerName === "빗물이용시설" ||
layerName == "tb_wr_rswtr_fclt"
) {
featureId = `${layerName}.${data.bdstInfoSn}`;
} else if (layerName === "tb_il_swage_prcs_fclt") { //하수처리시설
} else if (layerName === "tb_il_swage_prcs_fclt") {
//하수처리시설
featureId = `${layerName}.${data.bscFctCd}`;
} else if (layerName === "tb_wr_htdfl_fclt") { //온배수시설
} else if (layerName === "tb_wr_htdfl_fclt") {
//온배수시설
featureId = `${layerName}.${data.bdstRuseFcltSn}`;
} else if (layerName == "tb_wr_ruse_fclt_dmd_co") { //수요처
} else if (layerName == "tb_wr_ruse_fclt_dmd_co") {
//수요처
featureId = `${layerName}.${data.dmdInfoId.replaceAll("-", "")}`;
} else {
console.warn("해당 레이어에 대한 featureId 매핑 없음", layerName);
@@ -120,17 +125,6 @@ const Map = () => {
setIsSearchModalOpen(false);
};
// MapSearchBar 래퍼 컴포넌트
// const CustomMapSearchBar = (props: any) => {
// return (
// <MapSearchBar
// {...props}
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// />
// );
// };
return (
<div className="bg-container">
<Gis
@@ -156,7 +150,24 @@ const Map = () => {
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
imgVisble={true}
searchBar={undefined}
searchBar={(props: any) => {
const facilityTypes = [
{ text: "하수처리시설", val: "tb_il_swage_prcs_fclt" },
{ text: "하수재이용시설", val: "tb_wr_bdst_ruse_fclt" },
{ text: "빗물이용시설", val: "tb_wr_rnwtr_utztn_fclt" },
{ text: "중수도시설", val: "tb_wr_rswtr_fclt" },
{ text: "온배수시설", val: "tb_wr_htdfl_fclt" },
{ text: "수요처", val: "tb_wr_ruse_fclt_dmd_co" },
{ text: "잠재수요처", val: "tb_wr_tmpr_dmd_co" },
];
return (
<SearchBar
onFeatureSelect={onFeatureSelect}
gisMaps={gisMaps}
facilityTypes={facilityTypes}
/>
);
}}
/>
</div>
);
+299
View File
@@ -0,0 +1,299 @@
"use client";
import { SelectBox, type SelectDataType } from "@common/business_components/ui";
import { Button } from "@common/components/ui";
import { useCallback, useEffect, useState } from "react";
import { SERVER_API_WR_URL } from "@/src/shared/config";
const geoserverUrl =
"/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&outputFormat=application/json";
const FACILITY_TYPE_URL_MAP: Record<string, string> = {
tb_il_swage_prcs_fclt: geoserverUrl, //하수처리시설
tb_wr_bdst_ruse_fclt:
"/reuse-service/reuseFc/pubDetailList/{year}/{sggCd}/1/1000", //재이용시설 post
tb_wr_rnwtr_utztn_fclt: "/reuse-service/reuseFc/rainyInfoList/1/1000/{sggCd}", //빗물이용시설 get
tb_wr_rswtr_fclt: "/reuse-service/reuseFc/waterreuseFc/1/1000/{sggCd}", //중수도시설 get
tb_wr_htdfl_fclt: "/reuse-service/drain/facilityList/1/1000/{sggCd}", //온배수시설 get
tb_wr_ruse_fclt_dmd_co: "/reuse-service/reuseFc/request/1/1000/{sggCd}", //수요처 get
tb_wr_tmpr_dmd_co:
"/reuse-service/reuseFc/request/temp/1/1000/{sidoNm} {sggNm}", //잠재수요처 get
};
interface SearchBarProps {
onFeatureSelect: (feature: any) => void;
gisMaps: React.RefObject<any>;
facilityTypes: { text: string; val: string }[];
}
export function SearchBar({
onFeatureSelect,
gisMaps,
facilityTypes,
}: 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 [facilityType, setFacilityType] = useState(
facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0"
);
//시도, 시군구 조회
const fetchSido = async () => {
const url = `/cms-service/api/v1/code/cache/ADSTDSTRCD/Y`;
const res = await fetch(url);
const json = await res.json();
const sidoList = json
.filter(
(item: any) =>
typeof item.lwpoCd === "string" && item.lwpoCd.length === 2
)
.map((feature: any) => ({
val: feature.lwpoCd,
text: feature.lwpoCdNm,
}));
setSidoData([{ text: "선택", val: "0" }, ...sidoList]);
const sigunList = json
.filter(
(item: any) =>
typeof item.lwpoCd === "string" && item.lwpoCd.length !== 2
)
.map((feature: any) => ({
val: feature.lwpoCd,
text: feature.lwpoCdNm,
}));
setSigunData(sigunList);
};
//시설명 콤보박스 조회
const fetchFacilitiesBySggNm = async (
sggCd: string,
sggNm: string,
sidoNm: string,
currentFacilityType?: string
) => {
const type = currentFacilityType ?? facilityType;
if (!type || type === "0") return;
const baseUrlTemplate = FACILITY_TYPE_URL_MAP[type];
if (!baseUrlTemplate) return;
const year = new Date().getFullYear().toString();
let url = baseUrlTemplate
.replace("{year}", year)
.replace("{sggCd}", sggCd)
.replace("{sidoNm}", encodeURIComponent(sidoNm))
.replace("{sggNm}", encodeURIComponent(sggNm));
let list: SelectDataType[] = [];
if (
type === "tb_il_swage_prcs_fclt" //하수처리시설
) {
const cqlFilter = `ctpv_nm='${encodeURIComponent(sidoNm)}' AND sgg_nm='${encodeURIComponent(sggNm)}'`;
url = `${url}&typeName=hasudo:${type}&cql_filter=${cqlFilter}`;
const res = await fetch(url);
const json = await res.json();
list = json.features.map((f: any) => ({
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
val: f.id,
}));
} else if (
type === "tb_wr_bdst_ruse_fclt" // 재이용시설
) {
//재용에서 참조하는 하수처리시설 목록
const paramsRes = await fetch("/reuse-service/reuseFc/pubFc");
const paramsJson = await paramsRes.json();
const params = paramsJson.data;
const res = await fetch(url, {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.ruseUtztnFcltNm ?? "(이름없음)",
val: item.pbnstRuseFcltId,
}));
} else if (
type === "tb_wr_ruse_fclt_dmd_co" //수요처
) {
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.dmdCoNm ?? "(이름없음)",
val: item.dmdInfoId,
}));
} else if (
type === "tb_wr_tmpr_dmd_co" //잠재수요처
) {
// GET 방식
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.dmdCoNm ?? "(이름없음)",
val: item.fctryMngSn,
}));
} else {
// GET 방식
const res = await fetch(url);
const json = await res.json();
list = (json.data || []).map((item: any) => ({
text: item.bdstNm ?? "(이름없음)",
val: item.bdstInfoSn,
}));
}
setFacilityData([{ text: "선택", val: "0" }, ...list]);
setFacilityValue("0");
};
//시도, 시군구 초기 데이터 조회
useEffect(() => {
fetchSido();
}, []);
//시군구 데이터 초기화
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" }]);
setFacilityValue("0");
} else if (type === "sggCd") {
setSigunValue(val);
const sidoNm =
sidoData.find((item) => item.val === sidoValue)?.text || "";
const sggNm = sigunData.find((item) => item.val === val)?.text || "";
const sggCd = sigunData.find((item) => item.val === val)?.val || "";
if (sggNm && sidoNm)
fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType);
} else if (type === "facilityType") {
setFacilityType(val);
setFacilityValue("0");
setFacilityData([{ text: "선택", val: "0" }]);
const sidoNm =
sidoData.find((item) => item.val === sidoValue)?.text || "";
const sggNm =
sigunData.find((item) => item.val === sigunValue)?.text || "";
const sggCd =
sigunData.find((item) => item.val === sigunValue)?.val || "";
if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, val);
} else if (type === "fcltCd") {
setFacilityValue(val);
}
},
[initializeSigunData, sigunData, sigunValue, sidoData, sidoValue]
);
//조회 버튼 클릭
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">
{facilityTypes.length > 1 && (
<SelectBox
labelClass="w-[120px]"
className="mr-10 w-[190px]"
selectClass="rounded-lg bg-white"
label="시설물유형"
selectArray={[{ text: "선택", val: "0" }, ...facilityTypes]}
onSelectValue={(val) =>
onSelectValue(val.toString(), "facilityType")
}
value={facilityType}
/>
)}
<SelectBox
labelClass="w-[40px]"
className="mr-10 w-[150px]"
selectClass="rounded-lg bg-white"
label="시도"
selectArray={sidoData}
onSelectValue={(val) => onSelectValue(val.toString(), "ctpvCd")}
value={sidoValue}
/>
<SelectBox
labelClass="w-[65px]"
className="mr-10 w-[150px]"
selectClass="rounded-lg bg-white"
label="시군구"
selectArray={filteredSigunData}
onSelectValue={(val) => onSelectValue(val.toString(), "sggCd")}
value={sigunValue}
/>
<SelectBox
labelClass="w-[60px]"
className="mr-3 w-[180px]"
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-5"
size={"sm"}
onClick={handleSearch}
>
조회
</Button>
</div>
</div>
</div>
</div>
);
}
+11
View File
@@ -8,6 +8,7 @@ const serverApiUrl = CS_URL;
const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
const LX_GEOSERVER_URL = process.env.NEXT_PUBLIC_LX_GEOSERVER_URL || "";
const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || "";
const REUSE_API_URL = process.env.NEXT_PUBLIC_WR_SERVER_API_URL || "";
export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname;
@@ -135,6 +136,15 @@ export function middleware(req: NextRequest) {
return NextResponse.rewrite(new URL(newUrl));
}
if (pathname.startsWith("/reuse-service")) {
const queryParams = searchParams.toString();
const newUrl = `${REUSE_API_URL}${pathname.replace("/reuse-service", "")}${
queryParams ? `?${queryParams}` : ""
}`;
console.log(`[Middleware] Rewriting from '/reuse-service' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
return NextResponse.next();
}
@@ -146,6 +156,7 @@ export const config = {
"/attach-service/:path*",
"/geoserver/:path*",
"/sewerage-service/:path*",
"/reuse-service/:path*",
"/"
],
};
@@ -326,8 +326,7 @@ function Gis({
{/* 시도/시군구 조회 영역 */}
{SearchBar && (
<div
className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded"
style={{ maxWidth: "calc(100% - 2rem)" }}
className="absolute left-1/2 top-4 z-30 -translate-x-1/2 rounded w-max"
>
<SearchBar />
</div>
@@ -143,11 +143,11 @@ GisOlLayer.prototype = {
let source;
let lyrTyNm = option.lyrTyNm;
if(lyrTyNm == 'tb_ms_flood_dngr_map'
|| lyrTyNm == 'tb_ms_sbmrg_dngr_map'
|| lyrTyNm == 'tb_ms_swage_mhl'
|| lyrTyNm == 'tb_ms_swage_ppl'
|| lyrTyNm == 'tb_ms_swage_pmstn'
if(lyrTyNm == 'tb_ms_flood_dngr_map' //침수위험지도
|| lyrTyNm == 'tb_ms_sbmrg_dngr_map' //도시침수지도
|| lyrTyNm == 'tb_ms_swage_mhl' //맨홀
|| lyrTyNm == 'tb_ms_swage_ppl' //관로
|| lyrTyNm == 'tb_ms_swage_pmstn' //펌프장
) {
source = this.getVectorSourceBbox(option);
}else {