diff --git a/.env.development b/.env.development index f480fa97..4f1cc1eb 100644 --- a/.env.development +++ b/.env.development @@ -6,8 +6,9 @@ # 배포 경로 VITE_BASE_URL=/ -# API 서버 (SNP-Batch API) -VITE_API_URL=http://211.208.115.83:8041/snp-api +# API 서버 — 로컬 개발은 Vite 프록시 사용 (/snp-api → 211.208.115.83:8041) +# 빈 값으로 설정하여 .env의 절대 URL을 override → aisTargetApi 기본값 /snp-api 사용 +VITE_API_URL= # 선박 데이터 쓰로틀링 (ms, 0=무제한) VITE_SHIP_THROTTLE=0 diff --git a/.node-version b/.node-version index a45fd52c..209e3ef4 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -24 +20 diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 42a1c98a..00000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v22.22.0 diff --git a/src/api/aisTargetApi.js b/src/api/aisTargetApi.js index 3c92d68f..043dc75a 100644 --- a/src/api/aisTargetApi.js +++ b/src/api/aisTargetApi.js @@ -4,7 +4,9 @@ */ import axios from 'axios'; -const BASE_URL = import.meta.env.VITE_API_URL || ''; +// dev: Vite 프록시 (/snp-api → 211.208.115.83:8041) +// prod: 환경변수로 직접 지정 +const BASE_URL = import.meta.env.VITE_API_URL || '/snp-api'; /** * AIS 타겟 검색 (최근 N분 데이터) diff --git a/src/hooks/useFavoriteData.js b/src/hooks/useFavoriteData.js index 4d243ba1..68dad97c 100644 --- a/src/hooks/useFavoriteData.js +++ b/src/hooks/useFavoriteData.js @@ -1,40 +1,10 @@ -import { useEffect, useRef } from 'react'; -import { fetchFavoriteShips, fetchRealms } from '../api/favoriteApi'; -import useFavoriteStore from '../stores/favoriteStore'; - /** * 관심선박 + 관심구역 데이터 로딩 훅 * MapContainer에서 1회 호출 + * + * 비활성화: 내부망 API(/api/gis) 접근 불가 (민간화) + * TODO: 외부 API 연동 시 복원 */ export default function useFavoriteData() { - const loaded = useRef(false); - - useEffect(() => { - if (loaded.current) return; - loaded.current = true; - - const load = async () => { - const [ships, realms] = await Promise.allSettled([ - fetchFavoriteShips(), - fetchRealms(), - ]); - - const shipList = ships.status === 'fulfilled' ? ships.value : []; - const realmList = realms.status === 'fulfilled' ? realms.value : []; - - if (ships.status === 'rejected') { - console.warn('[useFavoriteData] 관심선박 로드 실패:', ships.reason); - } - if (realms.status === 'rejected') { - console.warn('[useFavoriteData] 관심구역 로드 실패:', realms.reason); - } - - useFavoriteStore.getState().setFavoriteList(shipList); - useFavoriteStore.getState().setRealmList(realmList); - - console.log(`[useFavoriteData] 관심선박 ${shipList.length}건, 관심구역 ${realmList.length}건 로드`); - }; - - load(); - }, []); + // noop — 내부망 /api/gis 의존 제거 } diff --git a/src/map/layers/baseLayer.js b/src/map/layers/baseLayer.js index 5d90c50c..6f95dc8c 100644 --- a/src/map/layers/baseLayer.js +++ b/src/map/layers/baseLayer.js @@ -1,106 +1,22 @@ /** * 베이스맵 레이어 설정 - * - 메인 프로젝트(mda-react-front)의 mapLayer.ts 참조 + * - 민간화: 내부망 타일서버 → OSM 타일로 임시 전환 + * - Phase 3에서 MapLibre GL JS 벡터맵으로 최종 전환 예정 */ -import { XYZ } from 'ol/source'; -import { transformExtent } from 'ol/proj'; -import WebGLTileLayer from 'ol/layer/WebGLTile'; +import { XYZ, OSM } from 'ol/source'; +import TileLayer from 'ol/layer/Tile'; -// 좌표계 상수 -const EPSG_3857 = 'EPSG:3857'; -const EPSG_4326 = 'EPSG:4326'; - -// 1x1 투명 PNG (타일 로드 실패 시 대체용) -const TRANSPARENT_PIXEL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; +const DARK_TILE_URL = 'https://{a-d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png'; /** - * 타일 로드 함수 (fetch 사용으로 콘솔 에러 완전 방지) - * - 404/네트워크 에러 시 투명 이미지로 대체 - * - 브라우저 네트워크 탭에는 표시되지만 콘솔 에러는 없음 - */ -function silentTileLoadFunction(imageTile, src) { - const img = imageTile.getImage(); - - fetch(src) - .then(response => { - if (!response.ok) { - throw new Error('Tile not found'); - } - return response.blob(); - }) - .then(blob => { - img.src = URL.createObjectURL(blob); - }) - .catch(() => { - img.src = TRANSPARENT_PIXEL; - }); -} - -/** - * 레이어 설정 + * 레이어 설정 (하위 모듈 호환용 export) */ export const mapLayerConfig = { - // 일반지도 (줌 0-11) - worldLayer: { - source: new XYZ({ - url: '/MAPS/WORLD_webp/{z}/{x}/{y}.webp', - minZoom: 0, - maxZoom: 11, - attributions: 'ⓒ OpenStreetMap', - tileLoadFunction: silentTileLoadFunction, - }), - preload: Infinity, - }, - - // 전자해도 (줌 0-11) - URL은 임시로 worldLayer와 동일 - encLayer: { - source: new XYZ({ - url: '/MAPS/WORLD_webp/{z}/{x}/{y}.webp', - minZoom: 0, - maxZoom: 11, - attributions: 'ⓒ OpenStreetMap', - tileLoadFunction: silentTileLoadFunction, - }), - preload: Infinity, - }, - - // 야간지도 (줌 5-15, 타일은 6-11레벨까지만 로드 → 12+ 확대 표시) darkLayer: { source: new XYZ({ - url: '/MAPS/SIMPLE_B_webp/{z}/{x}/{y}.webp', - minZoom: 6, - maxZoom: 11, // 타일은 11레벨까지만 로드 (12+ 는 11레벨 타일 확대) - tileLoadFunction: silentTileLoadFunction, + url: DARK_TILE_URL, + maxZoom: 19, }), - preload: Infinity, - }, - - // 동아시아 상세 (줌 12-15, 타일은 12레벨까지만 로드 → 13+ 확대 표시) - eastAsiaLayer: { - source: new XYZ({ - url: '/MAPS/EAST_ASIA_webp/{z}/{x}/{y}.webp', - minZoom: 12, - maxZoom: 12, // 타일은 12레벨까지만 로드 (13+ 는 12레벨 타일 확대) - tileLoadFunction: silentTileLoadFunction, - }), - preload: 0, - minZoom: 12, - zIndex: 1, - extent: transformExtent([110, 20, 140, 45], EPSG_4326, EPSG_3857), - }, - - // 한국 상세 (줌 16-17) - korLayer: { - source: new XYZ({ - url: '/MAPS/KOR_webp/{z}/{x}/{y}.webp', - minZoom: 16, - maxZoom: 17, - tileLoadFunction: silentTileLoadFunction, - }), - preload: Infinity, - minZoom: 16, - zIndex: 1, - extent: transformExtent([124, 32, 133, 39], EPSG_4326, EPSG_3857), }, }; @@ -109,30 +25,31 @@ export const mapLayerConfig = { * @param {string} baseMapType - 배경지도 타입 ('normal' | 'enc' | 'dark') */ export const createBaseLayers = (baseMapType = 'dark') => { - // 3가지 배경지도 레이어 생성 - const worldMap = new WebGLTileLayer({ - ...mapLayerConfig.worldLayer, + // OSM 기반 일반지도 + const worldMap = new TileLayer({ + source: new OSM(), visible: baseMapType === 'normal', }); - const encMap = new WebGLTileLayer({ - ...mapLayerConfig.encLayer, + + // ENC (일반지도와 동일 — 임시) + const encMap = new TileLayer({ + source: new OSM(), visible: baseMapType === 'enc', }); - const darkMap = new WebGLTileLayer({ - ...mapLayerConfig.darkLayer, + + // 야간 모드 (CartoDB Dark Matter) + const darkMap = new TileLayer({ + source: new XYZ({ + url: DARK_TILE_URL, + attributions: '© OpenStreetMap contributors, © CARTO', + maxZoom: 19, + }), visible: baseMapType === 'dark', }); - // 상세 지도 레이어 (일반지도/전자해도 전용, 야간지도에서는 숨김) - const isDarkMode = baseMapType === 'dark'; - const eastAsiaMap = new WebGLTileLayer({ - ...mapLayerConfig.eastAsiaLayer, - visible: !isDarkMode, - }); - const korMap = new WebGLTileLayer({ - ...mapLayerConfig.korLayer, - visible: !isDarkMode, - }); + // 상세 레이어는 OSM이 자체 커버하므로 빈 레이어로 대체 + const eastAsiaMap = new TileLayer({ visible: false }); + const korMap = new TileLayer({ visible: false }); return { worldMap, diff --git a/vite.config.js b/vite.config.js index ed419745..a8445fef 100644 --- a/vite.config.js +++ b/vite.config.js @@ -42,55 +42,9 @@ export default ({ mode, command }) => { host: true, port: 3000, proxy: { - // 지도 타일 서버 - '/MAPS': { - target: env.VITE_MAP_TILE_URL || 'http://10.26.252.39:9090', - changeOrigin: true, - secure: false, - }, - // GeoJSON 데이터 - '/geo': { - target: env.VITE_MAP_TILE_URL || 'http://10.26.252.39:9090', - changeOrigin: true, - secure: false, - }, - // 선박 신호 API (signal-api) - // 참조: mda-react-front/vite.config.ts - '/signal-api': { - target: env.VITE_SIGNAL_API || 'http://10.26.252.39:9090/signal-api', - changeOrigin: true, - secure: false, - rewrite: (path) => path.replace(/^\/signal-api/, ''), - }, - // 선박 이미지 (국기, 선종 아이콘) - // 참조: mda-react-front/vite.config.ts - /ship/image 프록시 - '/ship/image': { - target: env.VITE_API_URL || 'http://10.26.252.39:9090', - changeOrigin: true, - secure: false, - }, - // 항적 조회 API (별도 서버) - // 참조: mda-react-front/vite.config.ts - /api/v2/tracks 프록시 - '/api/v2/tracks': { - target: env.VITE_TRACK_API || 'http://10.26.252.51:8090', - changeOrigin: true, - secure: false, - }, - // 공통 API (개인설정, 공통코드 등) — 메인 API 서버로 라우팅 - '/api/cmn': { - target: env.VITE_API_URL || 'http://10.26.252.39:9090', - changeOrigin: true, - secure: false, - }, - // 기상/위성 등 GIS API — 메인 API 서버로 라우팅 - '/api/gis': { - target: env.VITE_API_URL || 'http://10.26.252.39:9090', - changeOrigin: true, - secure: false, - }, - // API 서버 (기타) - '/api': { - target: env.VITE_TRACK_API || 'http://localhost:8090', + // SNP-Batch AIS API (선박 위치 데이터) + '/snp-api': { + target: env.VITE_SNP_API_TARGET || 'http://211.208.115.83:8041', changeOrigin: true, secure: false, },