지역 필터링 기능 추가
This commit is contained in:
parent
a20712d48e
commit
469c8b2e57
|
|
@ -8,6 +8,8 @@ import { Button } from "@/components/ui/button";
|
|||
import { Loader2, RefreshCw } from "lucide-react";
|
||||
import { applyColumnMapping } from "@/lib/utils/columnMapping";
|
||||
import { getApiUrl } from "@/lib/utils/apiUrl";
|
||||
import { regionOptions, filterVehiclesByRegion } from "@/lib/constants/regionBounds";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
// Popup 말풍선 꼬리 제거 스타일
|
||||
|
|
@ -101,6 +103,9 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
const [routeLoading, setRouteLoading] = useState(false);
|
||||
const [routeDate, setRouteDate] = useState<string>(new Date().toISOString().split("T")[0]); // YYYY-MM-DD 형식
|
||||
|
||||
// 지역 필터 상태
|
||||
const [selectedRegion, setSelectedRegion] = useState<string>("all");
|
||||
|
||||
// dataSources를 useMemo로 추출 (circular reference 방지)
|
||||
const dataSources = useMemo(() => {
|
||||
return element?.dataSources || element?.chartConfig?.dataSources;
|
||||
|
|
@ -1165,6 +1170,20 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* 지역 필터 */}
|
||||
<Select value={selectedRegion} onValueChange={setSelectedRegion}>
|
||||
<SelectTrigger className="h-8 w-[140px] text-xs">
|
||||
<SelectValue placeholder="지역 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{regionOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value} className="text-xs">
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 이동경로 날짜 선택 */}
|
||||
{selectedUserId && (
|
||||
<div className="flex items-center gap-1 rounded border bg-blue-50 px-2 py-1">
|
||||
|
|
@ -1442,8 +1461,8 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
);
|
||||
})}
|
||||
|
||||
{/* 마커 렌더링 */}
|
||||
{markers.map((marker) => {
|
||||
{/* 마커 렌더링 (지역 필터 적용) */}
|
||||
{filterVehiclesByRegion(markers, selectedRegion).map((marker) => {
|
||||
// 마커의 소스에 해당하는 데이터 소스 찾기
|
||||
const sourceDataSource = dataSources?.find((ds) => ds.name === marker.source) || dataSources?.[0];
|
||||
const markerType = sourceDataSource?.markerType || "circle";
|
||||
|
|
@ -1771,7 +1790,12 @@ export default function MapTestWidgetV2({ element }: MapTestWidgetV2Props) {
|
|||
{/* 하단 정보 */}
|
||||
{(markers.length > 0 || polygons.length > 0) && (
|
||||
<div className="text-muted-foreground border-t p-2 text-xs">
|
||||
{markers.length > 0 && `마커 ${markers.length}개`}
|
||||
{markers.length > 0 && (
|
||||
<>
|
||||
마커 {filterVehiclesByRegion(markers, selectedRegion).length}개
|
||||
{selectedRegion !== "all" && ` (전체 ${markers.length}개)`}
|
||||
</>
|
||||
)}
|
||||
{markers.length > 0 && polygons.length > 0 && " · "}
|
||||
{polygons.length > 0 && `영역 ${polygons.length}개`}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
/**
|
||||
* 전국 시/도별 좌표 범위 (경계 좌표)
|
||||
* 차량 위치 필터링에 사용
|
||||
*/
|
||||
|
||||
export interface RegionBounds {
|
||||
south: number; // 최남단 위도
|
||||
north: number; // 최북단 위도
|
||||
west: number; // 최서단 경도
|
||||
east: number; // 최동단 경도
|
||||
}
|
||||
|
||||
export interface RegionOption {
|
||||
value: string;
|
||||
label: string;
|
||||
bounds?: RegionBounds;
|
||||
}
|
||||
|
||||
// 전국 시/도별 좌표 범위
|
||||
export const regionBounds: Record<string, RegionBounds> = {
|
||||
// 서울특별시
|
||||
seoul: {
|
||||
south: 37.413,
|
||||
north: 37.715,
|
||||
west: 126.734,
|
||||
east: 127.183,
|
||||
},
|
||||
// 부산광역시
|
||||
busan: {
|
||||
south: 34.879,
|
||||
north: 35.389,
|
||||
west: 128.758,
|
||||
east: 129.314,
|
||||
},
|
||||
// 대구광역시
|
||||
daegu: {
|
||||
south: 35.601,
|
||||
north: 36.059,
|
||||
west: 128.349,
|
||||
east: 128.761,
|
||||
},
|
||||
// 인천광역시
|
||||
incheon: {
|
||||
south: 37.166,
|
||||
north: 37.592,
|
||||
west: 126.349,
|
||||
east: 126.775,
|
||||
},
|
||||
// 광주광역시
|
||||
gwangju: {
|
||||
south: 35.053,
|
||||
north: 35.267,
|
||||
west: 126.652,
|
||||
east: 127.013,
|
||||
},
|
||||
// 대전광역시
|
||||
daejeon: {
|
||||
south: 36.197,
|
||||
north: 36.488,
|
||||
west: 127.246,
|
||||
east: 127.538,
|
||||
},
|
||||
// 울산광역시
|
||||
ulsan: {
|
||||
south: 35.360,
|
||||
north: 35.710,
|
||||
west: 128.958,
|
||||
east: 129.464,
|
||||
},
|
||||
// 세종특별자치시
|
||||
sejong: {
|
||||
south: 36.432,
|
||||
north: 36.687,
|
||||
west: 127.044,
|
||||
east: 127.364,
|
||||
},
|
||||
// 경기도
|
||||
gyeonggi: {
|
||||
south: 36.893,
|
||||
north: 38.284,
|
||||
west: 126.387,
|
||||
east: 127.839,
|
||||
},
|
||||
// 강원도 (강원특별자치도)
|
||||
gangwon: {
|
||||
south: 37.017,
|
||||
north: 38.613,
|
||||
west: 127.085,
|
||||
east: 129.359,
|
||||
},
|
||||
// 충청북도
|
||||
chungbuk: {
|
||||
south: 36.012,
|
||||
north: 37.261,
|
||||
west: 127.282,
|
||||
east: 128.657,
|
||||
},
|
||||
// 충청남도
|
||||
chungnam: {
|
||||
south: 35.972,
|
||||
north: 37.029,
|
||||
west: 125.927,
|
||||
east: 127.380,
|
||||
},
|
||||
// 전라북도 (전북특별자치도)
|
||||
jeonbuk: {
|
||||
south: 35.287,
|
||||
north: 36.133,
|
||||
west: 126.392,
|
||||
east: 127.923,
|
||||
},
|
||||
// 전라남도
|
||||
jeonnam: {
|
||||
south: 33.959,
|
||||
north: 35.507,
|
||||
west: 125.979,
|
||||
east: 127.921,
|
||||
},
|
||||
// 경상북도
|
||||
gyeongbuk: {
|
||||
south: 35.571,
|
||||
north: 37.144,
|
||||
west: 128.113,
|
||||
east: 130.922,
|
||||
},
|
||||
// 경상남도
|
||||
gyeongnam: {
|
||||
south: 34.599,
|
||||
north: 35.906,
|
||||
west: 127.555,
|
||||
east: 129.224,
|
||||
},
|
||||
// 제주특별자치도
|
||||
jeju: {
|
||||
south: 33.106,
|
||||
north: 33.959,
|
||||
west: 126.117,
|
||||
east: 126.978,
|
||||
},
|
||||
};
|
||||
|
||||
// 지역 선택 옵션 (드롭다운용)
|
||||
export const regionOptions: RegionOption[] = [
|
||||
{ value: "all", label: "전체" },
|
||||
{ value: "seoul", label: "서울특별시", bounds: regionBounds.seoul },
|
||||
{ value: "busan", label: "부산광역시", bounds: regionBounds.busan },
|
||||
{ value: "daegu", label: "대구광역시", bounds: regionBounds.daegu },
|
||||
{ value: "incheon", label: "인천광역시", bounds: regionBounds.incheon },
|
||||
{ value: "gwangju", label: "광주광역시", bounds: regionBounds.gwangju },
|
||||
{ value: "daejeon", label: "대전광역시", bounds: regionBounds.daejeon },
|
||||
{ value: "ulsan", label: "울산광역시", bounds: regionBounds.ulsan },
|
||||
{ value: "sejong", label: "세종특별자치시", bounds: regionBounds.sejong },
|
||||
{ value: "gyeonggi", label: "경기도", bounds: regionBounds.gyeonggi },
|
||||
{ value: "gangwon", label: "강원특별자치도", bounds: regionBounds.gangwon },
|
||||
{ value: "chungbuk", label: "충청북도", bounds: regionBounds.chungbuk },
|
||||
{ value: "chungnam", label: "충청남도", bounds: regionBounds.chungnam },
|
||||
{ value: "jeonbuk", label: "전북특별자치도", bounds: regionBounds.jeonbuk },
|
||||
{ value: "jeonnam", label: "전라남도", bounds: regionBounds.jeonnam },
|
||||
{ value: "gyeongbuk", label: "경상북도", bounds: regionBounds.gyeongbuk },
|
||||
{ value: "gyeongnam", label: "경상남도", bounds: regionBounds.gyeongnam },
|
||||
{ value: "jeju", label: "제주특별자치도", bounds: regionBounds.jeju },
|
||||
];
|
||||
|
||||
/**
|
||||
* 좌표가 특정 지역 범위 내에 있는지 확인
|
||||
*/
|
||||
export function isInRegion(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
region: string
|
||||
): boolean {
|
||||
if (region === "all") return true;
|
||||
|
||||
const bounds = regionBounds[region];
|
||||
if (!bounds) return false;
|
||||
|
||||
return (
|
||||
latitude >= bounds.south &&
|
||||
latitude <= bounds.north &&
|
||||
longitude >= bounds.west &&
|
||||
longitude <= bounds.east
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 좌표로 지역 찾기 (해당하는 첫 번째 지역 반환)
|
||||
*/
|
||||
export function findRegionByCoords(
|
||||
latitude: number,
|
||||
longitude: number
|
||||
): string | null {
|
||||
for (const [region, bounds] of Object.entries(regionBounds)) {
|
||||
if (
|
||||
latitude >= bounds.south &&
|
||||
latitude <= bounds.north &&
|
||||
longitude >= bounds.west &&
|
||||
longitude <= bounds.east
|
||||
) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 차량 목록을 지역별로 필터링
|
||||
*/
|
||||
export function filterVehiclesByRegion<
|
||||
T extends { latitude?: number; longitude?: number; lat?: number; lng?: number }
|
||||
>(vehicles: T[], region: string): T[] {
|
||||
if (region === "all") return vehicles;
|
||||
|
||||
const bounds = regionBounds[region];
|
||||
if (!bounds) return vehicles;
|
||||
|
||||
return vehicles.filter((v) => {
|
||||
const lat = v.latitude ?? v.lat;
|
||||
const lng = v.longitude ?? v.lng;
|
||||
|
||||
if (lat === undefined || lng === undefined) return false;
|
||||
|
||||
return (
|
||||
lat >= bounds.south &&
|
||||
lat <= bounds.north &&
|
||||
lng >= bounds.west &&
|
||||
lng <= bounds.east
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 지역명(한글) 가져오기
|
||||
*/
|
||||
export function getRegionLabel(regionValue: string): string {
|
||||
const option = regionOptions.find((opt) => opt.value === regionValue);
|
||||
return option?.label ?? regionValue;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue