feat: 해경관할구역 FGB 레이어 + 필터 개인설정 영속화 (AI모드/위험물)
- useCoastGuardLayer: flatgeobuf 해경관할구역 레이어 (테마별 스타일) - userSettingApi: 필터 개인설정 저장/불러오기 API - applyFilterSettings/buildFilterSettings에 AI모드(6개 서브) + 위험물 추가 - AI모드 전체 토글: 선종/국적/신호와 동일 every 패턴으로 통일 - DisplayComponent: AI모드/위험물/해경관할구역/관심구역 토글 바인딩 - 해경관할구역 기본값 ON Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
059b0670fc
커밋
8ccb261d65
BIN
.yarn-offline-cache/@repeaterjs-repeater-3.0.6.tgz
Normal file
BIN
.yarn-offline-cache/@repeaterjs-repeater-3.0.6.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/@types-rbush-4.0.0.tgz
Normal file
BIN
.yarn-offline-cache/@types-rbush-4.0.0.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/@zarrita-storage-0.1.4.tgz
Normal file
BIN
.yarn-offline-cache/@zarrita-storage-0.1.4.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/earcut-3.0.2.tgz
Normal file
BIN
.yarn-offline-cache/earcut-3.0.2.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/fflate-0.8.2.tgz
Normal file
BIN
.yarn-offline-cache/fflate-0.8.2.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/flatbuffers-25.9.23.tgz
Normal file
BIN
.yarn-offline-cache/flatbuffers-25.9.23.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/flatgeobuf-4.4.0.tgz
Normal file
BIN
.yarn-offline-cache/flatgeobuf-4.4.0.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/geotiff-3.0.2.tgz
Normal file
BIN
.yarn-offline-cache/geotiff-3.0.2.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/numcodecs-0.3.2.tgz
Normal file
BIN
.yarn-offline-cache/numcodecs-0.3.2.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/ol-10.8.0.tgz
Normal file
BIN
.yarn-offline-cache/ol-10.8.0.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/pbf-4.0.1.tgz
Normal file
BIN
.yarn-offline-cache/pbf-4.0.1.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/quickselect-3.0.0.tgz
Normal file
BIN
.yarn-offline-cache/quickselect-3.0.0.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/rbush-4.0.1.tgz
Normal file
BIN
.yarn-offline-cache/rbush-4.0.1.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/reference-spec-reader-0.2.0.tgz
Normal file
BIN
.yarn-offline-cache/reference-spec-reader-0.2.0.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/slice-source-0.4.1.tgz
Normal file
BIN
.yarn-offline-cache/slice-source-0.4.1.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/unzipit-1.4.3.tgz
Normal file
BIN
.yarn-offline-cache/unzipit-1.4.3.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/uzip-module-1.0.3.tgz
Normal file
BIN
.yarn-offline-cache/uzip-module-1.0.3.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/zarrita-0.6.1.tgz
Normal file
BIN
.yarn-offline-cache/zarrita-0.6.1.tgz
Normal file
Binary file not shown.
BIN
.yarn-offline-cache/zstddec-0.2.0.tgz
Normal file
BIN
.yarn-offline-cache/zstddec-0.2.0.tgz
Normal file
Binary file not shown.
@ -23,6 +23,7 @@
|
||||
"@stomp/stompjs": "^7.2.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.11",
|
||||
"flatgeobuf": "^4.4.0",
|
||||
"html2canvas": "^1.4.1",
|
||||
"ol": "^9.2.4",
|
||||
"ol-ext": "^4.0.10",
|
||||
|
||||
BIN
public/fgb/해경관할구역.fgb
Normal file
BIN
public/fgb/해경관할구역.fgb
Normal file
Binary file not shown.
32
src/api/userSettingApi.js
Normal file
32
src/api/userSettingApi.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { fetchWithAuth } from './fetchWithAuth';
|
||||
import { USER_SETTING_FILTER } from '../types/constants';
|
||||
|
||||
const SEARCH_ENDPOINT = '/api/cmn/personal/settings/search';
|
||||
const SAVE_ENDPOINT = '/api/cmn/personal/settings/save';
|
||||
|
||||
/**
|
||||
* 필터 설정 조회
|
||||
* @returns {Promise<Array|null>} 설정 배열 또는 null (저장된 설정 없음)
|
||||
*/
|
||||
export async function fetchUserFilter() {
|
||||
const url = `${SEARCH_ENDPOINT}?type=${USER_SETTING_FILTER}`;
|
||||
const response = await fetchWithAuth(url);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const result = await response.json();
|
||||
if (result?.code === 4006) return null;
|
||||
return result?.data?.[USER_SETTING_FILTER] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 필터 설정 저장
|
||||
* @param {Array<{code: string, value: string}>} settings
|
||||
*/
|
||||
export async function saveUserFilter(settings) {
|
||||
const response = await fetchWithAuth(SAVE_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ settings }),
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return response.json();
|
||||
}
|
||||
@ -3,6 +3,9 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import Slider from '../../common/Slider';
|
||||
import useShipStore from '../../../stores/shipStore';
|
||||
import { useMapStore, BASE_MAP_TYPES } from '../../../stores/mapStore';
|
||||
import { saveUserFilter } from '../../../api/userSettingApi';
|
||||
import { showToast } from '../../../components/common/Toast';
|
||||
import useFavoriteStore from '../../../stores/favoriteStore';
|
||||
import {
|
||||
SIGNAL_SOURCE_CODE_AIS,
|
||||
SIGNAL_SOURCE_CODE_ENAV,
|
||||
@ -25,17 +28,17 @@ import {
|
||||
NATIONAL_CODE_OTHER,
|
||||
} from '../../../types/constants';
|
||||
|
||||
// 신호원 필터 매핑
|
||||
// 신호원 필터 매핑 (메인 프로젝트와 동일 순서)
|
||||
const SIGNAL_FILTERS = [
|
||||
{ code: SIGNAL_SOURCE_CODE_AIS, label: 'AIS' },
|
||||
{ code: SIGNAL_SOURCE_CODE_ENAV, label: 'E-NAV' },
|
||||
{ code: SIGNAL_SOURCE_CODE_VPASS, label: 'V-PASS' },
|
||||
{ code: SIGNAL_SOURCE_CODE_ENAV, label: 'E-NAV' },
|
||||
{ code: SIGNAL_SOURCE_CODE_VTS_AIS, label: 'VTS_AIS' },
|
||||
{ code: SIGNAL_SOURCE_CODE_D_MF_HF, label: 'D_MF_HF' },
|
||||
{ code: SIGNAL_SOURCE_CODE_RADAR, label: 'VTS_RADAR' },
|
||||
];
|
||||
|
||||
// 선종 필터 매핑
|
||||
// 선종 필터 매핑 (메인 프로젝트와 동일 순서)
|
||||
const KIND_FILTERS = [
|
||||
{ code: SIGNAL_KIND_CODE_FISHING, label: '어선' },
|
||||
{ code: SIGNAL_KIND_CODE_PASSENGER, label: '여객선' },
|
||||
@ -43,8 +46,8 @@ const KIND_FILTERS = [
|
||||
{ code: SIGNAL_KIND_CODE_TANKER, label: '유조선' },
|
||||
{ code: SIGNAL_KIND_CODE_GOV, label: '관공선' },
|
||||
{ code: SIGNAL_KIND_CODE_KCGV, label: '함정' },
|
||||
{ code: SIGNAL_KIND_CODE_NORMAL, label: '기타' },
|
||||
{ code: SIGNAL_KIND_CODE_BUOY, label: '어망/부이' },
|
||||
{ code: SIGNAL_KIND_CODE_NORMAL, label: '기타' },
|
||||
];
|
||||
|
||||
// 국적 필터 매핑
|
||||
@ -70,20 +73,35 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
nationalVisibility,
|
||||
darkSignalVisible,
|
||||
darkSignalCount,
|
||||
aiModeVisibility,
|
||||
hazardVisible,
|
||||
toggleSourceVisibility,
|
||||
toggleKindVisibility,
|
||||
toggleNationalVisibility,
|
||||
toggleDarkSignalVisible,
|
||||
toggleAiModeEnabled,
|
||||
toggleAiModeVisibility,
|
||||
toggleHazardVisible,
|
||||
clearDarkSignals,
|
||||
} = useShipStore();
|
||||
|
||||
// 관심선박/관심구역 스토어 연결
|
||||
const isFavoriteEnabled = useFavoriteStore((s) => s.isFavoriteEnabled);
|
||||
const toggleFavoriteEnabled = useFavoriteStore((s) => s.toggleFavoriteEnabled);
|
||||
const isRealmVisible = useFavoriteStore((s) => s.isRealmVisible);
|
||||
const toggleRealmVisible = useFavoriteStore((s) => s.toggleRealmVisible);
|
||||
|
||||
// 해경관할구역
|
||||
const isCoastGuardVisible = useMapStore((s) => s.isCoastGuardVisible);
|
||||
const toggleCoastGuard = useMapStore((s) => s.toggleCoastGuard);
|
||||
|
||||
// 투명도
|
||||
const [opacity, setOpacity] = useState(70);
|
||||
|
||||
// 아코디언
|
||||
const [isAccordionOpen1, setIsAccordionOpen1] = useState(true); // 신호
|
||||
const [isAccordionOpen2, setIsAccordionOpen2] = useState(true); // 선종
|
||||
const [isAccordionOpen3, setIsAccordionOpen3] = useState(true); // 국적
|
||||
const [isAccordionOpen1, setIsAccordionOpen1] = useState(true); // 선종
|
||||
const [isAccordionOpen2, setIsAccordionOpen2] = useState(true); // 국적
|
||||
const [isAccordionOpen3, setIsAccordionOpen3] = useState(true); // 신호
|
||||
const [isAccordionOpen4, setIsAccordionOpen4] = useState(false); // AI 모드
|
||||
|
||||
const toggleAccordion1 = () => setIsAccordionOpen1(prev => !prev);
|
||||
@ -129,6 +147,21 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
});
|
||||
}, [isAllNationalsOn, nationalVisibility, toggleNationalVisibility]);
|
||||
|
||||
// AI 모드 전체 On/Off (선종/국적/신호와 동일 패턴)
|
||||
const isAllAiModeOn = Object.values(aiModeVisibility).every(v => v);
|
||||
|
||||
// 필터 저장
|
||||
const handleSaveFilter = useCallback(async () => {
|
||||
try {
|
||||
const settings = useShipStore.getState().buildFilterSettings();
|
||||
await saveUserFilter(settings);
|
||||
showToast('필터 설정이 저장되었습니다.');
|
||||
} catch (err) {
|
||||
console.error('[Filter] 저장 실패:', err);
|
||||
showToast('필터 저장에 실패했습니다.');
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 탭이동 (좌측 메뉴와 동기화)
|
||||
const [activeTab, setActiveTab] = useState(initialTab);
|
||||
|
||||
@ -165,51 +198,7 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
<div className="tabWrapInner">
|
||||
<div className="tabWrapCnt">
|
||||
|
||||
{/* 스위치그룹 01 - 신호 */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
<span>신호</span>
|
||||
<label className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="신호"
|
||||
checked={isAllSignalsOn}
|
||||
onChange={toggleAllSignals}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggleBtn ${isAccordionOpen1 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen1}
|
||||
onClick={toggleAccordion1}
|
||||
></button>
|
||||
</div>
|
||||
{/* 여기서부터 토글 */}
|
||||
<div className={`switchBox ${isAccordionOpen1 ? 'is-open' : ''}`}>
|
||||
<ul className="switchList">
|
||||
{SIGNAL_FILTERS.map(({ code, label }) => (
|
||||
<li key={code}>
|
||||
<span>{label}</span>
|
||||
<label className="switch sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={label}
|
||||
checked={sourceVisibility[code] || false}
|
||||
onChange={() => toggleSourceVisibility(code)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{/* 여기까지 */}
|
||||
</div>
|
||||
|
||||
{/* 스위치그룹 02 - 선종 */}
|
||||
{/* 스위치그룹 01 - 선종 (메인 프로젝트와 동일 순서) */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
@ -226,13 +215,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggleBtn ${isAccordionOpen2 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen2}
|
||||
onClick={toggleAccordion2}
|
||||
className={`toggleBtn ${isAccordionOpen1 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen1}
|
||||
onClick={toggleAccordion1}
|
||||
></button>
|
||||
</div>
|
||||
{/* 여기서부터 토글 */}
|
||||
<div className={`switchBox ${isAccordionOpen2 ? 'is-open' : ''}`}>
|
||||
<div className={`switchBox ${isAccordionOpen1 ? 'is-open' : ''}`}>
|
||||
<ul className="switchList">
|
||||
{KIND_FILTERS.map(({ code, label }) => (
|
||||
<li key={code}>
|
||||
@ -253,7 +242,7 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
{/* 여기까지 */}
|
||||
</div>
|
||||
|
||||
{/* 스위치그룹 03 - 국적 */}
|
||||
{/* 스위치그룹 02 - 국적 */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
@ -270,13 +259,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggleBtn ${isAccordionOpen3 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen3}
|
||||
onClick={toggleAccordion3}
|
||||
className={`toggleBtn ${isAccordionOpen2 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen2}
|
||||
onClick={toggleAccordion2}
|
||||
></button>
|
||||
</div>
|
||||
{/* 여기서부터 토글 */}
|
||||
<div className={`switchBox ${isAccordionOpen3 ? 'is-open' : ''}`}>
|
||||
<div className={`switchBox ${isAccordionOpen2 ? 'is-open' : ''}`}>
|
||||
<ul className="switchList">
|
||||
{NATIONAL_FILTERS.map(({ code, label }) => (
|
||||
<li key={code}>
|
||||
@ -296,12 +285,59 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
</div>
|
||||
{/* 여기까지 */}
|
||||
</div>
|
||||
{/* 스위치그룹 04 */}
|
||||
|
||||
{/* 스위치그룹 03 - 신호종류 */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
<span>신호</span>
|
||||
<label className="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label="신호"
|
||||
checked={isAllSignalsOn}
|
||||
onChange={toggleAllSignals}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className={`toggleBtn ${isAccordionOpen3 ? 'is-open' : ''}`}
|
||||
aria-expanded={isAccordionOpen3}
|
||||
onClick={toggleAccordion3}
|
||||
></button>
|
||||
</div>
|
||||
{/* 여기서부터 토글 */}
|
||||
<div className={`switchBox ${isAccordionOpen3 ? 'is-open' : ''}`}>
|
||||
<ul className="switchList">
|
||||
{SIGNAL_FILTERS.map(({ code, label }) => (
|
||||
<li key={code}>
|
||||
<span>{label}</span>
|
||||
<label className="switch sm">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-label={label}
|
||||
checked={sourceVisibility[code] || false}
|
||||
onChange={() => toggleSourceVisibility(code)}
|
||||
/>
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
{/* 여기까지 */}
|
||||
</div>
|
||||
{/* 스위치그룹 04 - AI 모드 */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
<span>AI 모드</span>
|
||||
<label className="switch"> <input type="checkbox" aria-label="AI 모드" /> <span></span></label>
|
||||
<label className="switch">
|
||||
<input type="checkbox" aria-label="AI 모드" checked={isAllAiModeOn} onChange={toggleAiModeEnabled} />
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@ -315,27 +351,45 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
<ul className="switchList">
|
||||
<li>
|
||||
<span>MMSI 변조</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="MMSI 변조" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="MMSI 변조" checked={aiModeVisibility.mmsiChange} onChange={() => toggleAiModeVisibility('mmsiChange')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>중국 허가선박</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="중국 허가선박" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="중국 허가선박" checked={aiModeVisibility.chinaPermission} onChange={() => toggleAiModeVisibility('chinaPermission')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>관공선</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="관공선" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="관공선" checked={aiModeVisibility.govShip} onChange={() => toggleAiModeVisibility('govShip')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>비정상 접촉</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="비정상 접촉" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="비정상 접촉" checked={aiModeVisibility.sseZoneContact} onChange={() => toggleAiModeVisibility('sseZoneContact')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>비정상 선박</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="비정상 선박" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="비정상 선박" checked={aiModeVisibility.nonPermission} onChange={() => toggleAiModeVisibility('nonPermission')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<span>북한선박</span>
|
||||
<label className="switch sm"> <input type="checkbox" aria-label="북한선박" /> <span></span></label>
|
||||
<label className="switch sm">
|
||||
<input type="checkbox" aria-label="북한선박" checked={aiModeVisibility.northKoreaAi} onChange={() => toggleAiModeVisibility('northKoreaAi')} />
|
||||
<span></span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -371,13 +425,16 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 스위치그룹 06 */}
|
||||
{/* 스위치그룹 06 - 위험물 */}
|
||||
<div className="switchGroup">
|
||||
<div className="sgHeader">
|
||||
<div className="colL">
|
||||
<span>위험물</span>
|
||||
</div>
|
||||
<label className="switch"> <input type="checkbox" aria-label="위험물" /> <span></span></label>
|
||||
<label className="switch">
|
||||
<input type="checkbox" aria-label="위험물" checked={hazardVisible} onChange={toggleHazardVisible} />
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -388,14 +445,14 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
<i className="favship"></i>
|
||||
<span>관심선박</span>
|
||||
</div>
|
||||
<label className="switch"> <input type="checkbox" aria-label="관심선박" /> <span></span></label>
|
||||
<label className="switch"> <input type="checkbox" aria-label="관심선박" checked={isFavoriteEnabled} onChange={toggleFavoriteEnabled} /> <span></span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 버튼영역 */}
|
||||
<div className="btnBox">
|
||||
<button type="button" className="btn btnLine">저장</button>
|
||||
<button type="button" className="btn btnLine" onClick={handleSaveFilter}>저장</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -466,7 +523,13 @@ export default function DisplayComponent({ isOpen, onToggle, initialTab = 'filte
|
||||
</li>
|
||||
<li>
|
||||
<label className="checkbox checkL">
|
||||
<input type="checkbox" />
|
||||
<input type="checkbox" checked={isRealmVisible} onChange={toggleRealmVisible} />
|
||||
<span>관심구역</span>
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<label className="checkbox checkL">
|
||||
<input type="checkbox" checked={isCoastGuardVisible} onChange={toggleCoastGuard} />
|
||||
<span>해경관할구역</span>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
160
src/hooks/useCoastGuardLayer.js
Normal file
160
src/hooks/useCoastGuardLayer.js
Normal file
@ -0,0 +1,160 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import VectorImageLayer from 'ol/layer/VectorImage';
|
||||
import VectorSource from 'ol/source/Vector';
|
||||
import GeoJSON from 'ol/format/GeoJSON';
|
||||
import { Style, Fill, Stroke, Text } from 'ol/style';
|
||||
import { deserialize } from 'flatgeobuf/lib/mjs/geojson.js';
|
||||
import { useMapStore, THEME_TYPES } from '../stores/mapStore';
|
||||
|
||||
const BASE_URL = import.meta.env.BASE_URL || '/';
|
||||
const FGB_URL = `${BASE_URL}fgb/해경관할구역.fgb`;
|
||||
|
||||
/** 테마별 색상 정의 */
|
||||
const THEME_STYLE = {
|
||||
[THEME_TYPES.DARK]: {
|
||||
lineColor: 'rgba(100, 200, 255, 0.8)',
|
||||
textColor: 'rgba(100, 200, 255, 0.9)',
|
||||
textStrokeColor: 'rgba(0, 0, 0, 0.6)',
|
||||
textStrokeWidth: 1,
|
||||
font: 'bold 1.1rem "NanumSquare", sans-serif',
|
||||
},
|
||||
[THEME_TYPES.LIGHT]: {
|
||||
lineColor: 'rgba(20, 60, 100, 0.7)',
|
||||
textColor: 'rgba(20, 60, 100, 0.8)',
|
||||
textStrokeColor: 'rgba(255, 255, 255, 0.7)',
|
||||
textStrokeWidth: 1,
|
||||
font: 'bold 1.1rem "NanumSquare", sans-serif',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 해경관할구역 스타일 팩토리
|
||||
* 테마에 따라 스타일 함수를 생성
|
||||
*/
|
||||
function createKcgAreaStyle(theme) {
|
||||
const ts = THEME_STYLE[theme] || THEME_STYLE[THEME_TYPES.DARK];
|
||||
|
||||
return (feature) => {
|
||||
const areaName = feature.get('해역명');
|
||||
const isSpecial = areaName != null && areaName.includes('특별');
|
||||
|
||||
if (isSpecial) {
|
||||
return [
|
||||
new Style({
|
||||
stroke: new Stroke({
|
||||
color: 'rgba(255, 80, 80, 0.8)',
|
||||
lineDash: [5, 5],
|
||||
width: 2,
|
||||
}),
|
||||
fill: new Fill({ color: 'rgba(255,255,255,0)' }),
|
||||
text: new Text({
|
||||
offsetY: -15,
|
||||
text: areaName || '',
|
||||
font: ts.font,
|
||||
fill: new Fill({ color: 'rgba(255, 80, 80, 0.9)' }),
|
||||
stroke: new Stroke({ color: ts.textStrokeColor, width: ts.textStrokeWidth }),
|
||||
}),
|
||||
zIndex: 999,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
new Style({
|
||||
stroke: new Stroke({ color: ts.lineColor, width: 2 }),
|
||||
fill: new Fill({ color: 'rgba(255,255,255,0)' }),
|
||||
text: new Text({
|
||||
offsetY: -15,
|
||||
text: areaName || '',
|
||||
font: ts.font,
|
||||
fill: new Fill({ color: ts.textColor }),
|
||||
stroke: new Stroke({ color: ts.textStrokeColor, width: ts.textStrokeWidth }),
|
||||
}),
|
||||
}),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 해경관할구역 FGB 레이어 관리 훅
|
||||
* 참조: mda-react-front/src/common/targetLayer.ts - kcgWatchZoneLayer, setFGBFeatures
|
||||
*/
|
||||
export default function useCoastGuardLayer() {
|
||||
const map = useMapStore((s) => s.map);
|
||||
const layerRef = useRef(null);
|
||||
const loadedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
|
||||
const currentTheme = useMapStore.getState().getTheme();
|
||||
const source = new VectorSource();
|
||||
const layer = new VectorImageLayer({
|
||||
source,
|
||||
zIndex: 45,
|
||||
style: createKcgAreaStyle(currentTheme),
|
||||
declutter: true,
|
||||
visible: useMapStore.getState().isCoastGuardVisible,
|
||||
});
|
||||
|
||||
map.addLayer(layer);
|
||||
layerRef.current = layer;
|
||||
|
||||
// FGB 파일 로드 (1회)
|
||||
if (!loadedRef.current) {
|
||||
loadedRef.current = true;
|
||||
loadFgb(source);
|
||||
}
|
||||
|
||||
// visible 토글 구독
|
||||
const unsubVisible = useMapStore.subscribe(
|
||||
(state) => state.isCoastGuardVisible,
|
||||
(isVisible) => {
|
||||
if (layerRef.current) {
|
||||
layerRef.current.setVisible(isVisible);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// 배경지도(테마) 변경 구독 → 스타일 재적용
|
||||
const unsubTheme = useMapStore.subscribe(
|
||||
(state) => state.baseMapType,
|
||||
() => {
|
||||
if (layerRef.current) {
|
||||
const theme = useMapStore.getState().getTheme();
|
||||
layerRef.current.setStyle(createKcgAreaStyle(theme));
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
unsubVisible();
|
||||
unsubTheme();
|
||||
if (map && layerRef.current) {
|
||||
map.removeLayer(layerRef.current);
|
||||
}
|
||||
layerRef.current = null;
|
||||
};
|
||||
}, [map]);
|
||||
}
|
||||
|
||||
/**
|
||||
* FlatGeobuf 파일 로드 → VectorSource에 Feature 추가
|
||||
*/
|
||||
async function loadFgb(source) {
|
||||
try {
|
||||
const response = await fetch(FGB_URL);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
|
||||
const format = new GeoJSON();
|
||||
|
||||
for await (const geojsonFeature of deserialize(response.body)) {
|
||||
const feature = format.readFeature(geojsonFeature);
|
||||
source.addFeature(feature);
|
||||
}
|
||||
|
||||
console.log(`[useCoastGuardLayer] 해경관할구역 ${source.getFeatures().length}건 로드 완료`);
|
||||
} catch (err) {
|
||||
console.warn('[useCoastGuardLayer] FGB 로드 실패:', err);
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,9 @@ import AreaSearchTimeline from '../areaSearch/components/AreaSearchTimeline';
|
||||
import AreaSearchTooltip from '../areaSearch/components/AreaSearchTooltip';
|
||||
import useMeasure from './measure/useMeasure';
|
||||
import useTrackingMode from '../hooks/useTrackingMode';
|
||||
import useFavoriteData from '../hooks/useFavoriteData';
|
||||
import useRealmLayer from '../hooks/useRealmLayer';
|
||||
import useCoastGuardLayer from '../hooks/useCoastGuardLayer';
|
||||
import './measure/measure.scss';
|
||||
import './MapContainer.scss';
|
||||
|
||||
@ -71,6 +74,15 @@ export default function MapContainer() {
|
||||
// STOMP 선박 데이터 연결
|
||||
useShipData({ autoConnect: true });
|
||||
|
||||
// 관심선박 + 관심구역 데이터 로딩
|
||||
useFavoriteData();
|
||||
|
||||
// 관심구역 OL 레이어
|
||||
useRealmLayer();
|
||||
|
||||
// 해경관할구역 FGB 레이어
|
||||
useCoastGuardLayer();
|
||||
|
||||
// Deck.gl 선박 레이어
|
||||
const { deckRef } = useShipLayer(map);
|
||||
|
||||
|
||||
@ -119,4 +119,8 @@ export const useMapStore = create(subscribeWithSelector((set, get) => ({
|
||||
[layerName]: !state.layerVisibility[layerName],
|
||||
},
|
||||
})),
|
||||
|
||||
// 해경관할구역 레이어
|
||||
isCoastGuardVisible: true,
|
||||
toggleCoastGuard: () => set((s) => ({ isCoastGuardVisible: !s.isCoastGuardVisible })),
|
||||
})));
|
||||
|
||||
@ -27,6 +27,7 @@ import {
|
||||
NATIONAL_CODE_OTHER,
|
||||
SOURCE_PRIORITY_RANK,
|
||||
SOURCE_TO_ACTIVE_KEY,
|
||||
USER_SETTING_CODES,
|
||||
} from '../types/constants';
|
||||
|
||||
// =====================
|
||||
@ -238,6 +239,19 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({
|
||||
/** 마지막 모달 위치 (새 모달 초기 위치 계산용) */
|
||||
lastModalPos: null,
|
||||
|
||||
/** AI 모드 서브 토글 (메인 토글은 컴포넌트에서 every()로 파생 — 선종/국적/신호와 동일 패턴) */
|
||||
aiModeVisibility: {
|
||||
mmsiChange: false, // MMSI 변조
|
||||
chinaPermission: false, // 중국 허가선박
|
||||
govShip: false, // 관공선
|
||||
sseZoneContact: false, // 비정상 접촉
|
||||
nonPermission: false, // 비정상 선박
|
||||
northKoreaAi: false, // 북한선박
|
||||
},
|
||||
|
||||
/** 위험물 표시 여부 */
|
||||
hazardVisible: false,
|
||||
|
||||
/** 다크시그널(소실신호) 표시 여부 */
|
||||
darkSignalVisible: false,
|
||||
|
||||
@ -456,6 +470,33 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({
|
||||
/**
|
||||
* 다크시그널 표시 토글
|
||||
*/
|
||||
toggleAiModeEnabled: () => {
|
||||
set((state) => {
|
||||
const allOn = Object.values(state.aiModeVisibility).every(v => v);
|
||||
const next = !allOn;
|
||||
return {
|
||||
aiModeVisibility: {
|
||||
mmsiChange: next,
|
||||
chinaPermission: next,
|
||||
govShip: next,
|
||||
sseZoneContact: next,
|
||||
nonPermission: next,
|
||||
northKoreaAi: next,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
toggleAiModeVisibility: (key) => {
|
||||
set((state) => ({
|
||||
aiModeVisibility: { ...state.aiModeVisibility, [key]: !state.aiModeVisibility[key] },
|
||||
}));
|
||||
},
|
||||
|
||||
toggleHazardVisible: () => {
|
||||
set((state) => ({ hazardVisible: !state.hazardVisible }));
|
||||
},
|
||||
|
||||
toggleDarkSignalVisible: () => {
|
||||
set((state) => ({
|
||||
darkSignalVisible: !state.darkSignalVisible,
|
||||
@ -744,6 +785,103 @@ const useShipStore = create(subscribeWithSelector((set, get) => ({
|
||||
set((state) => ({ showLegend: !state.showLegend }));
|
||||
},
|
||||
|
||||
/**
|
||||
* 서버에서 불러온 필터 설정 배열을 스토어에 적용
|
||||
* 참조: mda-react-front/src/common/userSetting.ts
|
||||
* @param {Array<{settingCode: string, settingValue: string}>} filterArray
|
||||
*/
|
||||
applyFilterSettings: (filterArray) => {
|
||||
if (!Array.isArray(filterArray) || filterArray.length === 0) return;
|
||||
|
||||
const toBoolean = (item) => item?.settingValue === 'true';
|
||||
|
||||
// settingCode → settingValue 맵 생성
|
||||
const map = {};
|
||||
filterArray.forEach((item) => {
|
||||
if (item?.settingCode) map[item.settingCode] = item;
|
||||
});
|
||||
|
||||
set({
|
||||
kindVisibility: {
|
||||
[SIGNAL_KIND_CODE_FISHING]: toBoolean(map[USER_SETTING_CODES.FISHING]),
|
||||
[SIGNAL_KIND_CODE_PASSENGER]: toBoolean(map[USER_SETTING_CODES.PASS]),
|
||||
[SIGNAL_KIND_CODE_CARGO]: toBoolean(map[USER_SETTING_CODES.CARGO]),
|
||||
[SIGNAL_KIND_CODE_TANKER]: toBoolean(map[USER_SETTING_CODES.TANKER]),
|
||||
[SIGNAL_KIND_CODE_GOV]: toBoolean(map[USER_SETTING_CODES.GOV]),
|
||||
[SIGNAL_KIND_CODE_KCGV]: toBoolean(map[USER_SETTING_CODES.KCGV]),
|
||||
[SIGNAL_KIND_CODE_NORMAL]: toBoolean(map[USER_SETTING_CODES.NORMAL]),
|
||||
[SIGNAL_KIND_CODE_BUOY]: toBoolean(map[USER_SETTING_CODES.BUOY]),
|
||||
},
|
||||
nationalVisibility: {
|
||||
[NATIONAL_CODE_KR]: toBoolean(map[USER_SETTING_CODES.KOREA]),
|
||||
[NATIONAL_CODE_CN]: toBoolean(map[USER_SETTING_CODES.CHINA]),
|
||||
[NATIONAL_CODE_JP]: toBoolean(map[USER_SETTING_CODES.JAPAN]),
|
||||
[NATIONAL_CODE_KP]: toBoolean(map[USER_SETTING_CODES.NORTH_KOREA]),
|
||||
[NATIONAL_CODE_OTHER]: toBoolean(map[USER_SETTING_CODES.ETC_NATION]),
|
||||
},
|
||||
sourceVisibility: {
|
||||
[SIGNAL_SOURCE_CODE_AIS]: toBoolean(map[USER_SETTING_CODES.AIS]),
|
||||
[SIGNAL_SOURCE_CODE_VPASS]: toBoolean(map[USER_SETTING_CODES.VPASS]),
|
||||
[SIGNAL_SOURCE_CODE_ENAV]: toBoolean(map[USER_SETTING_CODES.ENAV]),
|
||||
[SIGNAL_SOURCE_CODE_VTS_AIS]: toBoolean(map[USER_SETTING_CODES.VTS_AIS]),
|
||||
[SIGNAL_SOURCE_CODE_D_MF_HF]: toBoolean(map[USER_SETTING_CODES.D_MF_HF]),
|
||||
[SIGNAL_SOURCE_CODE_RADAR]: toBoolean(map[USER_SETTING_CODES.RADAR]),
|
||||
},
|
||||
darkSignalVisible: toBoolean(map[USER_SETTING_CODES.LOST_SIGNAL]),
|
||||
// AI 모드 (메인 토글은 하위 토글에서 파생 — 서버에 000039가 없을 수 있음)
|
||||
aiModeVisibility: {
|
||||
mmsiChange: toBoolean(map[USER_SETTING_CODES.MMSI_CHANGE]),
|
||||
chinaPermission: toBoolean(map[USER_SETTING_CODES.CHINA_PERMISSION]),
|
||||
govShip: toBoolean(map[USER_SETTING_CODES.GOV_SHIP]),
|
||||
sseZoneContact: toBoolean(map[USER_SETTING_CODES.SSE_ZONE_CONTACT]),
|
||||
nonPermission: toBoolean(map[USER_SETTING_CODES.NON_PERMISSION]),
|
||||
northKoreaAi: toBoolean(map[USER_SETTING_CODES.NORTH_KOREA_AI]),
|
||||
},
|
||||
// 위험물
|
||||
hazardVisible: toBoolean(map[USER_SETTING_CODES.HAZARD]),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 현재 필터 상태를 서버 저장 형식으로 직렬화
|
||||
* @returns {Array<{code: string, value: string}>}
|
||||
*/
|
||||
buildFilterSettings: () => {
|
||||
const { kindVisibility, sourceVisibility, nationalVisibility, darkSignalVisible, aiModeVisibility, hazardVisible } = get();
|
||||
return [
|
||||
{ code: USER_SETTING_CODES.FISHING, value: String(!!kindVisibility[SIGNAL_KIND_CODE_FISHING]) },
|
||||
{ code: USER_SETTING_CODES.PASS, value: String(!!kindVisibility[SIGNAL_KIND_CODE_PASSENGER]) },
|
||||
{ code: USER_SETTING_CODES.CARGO, value: String(!!kindVisibility[SIGNAL_KIND_CODE_CARGO]) },
|
||||
{ code: USER_SETTING_CODES.TANKER, value: String(!!kindVisibility[SIGNAL_KIND_CODE_TANKER]) },
|
||||
{ code: USER_SETTING_CODES.GOV, value: String(!!kindVisibility[SIGNAL_KIND_CODE_GOV]) },
|
||||
{ code: USER_SETTING_CODES.KCGV, value: String(!!kindVisibility[SIGNAL_KIND_CODE_KCGV]) },
|
||||
{ code: USER_SETTING_CODES.NORMAL, value: String(!!kindVisibility[SIGNAL_KIND_CODE_NORMAL]) },
|
||||
{ code: USER_SETTING_CODES.BUOY, value: String(!!kindVisibility[SIGNAL_KIND_CODE_BUOY]) },
|
||||
{ code: USER_SETTING_CODES.KOREA, value: String(!!nationalVisibility[NATIONAL_CODE_KR]) },
|
||||
{ code: USER_SETTING_CODES.CHINA, value: String(!!nationalVisibility[NATIONAL_CODE_CN]) },
|
||||
{ code: USER_SETTING_CODES.JAPAN, value: String(!!nationalVisibility[NATIONAL_CODE_JP]) },
|
||||
{ code: USER_SETTING_CODES.NORTH_KOREA, value: String(!!nationalVisibility[NATIONAL_CODE_KP]) },
|
||||
{ code: USER_SETTING_CODES.ETC_NATION, value: String(!!nationalVisibility[NATIONAL_CODE_OTHER]) },
|
||||
{ code: USER_SETTING_CODES.AIS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_AIS]) },
|
||||
{ code: USER_SETTING_CODES.VPASS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_VPASS]) },
|
||||
{ code: USER_SETTING_CODES.ENAV, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_ENAV]) },
|
||||
{ code: USER_SETTING_CODES.VTS_AIS, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_VTS_AIS]) },
|
||||
{ code: USER_SETTING_CODES.D_MF_HF, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_D_MF_HF]) },
|
||||
{ code: USER_SETTING_CODES.RADAR, value: String(!!sourceVisibility[SIGNAL_SOURCE_CODE_RADAR]) },
|
||||
{ code: USER_SETTING_CODES.LOST_SIGNAL, value: String(!!darkSignalVisible) },
|
||||
// AI 모드
|
||||
{ code: USER_SETTING_CODES.AI, value: String(Object.values(aiModeVisibility).every(v => v)) },
|
||||
{ code: USER_SETTING_CODES.MMSI_CHANGE, value: String(!!aiModeVisibility.mmsiChange) },
|
||||
{ code: USER_SETTING_CODES.CHINA_PERMISSION, value: String(!!aiModeVisibility.chinaPermission) },
|
||||
{ code: USER_SETTING_CODES.GOV_SHIP, value: String(!!aiModeVisibility.govShip) },
|
||||
{ code: USER_SETTING_CODES.SSE_ZONE_CONTACT, value: String(!!aiModeVisibility.sseZoneContact) },
|
||||
{ code: USER_SETTING_CODES.NON_PERMISSION, value: String(!!aiModeVisibility.nonPermission) },
|
||||
{ code: USER_SETTING_CODES.NORTH_KOREA_AI, value: String(!!aiModeVisibility.northKoreaAi) },
|
||||
// 위험물
|
||||
{ code: USER_SETTING_CODES.HAZARD, value: String(!!hazardVisible) },
|
||||
];
|
||||
},
|
||||
|
||||
/**
|
||||
* 모든 선박 데이터 초기화
|
||||
*/
|
||||
|
||||
@ -331,3 +331,49 @@ export const SIGNAL_SOURCE_LIST = [
|
||||
// =====================
|
||||
export const TRACK_QUERY_MAX_DAYS = 7; // 최대 조회기간 (일)
|
||||
export const TRACK_QUERY_DEFAULT_DAYS = 3; // 기본 조회기간 (일)
|
||||
|
||||
// =====================
|
||||
// 세션 관리 (메인 프로젝트 동일)
|
||||
// =====================
|
||||
export const SESSION_TIMEOUT_MS = 14400000; // 4시간
|
||||
export const KCGV_GROUP_IDS = ['2', '18']; // 함정용 사용자 그룹
|
||||
|
||||
// =====================
|
||||
// 개인설정 API 타입 코드
|
||||
// 참조: mda-react-front/src/types/constants.ts (648-695)
|
||||
// =====================
|
||||
export const USER_SETTING_FILTER = '000001';
|
||||
|
||||
// 필터 개별 설정 코드 (저장용 배열 인덱스 → 코드)
|
||||
export const USER_SETTING_CODES = {
|
||||
FISHING: '000001',
|
||||
PASS: '000002',
|
||||
CARGO: '000003',
|
||||
TANKER: '000004',
|
||||
GOV: '000005',
|
||||
KCGV: '000006',
|
||||
NORMAL: '000008',
|
||||
KOREA: '000009',
|
||||
CHINA: '000010',
|
||||
JAPAN: '000011',
|
||||
NORTH_KOREA: '000012',
|
||||
ETC_NATION: '000013',
|
||||
AIS: '000014',
|
||||
VPASS: '000015',
|
||||
ENAV: '000016',
|
||||
VTS_AIS: '000017',
|
||||
D_MF_HF: '000018',
|
||||
RADAR: '000019',
|
||||
LOST_SIGNAL: '000026',
|
||||
BUOY: '000028',
|
||||
// AI 모드 설정 코드 (참조: mda-react-front/src/types/constants.ts)
|
||||
AI: '000039', // AI 모드 전체 토글
|
||||
MMSI_CHANGE: '000020', // MMSI 변조
|
||||
CHINA_PERMISSION: '000021', // 중국 허가선박
|
||||
GOV_SHIP: '000022', // 관공선
|
||||
SSE_ZONE_CONTACT: '000023', // 비정상 접촉
|
||||
NON_PERMISSION: '000024', // 비정상 선박
|
||||
NORTH_KOREA_AI: '000077', // 북한선박
|
||||
// 위험물
|
||||
HAZARD: '000027',
|
||||
};
|
||||
|
||||
135
yarn.lock
135
yarn.lock
@ -908,6 +908,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.2.tgz#156c4b481c0bee22a19f7924728a67120de06971"
|
||||
integrity sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==
|
||||
|
||||
"@repeaterjs/repeater@3.0.6":
|
||||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@repeaterjs/repeater/-/repeater-3.0.6.tgz#be23df0143ceec3c69f8b6c2517971a5578fdaa2"
|
||||
integrity sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==
|
||||
|
||||
"@rolldown/pluginutils@1.0.0-beta.27":
|
||||
version "1.0.0-beta.27"
|
||||
resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f"
|
||||
@ -1160,6 +1165,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.7.tgz#aa0e4af9855d81153a29ff84cc44cce25298eda9"
|
||||
integrity sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==
|
||||
|
||||
"@types/rbush@4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/rbush/-/rbush-4.0.0.tgz#b327bf54952e9c924ea6702c36904c2ce1d47f35"
|
||||
integrity sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==
|
||||
|
||||
"@ungap/structured-clone@^1.2.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
|
||||
@ -1177,6 +1187,14 @@
|
||||
"@types/babel__core" "^7.20.5"
|
||||
react-refresh "^0.17.0"
|
||||
|
||||
"@zarrita/storage@^0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@zarrita/storage/-/storage-0.1.4.tgz#05d7d1d43fc0163d22a17356b619ffeb1ed223d7"
|
||||
integrity sha512-qURfJAQcQGRfDQ4J9HaCjGaj3jlJKc66bnRk6G/IeLUsM7WKyG7Bzsuf1EZurSXyc0I4LVcu6HaeQQ4d3kZ16g==
|
||||
dependencies:
|
||||
reference-spec-reader "^0.2.0"
|
||||
unzipit "1.4.3"
|
||||
|
||||
a5-js@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/a5-js/-/a5-js-0.5.0.tgz#b0241651efdf573229d6f8e25243be31cd0b9451"
|
||||
@ -1646,6 +1664,11 @@ earcut@^2.2.3, earcut@^2.2.4:
|
||||
resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a"
|
||||
integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==
|
||||
|
||||
earcut@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/earcut/-/earcut-3.0.2.tgz#d478a29aaf99acf418151493048aa197d0512248"
|
||||
integrity sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==
|
||||
|
||||
electron-to-chromium@^1.5.263:
|
||||
version "1.5.286"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz#142be1ab5e1cd5044954db0e5898f60a4960384e"
|
||||
@ -1985,6 +2008,11 @@ fflate@0.7.4:
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
|
||||
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
|
||||
|
||||
fflate@^0.8.0:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
|
||||
integrity sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
@ -2009,6 +2037,22 @@ flat-cache@^3.0.4:
|
||||
keyv "^4.5.3"
|
||||
rimraf "^3.0.2"
|
||||
|
||||
flatbuffers@25.9.23:
|
||||
version "25.9.23"
|
||||
resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-25.9.23.tgz#346811557fe9312ab5647535e793c761e9c81eb1"
|
||||
integrity sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==
|
||||
|
||||
flatgeobuf@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/flatgeobuf/-/flatgeobuf-4.4.0.tgz#f2067f359ed35a5bd6a19e0cde1a3cd2d16d6c37"
|
||||
integrity sha512-uUt1xxywP+q8K73MmyKtapF4++dMCzvoqH+ojBTsCtZBbnQEg5qy0Ujze61Rwmpmt6Ra526jpRFHtEkFun5YVw==
|
||||
dependencies:
|
||||
"@repeaterjs/repeater" "3.0.6"
|
||||
flatbuffers "25.9.23"
|
||||
slice-source "0.4.1"
|
||||
optionalDependencies:
|
||||
ol ">=10"
|
||||
|
||||
flatted@^3.2.9:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
|
||||
@ -2093,6 +2137,20 @@ geotiff@^2.0.7:
|
||||
xml-utils "^1.0.2"
|
||||
zstddec "^0.1.0"
|
||||
|
||||
geotiff@^3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-3.0.2.tgz#f3ce7d9a6c9278b8b4e1638ffe10a08d507c8255"
|
||||
integrity sha512-KZ+0YK8gW9HWitovPhfHvkyd1gsyXtY9oOrS/OSX+12M8ojAm+NJ6Vl3tUjp7ZMcPO7e7pJoqpWoMdzO0rF8IQ==
|
||||
dependencies:
|
||||
"@petamoriken/float16" "^3.4.7"
|
||||
lerc "^3.0.0"
|
||||
pako "^2.0.4"
|
||||
parse-headers "^2.0.2"
|
||||
quick-lru "^6.1.1"
|
||||
web-worker "^1.5.0"
|
||||
xml-utils "^1.10.2"
|
||||
zstddec "^0.2.0"
|
||||
|
||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01"
|
||||
@ -2714,6 +2772,13 @@ node-releases@^2.0.27:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e"
|
||||
integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==
|
||||
|
||||
numcodecs@^0.3.2:
|
||||
version "0.3.2"
|
||||
resolved "https://registry.yarnpkg.com/numcodecs/-/numcodecs-0.3.2.tgz#09887cfc2a3ae1c59a495c01a7f0528118d85dcd"
|
||||
integrity sha512-6YSPnmZgg0P87jnNhi3s+FVLOcIn3y+1CTIgUulA3IdASzK9fJM87sUFkpyA+be9GibGRaST2wCgkD+6U+fWKw==
|
||||
dependencies:
|
||||
fflate "^0.8.0"
|
||||
|
||||
object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@ -2776,6 +2841,18 @@ ol-ext@^4.0.10:
|
||||
resolved "https://registry.yarnpkg.com/ol-ext/-/ol-ext-4.0.37.tgz#8da5c4097322e56f99b45537ca353c242d1c9b88"
|
||||
integrity sha512-RxzdgMWnNBDP9VZCza3oS3rl1+OCl+1SJLMjt7ATyDDLZl/zzrsQELfJ25WAL6HIWgjkQ2vYDh3nnHFupxOH4w==
|
||||
|
||||
ol@>=10:
|
||||
version "10.8.0"
|
||||
resolved "https://registry.yarnpkg.com/ol/-/ol-10.8.0.tgz#fe528cd93f13e673e309435f577076e644653aa3"
|
||||
integrity sha512-kLk7jIlJvKyhVMAjORTXKjzlM6YIByZ1H/d0DBx3oq8nSPCG6/gbLr5RxukzPgwbhnAqh+xHNCmrvmFKhVMvoQ==
|
||||
dependencies:
|
||||
"@types/rbush" "4.0.0"
|
||||
earcut "^3.0.0"
|
||||
geotiff "^3.0.2"
|
||||
pbf "4.0.1"
|
||||
rbush "^4.0.0"
|
||||
zarrita "^0.6.0"
|
||||
|
||||
ol@^9.2.4:
|
||||
version "9.2.4"
|
||||
resolved "https://registry.yarnpkg.com/ol/-/ol-9.2.4.tgz#07dcefdceb66ddbde13089bca136f4d4852b772b"
|
||||
@ -2880,6 +2957,13 @@ pbf@3.2.1:
|
||||
ieee754 "^1.1.12"
|
||||
resolve-protobuf-schema "^2.1.0"
|
||||
|
||||
pbf@4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pbf/-/pbf-4.0.1.tgz#ad9015e022b235dcdbe05fc468a9acadf483f0d4"
|
||||
integrity sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==
|
||||
dependencies:
|
||||
resolve-protobuf-schema "^2.1.0"
|
||||
|
||||
pbf@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.3.0.tgz#1790f3d99118333cc7f498de816028a346ef367f"
|
||||
@ -2966,6 +3050,11 @@ quickselect@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
|
||||
integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
|
||||
|
||||
quickselect@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-3.0.0.tgz#a37fc953867d56f095a20ac71c6d27063d2de603"
|
||||
integrity sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==
|
||||
|
||||
rbush@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf"
|
||||
@ -2973,6 +3062,13 @@ rbush@^3.0.1:
|
||||
dependencies:
|
||||
quickselect "^2.0.0"
|
||||
|
||||
rbush@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/rbush/-/rbush-4.0.1.tgz#1f55afa64a978f71bf9e9a99bc14ff84f3cb0d6d"
|
||||
integrity sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==
|
||||
dependencies:
|
||||
quickselect "^3.0.0"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||
@ -3031,6 +3127,11 @@ readdirp@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
|
||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||
|
||||
reference-spec-reader@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz#52bd79614dde68e68f05c97a05ae04ff20acd7ec"
|
||||
integrity sha512-q0mfCi5yZSSHXpCyxjgQeaORq3tvDsxDyzaadA/5+AbAUwRyRuuTh0aRQuE/vAOt/qzzxidJ5iDeu1cLHaNBlQ==
|
||||
|
||||
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
|
||||
version "1.0.10"
|
||||
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
|
||||
@ -3285,6 +3386,11 @@ side-channel@^1.1.0:
|
||||
side-channel-map "^1.0.1"
|
||||
side-channel-weakmap "^1.0.2"
|
||||
|
||||
slice-source@0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/slice-source/-/slice-source-0.4.1.tgz#40a57ac03c6668b5da200e05378e000bf2a61d79"
|
||||
integrity sha512-YiuPbxpCj4hD9Qs06hGAz/OZhQ0eDuALN0lRWJez0eD/RevzKqGdUx1IOMUnXgpr+sXZLq3g8ERwbAH0bCb8vg==
|
||||
|
||||
snappyjs@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/snappyjs/-/snappyjs-0.6.1.tgz#9bca9ff8c54b133a9cc84a71d22779e97fc51878"
|
||||
@ -3506,6 +3612,13 @@ undici-types@~7.16.0:
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46"
|
||||
integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==
|
||||
|
||||
unzipit@1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/unzipit/-/unzipit-1.4.3.tgz#738298a6b235892bf7ce7db82cff813d4ca664ac"
|
||||
integrity sha512-gsq2PdJIWWGhx5kcdWStvNWit9FVdTewm4SEG7gFskWs+XCVaULt9+BwuoBtJiRE8eo3L1IPAOrbByNLtLtIlg==
|
||||
dependencies:
|
||||
uzip-module "^1.0.2"
|
||||
|
||||
update-browserslist-db@^1.2.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d"
|
||||
@ -3546,6 +3659,11 @@ utrie@^1.0.2:
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
|
||||
uzip-module@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/uzip-module/-/uzip-module-1.0.3.tgz#6bbabe2a3efea5d5a4a47479f523a571de3427ce"
|
||||
integrity sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==
|
||||
|
||||
vite@^5.2.10:
|
||||
version "5.4.21"
|
||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027"
|
||||
@ -3557,7 +3675,7 @@ vite@^5.2.10:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.3"
|
||||
|
||||
web-worker@^1.2.0:
|
||||
web-worker@^1.2.0, web-worker@^1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.5.0.tgz#71b2b0fbcc4293e8f0aa4f6b8a3ffebff733dcc5"
|
||||
integrity sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==
|
||||
@ -3651,7 +3769,7 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
|
||||
xml-utils@^1.0.2:
|
||||
xml-utils@^1.0.2, xml-utils@^1.10.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/xml-utils/-/xml-utils-1.10.2.tgz#436b39ccc25a663ce367ea21abb717afdea5d6b1"
|
||||
integrity sha512-RqM+2o1RYs6T8+3DzDSoTRAUfrvaejbVHcp3+thnAtDKo8LskR+HomLajEy5UjTz24rpka7AxVBRR3g2wTUkJA==
|
||||
@ -3666,6 +3784,14 @@ yocto-queue@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zarrita@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/zarrita/-/zarrita-0.6.1.tgz#4e0d13e14c0ebcf70e08ed3bafd1d9a290651445"
|
||||
integrity sha512-YOMTW8FT55Rz+vadTIZeOFZ/F2h4svKizyldvPtMYSxPgSNcRkOzkxCsWpIWlWzB1I/LmISmi0bEekOhLlI+Zw==
|
||||
dependencies:
|
||||
"@zarrita/storage" "^0.1.4"
|
||||
numcodecs "^0.3.2"
|
||||
|
||||
zstd-codec@^0.1:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/zstd-codec/-/zstd-codec-0.1.5.tgz#c180193e4603ef74ddf704bcc835397d30a60e42"
|
||||
@ -3676,6 +3802,11 @@ zstddec@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.1.0.tgz#7050f3f0e0c3978562d0c566b3e5a427d2bad7ec"
|
||||
integrity sha512-w2NTI8+3l3eeltKAdK8QpiLo/flRAr2p8AGeakfMZOXBxOg9HIu4LVDxBi81sYgVhFhdJjv1OrB5ssI8uFPoLg==
|
||||
|
||||
zstddec@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.2.0.tgz#91c8cde8f351ef5fe0bdfca66bb14a5fa0d16d71"
|
||||
integrity sha512-oyPnDa1X5c13+Y7mA/FDMNJrn4S8UNBe0KCqtDmor40Re7ALrPN6npFwyYVRRh+PqozZQdeg23QtbcamZnG5rA==
|
||||
|
||||
zustand@^4.5.2:
|
||||
version "4.5.7"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.7.tgz#7d6bb2026a142415dd8be8891d7870e6dbe65f55"
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user