fix: 외부 환경 호환 (OSM 타일, AIS 프록시, Node 20)
- 내부망 타일서버 → OSM + CartoDB Dark Matter 전환 - 내부망 API 프록시 제거, /snp-api 프록시만 유지 - AIS API CORS 해결 (Vite 프록시 경유) - useFavoriteData 비활성화 (/api/gis 내부망 의존 제거) - .node-version: 20 (팀 템플릿 기준), .nvmrc 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
b9c0b70e06
커밋
3439407b71
@ -6,8 +6,9 @@
|
|||||||
# 배포 경로
|
# 배포 경로
|
||||||
VITE_BASE_URL=/
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
# API 서버 (SNP-Batch API)
|
# API 서버 — 로컬 개발은 Vite 프록시 사용 (/snp-api → 211.208.115.83:8041)
|
||||||
VITE_API_URL=http://211.208.115.83:8041/snp-api
|
# 빈 값으로 설정하여 .env의 절대 URL을 override → aisTargetApi 기본값 /snp-api 사용
|
||||||
|
VITE_API_URL=
|
||||||
|
|
||||||
# 선박 데이터 쓰로틀링 (ms, 0=무제한)
|
# 선박 데이터 쓰로틀링 (ms, 0=무제한)
|
||||||
VITE_SHIP_THROTTLE=0
|
VITE_SHIP_THROTTLE=0
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
24
|
20
|
||||||
|
|||||||
@ -4,7 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
import axios from 'axios';
|
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분 데이터)
|
* AIS 타겟 검색 (최근 N분 데이터)
|
||||||
|
|||||||
@ -1,40 +1,10 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { fetchFavoriteShips, fetchRealms } from '../api/favoriteApi';
|
|
||||||
import useFavoriteStore from '../stores/favoriteStore';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 관심선박 + 관심구역 데이터 로딩 훅
|
* 관심선박 + 관심구역 데이터 로딩 훅
|
||||||
* MapContainer에서 1회 호출
|
* MapContainer에서 1회 호출
|
||||||
|
*
|
||||||
|
* 비활성화: 내부망 API(/api/gis) 접근 불가 (민간화)
|
||||||
|
* TODO: 외부 API 연동 시 복원
|
||||||
*/
|
*/
|
||||||
export default function useFavoriteData() {
|
export default function useFavoriteData() {
|
||||||
const loaded = useRef(false);
|
// noop — 내부망 /api/gis 의존 제거
|
||||||
|
|
||||||
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();
|
|
||||||
}, []);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,106 +1,22 @@
|
|||||||
/**
|
/**
|
||||||
* 베이스맵 레이어 설정
|
* 베이스맵 레이어 설정
|
||||||
* - 메인 프로젝트(mda-react-front)의 mapLayer.ts 참조
|
* - 민간화: 내부망 타일서버 → OSM 타일로 임시 전환
|
||||||
|
* - Phase 3에서 MapLibre GL JS 벡터맵으로 최종 전환 예정
|
||||||
*/
|
*/
|
||||||
import { XYZ } from 'ol/source';
|
import { XYZ, OSM } from 'ol/source';
|
||||||
import { transformExtent } from 'ol/proj';
|
import TileLayer from 'ol/layer/Tile';
|
||||||
import WebGLTileLayer from 'ol/layer/WebGLTile';
|
|
||||||
|
|
||||||
// 좌표계 상수
|
const DARK_TILE_URL = 'https://{a-d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png';
|
||||||
const EPSG_3857 = 'EPSG:3857';
|
|
||||||
const EPSG_4326 = 'EPSG:4326';
|
|
||||||
|
|
||||||
// 1x1 투명 PNG (타일 로드 실패 시 대체용)
|
|
||||||
const TRANSPARENT_PIXEL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 타일 로드 함수 (fetch 사용으로 콘솔 에러 완전 방지)
|
* 레이어 설정 (하위 모듈 호환용 export)
|
||||||
* - 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 const mapLayerConfig = {
|
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: {
|
darkLayer: {
|
||||||
source: new XYZ({
|
source: new XYZ({
|
||||||
url: '/MAPS/SIMPLE_B_webp/{z}/{x}/{y}.webp',
|
url: DARK_TILE_URL,
|
||||||
minZoom: 6,
|
maxZoom: 19,
|
||||||
maxZoom: 11, // 타일은 11레벨까지만 로드 (12+ 는 11레벨 타일 확대)
|
|
||||||
tileLoadFunction: silentTileLoadFunction,
|
|
||||||
}),
|
}),
|
||||||
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')
|
* @param {string} baseMapType - 배경지도 타입 ('normal' | 'enc' | 'dark')
|
||||||
*/
|
*/
|
||||||
export const createBaseLayers = (baseMapType = 'dark') => {
|
export const createBaseLayers = (baseMapType = 'dark') => {
|
||||||
// 3가지 배경지도 레이어 생성
|
// OSM 기반 일반지도
|
||||||
const worldMap = new WebGLTileLayer({
|
const worldMap = new TileLayer({
|
||||||
...mapLayerConfig.worldLayer,
|
source: new OSM(),
|
||||||
visible: baseMapType === 'normal',
|
visible: baseMapType === 'normal',
|
||||||
});
|
});
|
||||||
const encMap = new WebGLTileLayer({
|
|
||||||
...mapLayerConfig.encLayer,
|
// ENC (일반지도와 동일 — 임시)
|
||||||
|
const encMap = new TileLayer({
|
||||||
|
source: new OSM(),
|
||||||
visible: baseMapType === 'enc',
|
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',
|
visible: baseMapType === 'dark',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 상세 지도 레이어 (일반지도/전자해도 전용, 야간지도에서는 숨김)
|
// 상세 레이어는 OSM이 자체 커버하므로 빈 레이어로 대체
|
||||||
const isDarkMode = baseMapType === 'dark';
|
const eastAsiaMap = new TileLayer({ visible: false });
|
||||||
const eastAsiaMap = new WebGLTileLayer({
|
const korMap = new TileLayer({ visible: false });
|
||||||
...mapLayerConfig.eastAsiaLayer,
|
|
||||||
visible: !isDarkMode,
|
|
||||||
});
|
|
||||||
const korMap = new WebGLTileLayer({
|
|
||||||
...mapLayerConfig.korLayer,
|
|
||||||
visible: !isDarkMode,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
worldMap,
|
worldMap,
|
||||||
|
|||||||
@ -42,55 +42,9 @@ export default ({ mode, command }) => {
|
|||||||
host: true,
|
host: true,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
proxy: {
|
proxy: {
|
||||||
// 지도 타일 서버
|
// SNP-Batch AIS API (선박 위치 데이터)
|
||||||
'/MAPS': {
|
'/snp-api': {
|
||||||
target: env.VITE_MAP_TILE_URL || 'http://10.26.252.39:9090',
|
target: env.VITE_SNP_API_TARGET || 'http://211.208.115.83:8041',
|
||||||
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',
|
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
},
|
},
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user