Files
PM_test/views/main/jsm/archive/olPmtiles.js
2026-06-12 17:14:03 +09:00

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;
}
}