sn: map 조회 버튼 추가
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
"use client";
|
||||
import { SelectBox, type SelectDataType } from "../../../ui";
|
||||
import { Button } from "@common/components/ui";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { transform } from "ol/proj";
|
||||
import GisApp from "../lib/gis.core";
|
||||
|
||||
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_il_swage_prcs_fclt: geoserverUrl,
|
||||
};
|
||||
|
||||
interface SearchBarProps {
|
||||
@@ -15,30 +17,125 @@ interface SearchBarProps {
|
||||
facilityTypes: { text: string; val: string }[];
|
||||
}
|
||||
|
||||
interface CenterSelectDataType extends SelectDataType {
|
||||
center?: [number, number];
|
||||
}
|
||||
|
||||
export const ProcSearchBar = ({ 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 [sidoData, setSidoData] = useState<CenterSelectDataType[]>([
|
||||
{ text: "시도", val: "0" },
|
||||
]);
|
||||
const [sigunData, setSigunData] = useState<CenterSelectDataType[]>([
|
||||
{ text: "시군구", val: "0" },
|
||||
]);
|
||||
const [filteredSigunData, setFilteredSigunData] = useState<CenterSelectDataType[]>([
|
||||
{ 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",
|
||||
facilityTypes?.length === 1 && facilityTypes[0] ? facilityTypes[0].val : "0",
|
||||
);
|
||||
|
||||
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 getGeometryCenter = (geometry: any): [number, number] | null => {
|
||||
if (!geometry?.coordinates) return null;
|
||||
|
||||
const list = res.features
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
let maxY = Number.NEGATIVE_INFINITY;
|
||||
|
||||
const visit = (coords: any) => {
|
||||
if (!Array.isArray(coords)) return;
|
||||
if (
|
||||
coords.length >= 2 &&
|
||||
typeof coords[0] === "number" &&
|
||||
typeof coords[1] === "number"
|
||||
) {
|
||||
const x = coords[0];
|
||||
const y = coords[1];
|
||||
if (x < minX) minX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y > maxY) maxY = y;
|
||||
return;
|
||||
}
|
||||
coords.forEach(visit);
|
||||
};
|
||||
|
||||
visit(geometry.coordinates);
|
||||
|
||||
if (
|
||||
!Number.isFinite(minX) ||
|
||||
!Number.isFinite(minY) ||
|
||||
!Number.isFinite(maxX) ||
|
||||
!Number.isFinite(maxY)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [(minX + maxX) / 2, (minY + maxY) / 2];
|
||||
};
|
||||
|
||||
const getMapCenterCoordinate = (centerCoord: [number, number]): [number, number] => {
|
||||
const [x, y] = centerCoord;
|
||||
const looksLikeLonLat = Math.abs(x) <= 180 && Math.abs(y) <= 90;
|
||||
const looksLikeLatLonSwapped = Math.abs(x) <= 90 && Math.abs(y) <= 180;
|
||||
|
||||
if (looksLikeLonLat) {
|
||||
return transform(centerCoord, GisApp.FROM_PROJ, GisApp.TO_PROJ) as [number, number];
|
||||
}
|
||||
|
||||
if (looksLikeLatLonSwapped) {
|
||||
return transform([y, x], GisApp.FROM_PROJ, GisApp.TO_PROJ) as [number, number];
|
||||
}
|
||||
|
||||
return centerCoord;
|
||||
};
|
||||
|
||||
const moveMapToCenter = (centerCoord?: [number, number], zoom?: number) => {
|
||||
if (!centerCoord) return;
|
||||
|
||||
const map = gisMaps.current;
|
||||
if (!map?.getView) return;
|
||||
|
||||
const view = map.getView();
|
||||
const center = getMapCenterCoordinate(centerCoord);
|
||||
if (!Number.isFinite(center[0]) || !Number.isFinite(center[1])) return;
|
||||
|
||||
const projection = view.getProjection?.();
|
||||
const extent = projection?.getExtent?.();
|
||||
if (extent) {
|
||||
const [minX, minY, maxX, maxY] = extent;
|
||||
if (center[0] < minX || center[0] > maxX || center[1] < minY || center[1] > maxY) {
|
||||
console.warn("중심좌표가 지도 범위를 벗어나 이동을 중단합니다.", { center });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
view.animate({
|
||||
center,
|
||||
...(typeof zoom === "number" ? { zoom } : {}),
|
||||
duration: 500,
|
||||
});
|
||||
};
|
||||
|
||||
const fetchSido = async () => {
|
||||
const url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_ctpv&srsName=${GisApp.TO_PROJ}&outputFormat=application/json&propertyName=mctpv_korn_nm,mctpv_cd,geom`;
|
||||
const res = await fetch(url).then((r) => r.json());
|
||||
|
||||
const list = (res.features || [])
|
||||
.map((f: any) => ({
|
||||
text: f.properties.mctpv_korn_nm,
|
||||
val: f.properties.mctpv_cd,
|
||||
center: getGeometryCenter(f.geometry) ?? undefined,
|
||||
}))
|
||||
// .sort((a: any, b: any) => a.text.localeCompare(b.text, "ko"));
|
||||
.sort((a: any, b: any) => {
|
||||
// 커스텀 순서 정의
|
||||
const customOrder = [
|
||||
@@ -76,23 +173,25 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
// 둘 다 커스텀 순서에 없으면 기본 알파벳 순서
|
||||
return a.text.localeCompare(b.text, 'ko');
|
||||
});
|
||||
|
||||
setSidoData([{ text: "시도", val: "0" }, ...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 url = `/geoserver/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo:tb_ms_sgg&srsName=${GisApp.TO_PROJ}&outputFormat=application/json&propertyName=sgg_cd,sgg_korn_nm,geom`;
|
||||
const res = await fetch(url).then((r) => r.json());
|
||||
|
||||
const list = res.features
|
||||
const list = (res.features || [])
|
||||
.map((f: any) => ({
|
||||
text: f.properties.sgg_korn_nm,
|
||||
val: f.properties.sgg_cd,
|
||||
center: getGeometryCenter(f.geometry) ?? undefined,
|
||||
}))
|
||||
.sort((a: any, b: any) => a.text.localeCompare(b.text, 'ko'));
|
||||
.sort((a: any, b: any) => a.text.localeCompare(b.text, "ko"));
|
||||
|
||||
setSigunData(list);
|
||||
};
|
||||
|
||||
//시설명 콤보박스 조회
|
||||
const fetchFacilitiesBySggNm = async (
|
||||
sggCd: string,
|
||||
sggNm: string,
|
||||
@@ -114,23 +213,18 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
|
||||
let list: SelectDataType[] = [];
|
||||
|
||||
if (
|
||||
type === "tb_il_swage_prcs_fclt" //하수처리시설
|
||||
) {
|
||||
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
|
||||
list = (json.features || [])
|
||||
.map((f: any) => ({
|
||||
text: f.properties.swtrmt_fclt_nm ?? "(이름없음)",
|
||||
val: f.id,
|
||||
}))
|
||||
.sort((a: any, b: any) => a.text.localeCompare(b.text, 'ko'));
|
||||
} else if (
|
||||
type === "tb_wr_bdst_ruse_fclt" // 재이용시설
|
||||
) {
|
||||
//재용에서 참조하는 하수처리시설 목록
|
||||
.sort((a: any, b: any) => a.text.localeCompare(b.text, "ko"));
|
||||
} 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;
|
||||
@@ -148,19 +242,14 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
text: item.ruseUtztnFcltNm ?? "(이름없음)",
|
||||
val: item.pbnstRuseFcltId,
|
||||
}));
|
||||
} else if (
|
||||
type === "tb_wr_ruse_fclt_dmd_co" //수요처
|
||||
) {
|
||||
} 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 방식
|
||||
} else if (type === "tb_wr_tmpr_dmd_co") {
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
@@ -168,7 +257,6 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
val: item.fctryMngSn,
|
||||
}));
|
||||
} else {
|
||||
// GET 방식
|
||||
const res = await fetch(url);
|
||||
const json = await res.json();
|
||||
list = (json.data || []).map((item: any) => ({
|
||||
@@ -177,27 +265,25 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
}));
|
||||
}
|
||||
|
||||
// 가나다순 정렬 후 set
|
||||
const sorted = list.slice().sort((a, b) => a.text.localeCompare(b.text, 'ko'));
|
||||
setFacilityData([{ text: "시도", val: "0" }, ...sorted]);
|
||||
const sorted = list.slice().sort((a, b) => a.text.localeCompare(b.text, "ko"));
|
||||
setFacilityData([{ text: "하수처리시설", val: "0" }, ...sorted]);
|
||||
setFacilityValue("0");
|
||||
};
|
||||
|
||||
//시도, 시군구 초기 데이터 조회
|
||||
useEffect(() => {
|
||||
fetchSido();
|
||||
fetchSigungu();
|
||||
}, []);
|
||||
|
||||
//시군구 데이터 초기화
|
||||
const initializeSigunData = useCallback(
|
||||
(sidoCd: string) => {
|
||||
if (sidoCd === "0") {
|
||||
(nextSidoCd: string) => {
|
||||
if (nextSidoCd === "0") {
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }]);
|
||||
setSigunValue("0");
|
||||
return;
|
||||
}
|
||||
const prefix = sidoCd.substring(0, 2);
|
||||
|
||||
const prefix = nextSidoCd.substring(0, 2);
|
||||
const filtered = sigunData.filter((item) => item.val.startsWith(prefix));
|
||||
setFilteredSigunData([{ text: "시군구", val: "0" }, ...filtered]);
|
||||
setSigunValue("0");
|
||||
@@ -205,7 +291,6 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
[sigunData],
|
||||
);
|
||||
|
||||
//콤보박스 선택시 연관 콤보박스 처리
|
||||
const onSelectValue = useCallback(
|
||||
(val: string, type: string) => {
|
||||
if (type === "ctpvCd") {
|
||||
@@ -213,54 +298,78 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
initializeSigunData(val);
|
||||
setFacilityData([{ text: "하수처리시설", val: "0" }]);
|
||||
setFacilityValue("0");
|
||||
} else if (type === "sggCd") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "sggCd") {
|
||||
setSigunValue(val);
|
||||
const sidoNm =
|
||||
sidoData.find((item) => item.val === sidoValue)?.text || "";
|
||||
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") {
|
||||
if (sggNm && sidoNm) fetchFacilitiesBySggNm(sggCd, sggNm, sidoNm, facilityType);
|
||||
return;
|
||||
}
|
||||
|
||||
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 || "";
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
// 하수처리시설일 때 바로 handleFeatureFocus 호출
|
||||
if (type === "fcltCd") {
|
||||
setFacilityValue(val);
|
||||
if (val !== "0") {
|
||||
handleFeatureFocus({
|
||||
fid: val,
|
||||
layerNm: "tb_il_swage_prcs_fclt", // 레이어 이름
|
||||
layerNm: "tb_il_swage_prcs_fclt",
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
[initializeSigunData, sigunData, sigunValue, sidoData, sidoValue],
|
||||
[initializeSigunData, sigunData, sigunValue, sidoData, sidoValue, facilityType],
|
||||
);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (sidoValue === "0") {
|
||||
alert("시도를 선택해 주세요");
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedSido = sidoData.find((item) => item.val === sidoValue);
|
||||
if (!selectedSido?.center) return;
|
||||
|
||||
if (sigunValue !== "0") {
|
||||
const selectedSigun =
|
||||
filteredSigunData.find((item) => item.val === sigunValue) ??
|
||||
sigunData.find((item) => item.val === sigunValue);
|
||||
|
||||
if (selectedSigun?.center) {
|
||||
moveMapToCenter(selectedSigun.center, 12);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
moveMapToCenter(selectedSido.center, 11);
|
||||
}, [sidoValue, sigunValue, sidoData, filteredSigunData, sigunData]);
|
||||
|
||||
const handleFeatureFocus = (data: any) => {
|
||||
const map = gisMaps.current;
|
||||
if (!map) {
|
||||
console.warn("지도가 초기화되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
// 항상 같은 select 객체 사용
|
||||
|
||||
const select = GisApp.Module.select as any;
|
||||
if (!select) return;
|
||||
// 1. 기존 선택 초기화
|
||||
//select.resetSelectedFeature();
|
||||
// 2. data가 OpenLayers Feature 인스턴스인지 확인
|
||||
|
||||
let foundFeature: any = null;
|
||||
let featureId = data.fid;
|
||||
const featureId = data.fid;
|
||||
|
||||
map
|
||||
.getLayers()
|
||||
.getArray()
|
||||
@@ -271,7 +380,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
if (feature) foundFeature = feature;
|
||||
}
|
||||
});
|
||||
// 3. 피처 선택
|
||||
|
||||
if (foundFeature) {
|
||||
const geometry = foundFeature.getGeometry();
|
||||
if (geometry) {
|
||||
@@ -284,7 +393,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
nearest: true,
|
||||
});
|
||||
} else {
|
||||
alert("위치정보를 찾을 수 없습니다.")
|
||||
alert("위치정보를 찾을 수 없습니다.");
|
||||
console.warn("geometry를 찾을 수 없습니다:", data);
|
||||
}
|
||||
} else {
|
||||
@@ -303,9 +412,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={sidoData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "ctpvCd")
|
||||
}
|
||||
onSelectValue={(val: any) => onSelectValue(val.toString(), "ctpvCd")}
|
||||
value={sidoValue}
|
||||
/>
|
||||
<SelectBox
|
||||
@@ -314,9 +421,7 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={filteredSigunData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "sggCd")
|
||||
}
|
||||
onSelectValue={(val: any) => onSelectValue(val.toString(), "sggCd")}
|
||||
value={sigunValue}
|
||||
/>
|
||||
<SelectBox
|
||||
@@ -325,11 +430,14 @@ export const ProcSearchBar = ({ gisMaps, facilityTypes }: SearchBarProps) => {
|
||||
selectClass="rounded-lg bg-white"
|
||||
label=""
|
||||
selectArray={facilityData}
|
||||
onSelectValue={(val: any) =>
|
||||
onSelectValue(val.toString(), "fcltCd")
|
||||
}
|
||||
onSelectValue={(val: any) => onSelectValue(val.toString(), "fcltCd")}
|
||||
value={facilityValue}
|
||||
/>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Button color="primary" className="mx-2 mt-1" size="sm" onClick={handleSearch}>
|
||||
조회
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user