- SPipe_server_2026-10-30.zip 내용을 풀어서 등록 (압축 파일은 올리지 않음) - README에 데모 v2 서버 업데이트 순서 추가, 저장소에 없는 문서 목차 정리 - 패치내역을 docs/로 이동, _worklog.md 추가 - 임시 파일(backend/temp.*)과 API 키가 든 파일은 제외
133 lines
5.8 KiB
TypeScript
133 lines
5.8 KiB
TypeScript
import React from 'react';
|
|
import { BasemapType, DisplayMode, type SimulationResult } from './types';
|
|
import { AddressSearch } from './AddressSearch';
|
|
import { CRS } from './crs';
|
|
import { type MapFocusRequest } from './Map';
|
|
|
|
export interface MapHeaderProps {
|
|
crs: CRS;
|
|
basemap: BasemapType;
|
|
displayMode: DisplayMode;
|
|
setCrs: React.Dispatch<React.SetStateAction<CRS>>;
|
|
crsSelectRef: React.RefObject<HTMLSelectElement>;
|
|
setBasemap: React.Dispatch<React.SetStateAction<BasemapType>>;
|
|
setDisplayMode: React.Dispatch<React.SetStateAction<DisplayMode>>;
|
|
requestMapFocus: (request: Omit<MapFocusRequest, 'id'>) => void;
|
|
showToast: (msg: string) => void;
|
|
wasmResult: SimulationResult | null;
|
|
}
|
|
|
|
/** Map pane toolbar: CRS, basemap and result display selectors. */
|
|
export const MapHeader: React.FC<MapHeaderProps> = ({
|
|
crs,
|
|
basemap,
|
|
displayMode,
|
|
setCrs,
|
|
crsSelectRef,
|
|
setBasemap,
|
|
setDisplayMode,
|
|
requestMapFocus,
|
|
showToast,
|
|
wasmResult,
|
|
}) => (
|
|
<div className="map-header">
|
|
{/* Right toolbar: Basemap, CRS, Data */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
{/* CRS Selector */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<select
|
|
ref={crsSelectRef}
|
|
value={crs}
|
|
onChange={(e) => setCrs(e.target.value as CRS)}
|
|
style={{ fontSize: '12px', padding: '3px 6px', border: '1px solid #cbd5e1', borderRadius: '4px', maxWidth: '175px' }}
|
|
aria-label="좌표계 선택"
|
|
>
|
|
<optgroup label="기본 및 글로벌">
|
|
<option value="LOCAL">로컬 평면 (Local)</option>
|
|
<option value="EPSG:4326">WGS84 경위도 (4326)</option>
|
|
<option value="EPSG:3857">Web Mercator (3857)</option>
|
|
</optgroup>
|
|
<optgroup label="국가표준 GRS80 (현행 법정)">
|
|
<option value="EPSG:5179">한국 UTM-K (5179)</option>
|
|
<option value="EPSG:5186">중부원점 GRS80 (5186)</option>
|
|
<option value="EPSG:5185">서부원점 GRS80 (5185)</option>
|
|
<option value="EPSG:5187">동부원점 GRS80 (5187)</option>
|
|
<option value="EPSG:5188">동해원점 GRS80 (5188)</option>
|
|
<option value="EPSG:5180">제주원점 GRS80 (5180)</option>
|
|
</optgroup>
|
|
<optgroup label="포털 지도">
|
|
<option value="EPSG:5181">카카오맵 중부 (5181)</option>
|
|
</optgroup>
|
|
<optgroup label="구 지적 / Bessel (상수도 구 GIS)">
|
|
<option value="EPSG:5174">구 지적 중부 (5174)</option>
|
|
<option value="EPSG:5176">구 기준점 중부 (5176)</option>
|
|
<option value="EPSG:5173">구 지적 서부 (5173)</option>
|
|
<option value="EPSG:5177">구 지적 동부 (5177)</option>
|
|
<option value="EPSG:5175">구 지적 제주 (5175)</option>
|
|
</optgroup>
|
|
<optgroup label="KGD2002 과도기 (Y+500,000)">
|
|
<option value="EPSG:102080">KGD2002 중부 (102080)</option>
|
|
<option value="EPSG:102081">KGD2002 서부 (102081)</option>
|
|
<option value="EPSG:102082">KGD2002 동부 (102082)</option>
|
|
<option value="EPSG:102083">KGD2002 동해 (102083)</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Basemap Selector */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>지도:</span>
|
|
<select
|
|
value={basemap}
|
|
onChange={(e) => setBasemap(e.target.value as BasemapType)}
|
|
style={{ fontSize: '12px', padding: '3px 6px', border: '1px solid #cbd5e1', borderRadius: '4px' }}
|
|
>
|
|
<option value="blank">단색 배경</option>
|
|
<option value="osm">OpenStreetMap</option>
|
|
<optgroup label="브이월드 (VWorld)">
|
|
<option value="vworld">일반 지도</option>
|
|
<option value="vworld-white">백지도</option>
|
|
<option value="vworld-midnight">야간 지도</option>
|
|
<option value="vworld-satellite">위성 영상</option>
|
|
<option value="vworld-satellite-hybrid">위성 + 도로·지명</option>
|
|
</optgroup>
|
|
</select>
|
|
</div>
|
|
|
|
{/* Data display mode */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
|
<span style={{ fontSize: '12px', color: 'var(--text-secondary)' }}>표시:</span>
|
|
<select
|
|
value={displayMode}
|
|
onChange={(e) =>
|
|
setDisplayMode(e.target.value as DisplayMode)
|
|
}
|
|
style={{ fontSize: '12px', padding: '3px 6px', border: '1px solid #cbd5e1', borderRadius: '4px' }}
|
|
>
|
|
<option value="pressure">압력 (Pressure)</option>
|
|
<option value="head">수두 (Head)</option>
|
|
<option value="flow">유량 (Flow)</option>
|
|
<option value="velocity">유속 (Velocity)</option>
|
|
<option value="headloss">수두손실 (Headloss)</option>
|
|
<option value="elevation">지형 표고 (Elevation)</option>
|
|
<option value="diff-pressure">수압 변화량 (Diff ΔP)</option>
|
|
<option value="diff-flow">유량 변화량 (Diff ΔQ)</option>
|
|
<option value="quality">
|
|
수질 (Quality - {wasmResult?.qualityInfo?.chemName || '농도/수령'})
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<AddressSearch
|
|
onSelect={(result) => {
|
|
requestMapFocus({
|
|
lngLat: [result.longitude, result.latitude],
|
|
zoom: 17,
|
|
});
|
|
setBasemap('vworld');
|
|
showToast(`주소로 이동: ${result.roadAddressName || result.parcelAddressName || result.addressName}`);
|
|
}}
|
|
/>
|
|
</div>
|
|
);
|