754 lines
31 KiB
JavaScript
754 lines
31 KiB
JavaScript
import { PMTiles } from 'https://esm.sh/pmtiles@3.0.7';
|
|
|
|
const MAX_LEVEL = 20;
|
|
const CLICK_HIGHLIGHT_OUTER_WIDTH = 10; // 바깥쪽 흰색 테두리 두께
|
|
const CLICK_HIGHLIGHT_INNER_WIDTH = 5; // 안쪽 선 두께
|
|
const HOVER_HIGHLIGHT_OUTER_WIDTH = 6; // 바깥쪽 흰색 테두리 두께
|
|
const HOVER_HIGHLIGHT_INNER_WIDTH = 2; // 안쪽 선 두께
|
|
|
|
// 선택/호버 상태 추적
|
|
let selectedFeature = null;
|
|
let hoveredFeature = null;
|
|
let selectLayer = null;
|
|
let hoverLayer = null;
|
|
// 현재 선택된 feature 저장
|
|
let currentSelectedFeature = null;
|
|
// 선택된 feature의 레이어 키 추가
|
|
let currentSelectedLayerKey = null;
|
|
|
|
// ---- OpenLayers VectorTile Layer ----
|
|
class OLVectorLayer {
|
|
constructor(olMap, layerId) {
|
|
this.olMap = olMap;
|
|
this.layerId = layerId;
|
|
this.type = null;
|
|
this.templateUrl = null;
|
|
this.pmtilesInstance = null;
|
|
this.maxLevel = MAX_LEVEL;
|
|
this.vectorLayer = null;
|
|
}
|
|
|
|
async setTileSource(source, type, zoomRange = null) {
|
|
this.type = type;
|
|
|
|
if (type === 'pbf') {
|
|
this.templateUrl = source;
|
|
|
|
try {
|
|
const baseUrl = source.replace('/{z}/{x}/{y}.pbf', '');
|
|
const metadataUrl = `${baseUrl}/metadata.json`;
|
|
const response = await fetch(metadataUrl);
|
|
const metadata = await response.json();
|
|
|
|
if (metadata.maxzoom) {
|
|
this.maxLevel = parseInt(metadata.maxzoom);
|
|
}
|
|
} catch (error) {
|
|
console.warn(`[${this.layerId}] Could not read metadata.json`);
|
|
}
|
|
|
|
this.vectorLayer = new ol.layer.VectorTile({
|
|
source: new ol.source.VectorTile({
|
|
format: new ol.format.MVT(),
|
|
url: source,
|
|
maxZoom: this.maxLevel
|
|
}),
|
|
style: styleFunction,
|
|
renderMode: 'vector',
|
|
zIndex: 1,
|
|
// 줌 범위 설정
|
|
minZoom: zoomRange ? zoomRange.min : undefined,
|
|
maxZoom: zoomRange ? zoomRange.max : undefined
|
|
});
|
|
|
|
this.olMap.addLayer(this.vectorLayer);
|
|
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
|
console.log(`[${this.layerId}] PBF Vector layer initialized${zoomInfo}`);
|
|
|
|
} else if (type === 'pmtiles') {
|
|
this.pmtilesInstance = source;
|
|
|
|
try {
|
|
const metadata = await this.pmtilesInstance.getMetadata();
|
|
if (metadata.maxzoom) {
|
|
this.maxLevel = parseInt(metadata.maxzoom);
|
|
}
|
|
// console.log(`[${this.layerId}] PMTiles metadata:`, metadata);
|
|
} catch (error) {
|
|
console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
|
|
}
|
|
|
|
this.vectorLayer = new ol.layer.VectorTile({
|
|
source: new ol.source.VectorTile({
|
|
format: new ol.format.MVT(),
|
|
tileUrlFunction: (tileCoord) => {
|
|
const z = tileCoord[0];
|
|
const x = tileCoord[1];
|
|
const y = tileCoord[2];
|
|
return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
|
|
},
|
|
tileLoadFunction: async (tile, url) => {
|
|
const urlParts = url.split('/');
|
|
const z = parseInt(urlParts[urlParts.length - 3]);
|
|
const x = parseInt(urlParts[urlParts.length - 2]);
|
|
const y = parseInt(urlParts[urlParts.length - 1]);
|
|
|
|
// console.log(`Loading PMTiles tile: ${z}/${x}/${y}`);
|
|
|
|
try {
|
|
const tileData = await this.pmtilesInstance.getZxy(z, x, y);
|
|
if (tileData && tileData.data) {
|
|
// console.log(`PMTiles tile ${z}/${x}/${y} loaded, size: ${tileData.data.byteLength} bytes`);
|
|
|
|
const format = new ol.format.MVT();
|
|
|
|
const tileGrid = ol.tilegrid.createXYZ({maxZoom: this.maxLevel});
|
|
const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
|
|
|
|
const features = format.readFeatures(tileData.data, {
|
|
extent: tileExtent,
|
|
featureProjection: 'EPSG:3857'
|
|
});
|
|
|
|
// console.log(`PMTiles tile ${z}/${x}/${y} features: ${features.length}`);
|
|
|
|
if (tile.setFeatures) {
|
|
tile.setFeatures(features);
|
|
} else {
|
|
tile.setLoader(() => Promise.resolve(features));
|
|
}
|
|
} else {
|
|
// console.log(`PMTiles tile ${z}/${x}/${y} - no data`);
|
|
if (tile.setFeatures) {
|
|
tile.setFeatures([]);
|
|
} else {
|
|
tile.setLoader(() => Promise.resolve([]));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// console.error(`Failed to load PMTiles tile ${z}/${x}/${y}:`, error);
|
|
if (tile.setFeatures) {
|
|
tile.setFeatures([]);
|
|
} else {
|
|
tile.setLoader(() => Promise.resolve([]));
|
|
}
|
|
}
|
|
},
|
|
maxZoom: this.maxLevel
|
|
}),
|
|
style: styleFunction,
|
|
zIndex: 1,
|
|
updateWhileAnimating: false,
|
|
updateWhileInteracting: false,
|
|
renderBuffer: 50,
|
|
declutter: false,
|
|
// 줌 범위 설정
|
|
minZoom: zoomRange ? zoomRange.min : undefined,
|
|
maxZoom: zoomRange ? zoomRange.max : undefined
|
|
});
|
|
|
|
this.olMap.addLayer(this.vectorLayer);
|
|
|
|
|
|
|
|
// this.pmtilesInstance = source;
|
|
|
|
// try {
|
|
// const metadata = await this.pmtilesInstance.getMetadata();
|
|
// if (metadata.maxzoom) this.maxLevel = parseInt(metadata.maxzoom, 10);
|
|
// } catch (e) {
|
|
// console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
|
|
// }
|
|
|
|
// // PMTiles가 512 기준이면 512 권장
|
|
// const tileSize = 256;
|
|
// const tileGrid = ol.tilegrid.createXYZ({
|
|
// maxZoom: this.maxLevel,
|
|
// tileSize
|
|
// });
|
|
|
|
// // 기존 styleFunction 그대로 사용
|
|
// const styleFn = styleFunction;
|
|
|
|
// // 벡터 → 래스터 변환용 레이어
|
|
// this.rasterLayer = new ol.layer.Tile({
|
|
// source: new ol.source.TileImage({
|
|
// tileGrid,
|
|
// tileUrlFunction: (tileCoord) => {
|
|
// const [z, x, y] = tileCoord;
|
|
// return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
|
|
// },
|
|
// tileLoadFunction: async (imageTile, url) => {
|
|
// const parts = url.split('/');
|
|
// const z = parseInt(parts[parts.length - 3], 10);
|
|
// const x = parseInt(parts[parts.length - 2], 10);
|
|
// const y = parseInt(parts[parts.length - 1], 10);
|
|
|
|
// // 타일 extent (EPSG:3857)
|
|
// const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
|
|
// const resolution = tileGrid.getResolution(z);
|
|
|
|
// // 해상도/부하 트레이드오프
|
|
// const pixelRatio = 1; // 필요시 window.devicePixelRatio
|
|
|
|
// const canvas = document.createElement('canvas');
|
|
// canvas.width = tileSize * pixelRatio;
|
|
// canvas.height = tileSize * pixelRatio;
|
|
// const ctx = canvas.getContext('2d');
|
|
|
|
// // ★ extent 반영하여 벡터 컨텍스트 생성
|
|
// const vectorCtx = ol.render.toContext(ctx, {
|
|
// size: [tileSize * pixelRatio, tileSize * pixelRatio],
|
|
// pixelRatio,
|
|
// extent: tileExtent // ← 중요: 지도좌표 → 픽셀 변환 기준
|
|
// });
|
|
|
|
// try {
|
|
// const tileData = await this.pmtilesInstance.getZxy(z, x, y);
|
|
|
|
// if (tileData && tileData.data) {
|
|
// // MVT 파싱 (extent는 featureProjection과 동일 좌표계여야 함)
|
|
// const format = new ol.format.MVT();
|
|
// const features = format.readFeatures(tileData.data, {
|
|
// extent: tileExtent, // EPSG:3857
|
|
// featureProjection: 'EPSG:3857'
|
|
// });
|
|
|
|
// // zIndex 반영을 위해 (feature, style) 쌍 정렬
|
|
// const drawQueue = [];
|
|
// for (const f of features) {
|
|
// let s = styleFn ? styleFn(f, resolution) : null;
|
|
// if (!s) continue;
|
|
// const styles = Array.isArray(s) ? s : [s];
|
|
// for (const one of styles) {
|
|
// if (one && typeof one.getZIndex === 'function') {
|
|
// const zi = one.getZIndex() ?? 0;
|
|
// drawQueue.push({ f, style: one, z: zi });
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// drawQueue.sort((a, b) => a.z - b.z);
|
|
// for (const { f, style } of drawQueue) {
|
|
// vectorCtx.drawFeature(f, style);
|
|
// }
|
|
// } else {
|
|
// // 데이터 없음 → 투명 타일
|
|
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
// }
|
|
|
|
// imageTile.getImage().src = canvas.toDataURL('image/png');
|
|
// } catch (err) {
|
|
// console.warn(`PMTiles raster tile load failed ${z}/${x}/${y}`, err);
|
|
// const empty = document.createElement('canvas');
|
|
// empty.width = tileSize;
|
|
// empty.height = tileSize;
|
|
// imageTile.getImage().src = empty.toDataURL();
|
|
// }
|
|
// },
|
|
// // crossOrigin: 'anonymous',
|
|
// }),
|
|
// zIndex: 100000,
|
|
// minZoom: zoomRange ? zoomRange.min : undefined,
|
|
// maxZoom: zoomRange ? zoomRange.max : undefined
|
|
// });
|
|
|
|
// this.olMap.addLayer(this.rasterLayer);
|
|
|
|
|
|
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
|
console.log(`[${this.layerId}] PMTiles Vector layer initialized${zoomInfo}`);
|
|
|
|
try {
|
|
const metadata = await this.pmtilesInstance.getMetadata();
|
|
// PMTiles metadata에서 bounds 정보 확인
|
|
if (metadata && (metadata.bounds || metadata.antimeridian_adjusted_bounds || metadata.center)) {
|
|
let extent;
|
|
let bounds;
|
|
if (metadata.bounds) bounds = metadata.bounds;
|
|
if (metadata.antimeridian_adjusted_bounds) bounds = metadata.antimeridian_adjusted_bounds.split(',');
|
|
|
|
if (bounds) {
|
|
// bounds: [minLon, minLat, maxLon, maxLat]
|
|
const [minLon, minLat, maxLon, maxLat] = bounds;
|
|
const bottomLeft = ol.proj.fromLonLat([minLon, minLat]);
|
|
const topRight = ol.proj.fromLonLat([maxLon, maxLat]);
|
|
extent = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
|
|
} else if (metadata.center) {
|
|
// center와 zoom을 이용한 대략적인 영역 계산
|
|
const [centerLon, centerLat, centerZoom] = metadata.center;
|
|
const centerPoint = ol.proj.fromLonLat([centerLon, centerLat]);
|
|
const resolution = this.olMap.getView().getResolutionForZoom(centerZoom || 10);
|
|
const size = 1000; // 대략적인 크기
|
|
extent = [
|
|
centerPoint[0] - size * resolution,
|
|
centerPoint[1] - size * resolution,
|
|
centerPoint[0] + size * resolution,
|
|
centerPoint[1] + size * resolution
|
|
];
|
|
}
|
|
|
|
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
|
|
if (extent) {
|
|
if (ol.map.getOverlays().getLength() == 0) {
|
|
this.olMap.getView().fit(extent, {
|
|
padding: [50, 50, 50, 50],
|
|
// duration: 500,
|
|
// constrainResolution: true,
|
|
maxZoom: 18
|
|
});
|
|
// console.log(`[${this.layerId}] PMTiles layer fitted to bounds`);
|
|
}
|
|
|
|
pmtilesFitBtn.style.display = 'flex';
|
|
pmtilesFitBtn.addEventListener('click', async() => {
|
|
this.olMap.getView().fit(extent, {
|
|
padding: [50, 50, 50, 50],
|
|
// duration: 500,
|
|
// constrainResolution: true,
|
|
maxZoom: 18
|
|
});
|
|
})
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(`[${this.layerId}] Could not fit PMTiles bounds:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// GeoJSON/KML용 벡터 소스 설정 메서드
|
|
async setVectorSource(url, type, zoomRange = null) {
|
|
this.type = type;
|
|
|
|
if (type === 'geojson') {
|
|
console.log(`[${this.layerId}] Loading GeoJSON from: ${url}`);
|
|
|
|
this.vectorLayer = new ol.layer.Vector({
|
|
source: new ol.source.Vector({
|
|
url: url,
|
|
format: new ol.format.GeoJSON()
|
|
}),
|
|
style: styleFunction,
|
|
zIndex: 1,
|
|
updateWhileAnimating: false,
|
|
updateWhileInteracting: false,
|
|
renderBuffer: 50,
|
|
declutter: false,
|
|
// 줌 범위 설정
|
|
minZoom: zoomRange ? zoomRange.min : undefined,
|
|
maxZoom: zoomRange ? zoomRange.max : undefined
|
|
});
|
|
|
|
this.olMap.addLayer(this.vectorLayer);
|
|
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
|
console.log(`[${this.layerId}] GeoJSON Vector layer initialized${zoomInfo}`);
|
|
|
|
// GeoJSON 로드 완료 이벤트 처리
|
|
const source = this.vectorLayer.getSource();
|
|
source.once('change', () => {
|
|
if (source.getState() === 'ready') {
|
|
const features = source.getFeatures();
|
|
const featureCount = features.length;
|
|
console.log(`[${this.layerId}] GeoJSON loaded: ${featureCount} features`);
|
|
|
|
// feature 수에 따른 경고
|
|
if (featureCount > 10000) {
|
|
console.warn(`⚠️ Large dataset detected (${featureCount} features). Performance may be affected.`);
|
|
}
|
|
|
|
// 로드된 features가 있으면 해당 영역으로 자동 줌 (단독 레이어인 경우만)
|
|
if (features.length > 0 && !zoomRange) {
|
|
const extent = source.getExtent();
|
|
this.olMap.getView().fit(extent, {
|
|
padding: [50, 50, 50, 50],
|
|
maxZoom: 18
|
|
});
|
|
}
|
|
}
|
|
});
|
|
} else if (type === 'kml') {
|
|
console.log(`[${this.layerId}] Loading KML from: ${url}`);
|
|
|
|
this.vectorLayer = new ol.layer.Vector({
|
|
source: new ol.source.Vector({
|
|
url: url,
|
|
format: new ol.format.KML({
|
|
extractStyles: false,
|
|
showPointNames: false
|
|
})
|
|
}),
|
|
style: styleFunction,
|
|
zIndex: 1,
|
|
updateWhileAnimating: false,
|
|
updateWhileInteracting: false,
|
|
renderBuffer: 50,
|
|
declutter: false,
|
|
// 줌 범위 설정
|
|
minZoom: zoomRange ? zoomRange.min : undefined,
|
|
maxZoom: zoomRange ? zoomRange.max : undefined
|
|
});
|
|
|
|
this.olMap.addLayer(this.vectorLayer);
|
|
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
|
console.log(`[${this.layerId}] KML Vector layer initialized${zoomInfo}`);
|
|
|
|
// KML 로드 완료 이벤트 처리
|
|
const source = this.vectorLayer.getSource();
|
|
source.once('change', () => {
|
|
if (source.getState() === 'ready') {
|
|
const features = source.getFeatures();
|
|
console.log(`[${this.layerId}] KML loaded: ${features.length} features`);
|
|
|
|
if (features.length > 0 && !zoomRange) {
|
|
const extent = source.getExtent();
|
|
this.olMap.getView().fit(extent, {
|
|
padding: [50, 50, 50, 50],
|
|
maxZoom: 18
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
clear() {
|
|
if (this.vectorLayer) {
|
|
this.olMap.removeLayer(this.vectorLayer);
|
|
this.vectorLayer = null;
|
|
console.log(`[${this.layerId}] Layer cleared`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- 통합 멀티 레이어 관리자 (OpenLayers) ----
|
|
export class OLMultiLayerManager {
|
|
constructor(olMap) {
|
|
this.olMap = olMap;
|
|
this.layerMap = new Map();
|
|
this.pmtilesInstances = new Map();
|
|
|
|
console.log('OLMultiLayerManager initialized');
|
|
}
|
|
|
|
async toggleLayer(layerId, format) {
|
|
const key = `${format}_${layerId}`;
|
|
|
|
if (this.layerMap.has(key)) {
|
|
// 레이어 제거
|
|
// PMTiles 제거 시 연결된 detail GeoJSON도 함께 제거
|
|
|
|
const layer = this.layerMap.get(key);
|
|
layer.clear();
|
|
this.layerMap.delete(key);
|
|
|
|
if (this.pmtilesInstances.has(key)) {
|
|
this.pmtilesInstances.delete(key);
|
|
}
|
|
|
|
console.log(`[${key}] Layer removed`);
|
|
return false;
|
|
} else {
|
|
// 레이어 추가 로직
|
|
if (format === 'pbf') {
|
|
const layer = new OLVectorLayer(this.olMap, key);
|
|
const templateUrl = `http://172.16.41.52:3003/vector_tile_pbf/${layerId}/{z}/{x}/{y}.pbf`;
|
|
await layer.setTileSource(templateUrl, 'pbf');
|
|
this.layerMap.set(key, layer);
|
|
|
|
} else if (format === 'pmtiles') {
|
|
// console.log(`🚀 Creating hybrid PMTiles + GeoJSON layers for: ${layerId}`);
|
|
|
|
// // 1. PMTiles 레이어 추가 (zoom 0-17만 표시)
|
|
// const pmtilesLayer = new OLVectorLayer(this.olMap, key);
|
|
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
|
|
// const pmtiles = new PMTiles(pmtilesPath);
|
|
// this.pmtilesInstances.set(key, pmtiles);
|
|
// await pmtilesLayer.setTileSource(pmtiles, 'pmtiles', { min: 0, max: 18.1 });
|
|
// this.layerMap.set(key, pmtilesLayer);
|
|
// console.log(`✅ [${key}] PMTiles layer added (zoom 0-17)`);
|
|
|
|
// // 2. GeoJSON 레이어 추가 (zoom 18-24만 표시)
|
|
// const detailGeojsonKey = `geojson_detail_${layerId}`;
|
|
// const detailGeojsonLayer = new OLVectorLayer(this.olMap, detailGeojsonKey);
|
|
// const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
|
|
// await detailGeojsonLayer.setVectorSource(geojsonUrl, 'geojson', { min: 17.9, max: 24 });
|
|
// this.layerMap.set(detailGeojsonKey, detailGeojsonLayer);
|
|
// console.log(`✅ [${detailGeojsonKey}] Detail GeoJSON layer added (zoom 18-24)`);
|
|
|
|
// console.log(`🎯 Hybrid layer setup complete. PMTiles will show at zoom 0-17, GeoJSON at zoom 18+`);
|
|
|
|
|
|
const pmtilesLayer = new OLVectorLayer(this.olMap, key);
|
|
|
|
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
|
|
let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${layerId}.pmtiles`;
|
|
if (layerId == 'testbim') pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/dsdj2.pmtiles`;
|
|
|
|
// let mapName = layerId;
|
|
// if (layerId == 'testbim') mapName = 'dsdj2';
|
|
// let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${mapName}.pmtiles`;
|
|
|
|
let checkUrlExistsResult = await checkUrlExists(pmtilesPath);
|
|
if (checkUrlExistsResult == true) {
|
|
const pmtiles = new PMTiles(pmtilesPath);
|
|
this.pmtilesInstances.set(key, pmtiles);
|
|
await pmtilesLayer.setTileSource(pmtiles, 'pmtiles');
|
|
this.layerMap.set(key, pmtilesLayer);
|
|
} else {
|
|
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
|
|
pmtilesFitBtn.style.display = 'none';
|
|
return;
|
|
}
|
|
} else if (format === 'geojson') {
|
|
const layer = new OLVectorLayer(this.olMap, key);
|
|
const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
|
|
await layer.setVectorSource(geojsonUrl, 'geojson');
|
|
this.layerMap.set(key, layer);
|
|
|
|
} else if (format === 'kml') {
|
|
const layer = new OLVectorLayer(this.olMap, key);
|
|
const kmlUrl = `http://172.16.41.52:3003/vector_tile_kml/${layerId}.kml`;
|
|
await layer.setVectorSource(kmlUrl, 'kml');
|
|
this.layerMap.set(key, layer);
|
|
}
|
|
|
|
// console.log(`[${key}] Layer added`);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
clearAll() {
|
|
this.layerMap.forEach(layer => layer.clear());
|
|
this.layerMap.clear();
|
|
this.pmtilesInstances.clear();
|
|
console.log('All OpenLayers layers cleared');
|
|
}
|
|
|
|
getActiveLayers() {
|
|
return Array.from(this.layerMap.keys());
|
|
}
|
|
}
|
|
|
|
// ---- 피처별 스타일 함수 ----
|
|
function styleFunction(feature, resolution) {
|
|
const props = feature.getProperties();
|
|
|
|
const entityType = props?.EntityType;
|
|
let color = props.Color || '#ff0000';
|
|
const constWidth = props.constWidth || 0;
|
|
const ltscale = props.ltscale || 1.0;
|
|
|
|
// let dashPattern = props.dashPattern;
|
|
// if (dashPattern && typeof dashPattern === 'string') {
|
|
// try {
|
|
// // "[0.5,-0.25]" → [0.5, -0.25]
|
|
// dashPattern = JSON.parse(dashPattern);
|
|
// } catch (e) {
|
|
// console.warn('dashPattern 파싱 실패:', dashPattern, e);
|
|
// dashPattern = null;
|
|
// }
|
|
// }
|
|
|
|
// ⭐ constWidth에 따른 선 두께 결정
|
|
let strokeWidth = 1; // 기본값
|
|
// if (constWidth === 0) {
|
|
// strokeWidth = 1;
|
|
// } else if (constWidth === 1) {
|
|
// strokeWidth = 5;
|
|
// } else {
|
|
// // 향후 다른 값 대비
|
|
// // strokeWidth = 2 + (constWidth * 2);
|
|
// // strokeWidth = 1 + (constWidth * 5);
|
|
// strokeWidth = constWidth * 5;
|
|
// }
|
|
|
|
// if (constWidth === 0) {
|
|
// strokeWidth = 1;
|
|
// } else {
|
|
// // 향후 다른 값 대비
|
|
// // strokeWidth = 2 + (constWidth * 2);
|
|
// // strokeWidth = 1 + (constWidth * 5);
|
|
// strokeWidth = constWidth * 10;
|
|
// }
|
|
|
|
// if (constWidth === 0) {
|
|
// strokeWidth = 1;
|
|
// } else if (constWidth > 0 && constWidth <= 1) {
|
|
// strokeWidth = constWidth * 10;
|
|
// } else {
|
|
// strokeWidth = constWidth * 5;
|
|
// }
|
|
|
|
// if (constWidth === 0) {
|
|
// strokeWidth = 1;
|
|
// } else {
|
|
// console.log(constWidth);
|
|
// strokeWidth = constWidth*2;
|
|
// }
|
|
|
|
// // ⭐ dashPattern을 OpenLayers lineDash로 변환
|
|
// let lineDash = null;
|
|
// if (dashPattern && Array.isArray(dashPattern) && dashPattern.length > 0) {
|
|
// // const zoomFactor = Math.min(1 / resolution, 1.0);
|
|
// const scaleFactor = 10; // 조정 가능 (0.3 ~ 1.0 추천)
|
|
// lineDash = dashPattern.map(value =>
|
|
// Math.abs(value) * ltscale * scaleFactor
|
|
// );
|
|
// }
|
|
|
|
// 투명도 처리
|
|
const hexToRgba = (hex, alpha = 1) => {
|
|
if (!hex || hex.length < 7) return `rgba(255, 0, 0, ${alpha})`;
|
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
const g = parseInt(hex.slice(3, 5), 16);
|
|
const b = parseInt(hex.slice(5, 7), 16);
|
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
};
|
|
|
|
const geomType = feature.getGeometry().getType();
|
|
if (entityType) {
|
|
if (entityType === 'TEXT_POLYGON' || entityType === 'LINETYPE_PATTERN_TEXT') {
|
|
return new ol.style.Style({
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 1.0)
|
|
}),
|
|
zIndex: 40
|
|
});
|
|
} else if (entityType === 'LINETYPE_PATTERN_SHAPE') {
|
|
return new ol.style.Style({
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 0.4)
|
|
}),
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 20
|
|
});
|
|
} else if (entityType === 'HATCH') {
|
|
return new ol.style.Style({
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 0.4)
|
|
}),
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 10
|
|
});
|
|
} else if (entityType === 'SOLID_LINE_POLYGON' || entityType === 'DASHED_LINE_POLYGON') {
|
|
return new ol.style.Style({
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 1)
|
|
}),
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 10
|
|
});
|
|
} else if (entityType === 'LINETYPE_PATTERN_SHAPE_LINE') {
|
|
return new ol.style.Style({
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 30
|
|
});
|
|
} else if (entityType === 'LWPOLYLINE' || entityType === 'LINE') {
|
|
return new ol.style.Style({
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 30
|
|
});
|
|
}
|
|
} else {
|
|
if (geomType === 'Polygon' || geomType === 'MultiPolygon') {
|
|
return new ol.style.Style({
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 0.4)
|
|
}),
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 20
|
|
});
|
|
} else if (geomType === 'LineString' || geomType === 'MultiLineString') {
|
|
// let strokeWidth, color;
|
|
// if (props.DIVI == '주곡선') {
|
|
// strokeWidth = 2;
|
|
// color = '#cccccc';
|
|
// } else {
|
|
// strokeWidth = 1;
|
|
// color = '#777777';
|
|
// }
|
|
return new ol.style.Style({
|
|
stroke: new ol.style.Stroke({
|
|
color: hexToRgba(color, 1.0),
|
|
width: strokeWidth,
|
|
// lineDash: lineDash,
|
|
lineCap: 'butt',
|
|
lineJoin: 'miter'
|
|
}),
|
|
zIndex: 30
|
|
});
|
|
} else if (geomType === 'Point' || geomType === 'MultiPoint') {
|
|
return new ol.style.Style({
|
|
image: new ol.style.Circle({
|
|
radius: 4,
|
|
fill: new ol.style.Fill({
|
|
color: hexToRgba(color, 1.0)
|
|
})
|
|
}),
|
|
zIndex: 30
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// async function checkUrlExists(url) {
|
|
// try {
|
|
// const res = await fetch(url, { method: "HEAD" });
|
|
|
|
// if (res.ok) {
|
|
// return true; // 200~299 → 정상 URL
|
|
// }
|
|
// return false; // 404, 403 등
|
|
// } catch (e) {
|
|
// return false; // 네트워크 오류, CORS 오류 포함
|
|
// }
|
|
// }
|
|
|
|
async function checkUrlExists(url) {
|
|
try {
|
|
const res = await fetch(url, { method: "HEAD" });
|
|
|
|
// 200~299
|
|
return res.ok;
|
|
} catch (e) {
|
|
// 여기서 swallow(삼키기)
|
|
return false;
|
|
}
|
|
} |