diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index 6cbc054f8..538fad976 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -569,14 +569,224 @@ GisOlLayer.prototype = { cesiumScene.camera.moveEnd.addEventListener(cesiumScene._moveEndListener); } }, - getFeatureById: function(featureId){ - this.layers - .getArray() - .forEach(layer => { - const source = layer.getSource(); - const feature = source.getFeatureById(String(featureId).trim()); - if (feature) return feature; - }); + getFeatureById: function (featureId) { + let returnFeature; + this.layers.getArray().some(function (layer) { + const source = layer.getSource(); + + if (source instanceof VectorSource) { + const feature = source.getFeatureById(featureId); + if (feature) { + returnFeature = feature; + return true; + } + } + }); + + return returnFeature; + }, + getOpenLayersZoom: function (cesiumScene) { + const cameraHeight = cesiumScene.camera.positionCartographic.height; + const maxResolution = 156543.03392804097; // OpenLayers 기본 해상도 (Google Mercator 기준) + const tileSize = 256; // 기본 타일 크기 + return Math.ceil(Math.log2(maxResolution / (cameraHeight / tileSize))); + }, + ensureSearchLayer: function () { + let layer = this.searchLayer; + + if (!layer) { + layer = new VectorLayer({ + source: new VectorSource(), + zIndex: 9999 + }); + let map = GisApp.Module.core.map; + map.addLayer(layer); + } + return layer; + }, + clearSearchLayer: function () { + const layer = this.ensureSearchLayer(); + layer.getSource().clear(true); + }, + showSearchResultsFromGeoJSON: function (lyrTyNm, featureId) { + const layer = this.ensureSearchLayer(); + + // 이전 검색결과는 깨끗이 + const prev = layer.getSource(); + if (prev) prev.clear(true); + + //레이어 타입 설정(point, line 등. 이걸 style에서 사용) + const layerCode = GisApp.layerCode; + const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm); + layer.set("gobjTy", matched.gobjTy); + + // 새 소스 만들기 + const source = this.getFeatureSourceFromGeoserver(lyrTyNm, featureId); + + // 1) 로드 완료시 extent 맞추기 + const onLoadEnd = () => { + const feats = source.getFeatures(); + if (feats && feats.length > 0) { + const select = GisApp.Module.select; + select.selectFeature(feats[0], false); + } else { + console.warn('[search-layer] GeoJSON에 피처가 없습니다.'); + } + // 한번만 쓰고 이벤트 정리 + source.un('featuresloadend', onLoadEnd); + source.un('featuresloaderror', onLoadError); + }; + + const onLoadError = (e) => { + console.error('[search-layer] GeoJSON 로드 실패:', e); + source.un('featuresloadend', onLoadEnd); + source.un('featuresloaderror', onLoadError); + }; + + // 이벤트를 먼저 걸고 → setSource + source.once('featuresloadend', onLoadEnd); + source.once('featuresloaderror', onLoadError); + + // 레이어에 소스 적용(이때부터 로딩 시작) + layer.setSource(source); + + // 필요시 명시적으로 재요청 + source.refresh(); + }, + /** + * 피쳐 ID로 지오서버에서 검색해서 피처의 벡터소스를 리턴 + * @param {} lyrTyNm + * @param {*} featureId + * @returns + */ + getFeatureSourceFromGeoserver: function (lyrTyNm, featureId) { + + const typeName = this.storageNm + lyrTyNm; + const srsName = GisApp.TO_PROJ; + const baseUrl = GisApp.GEOSERVER_URL; + const source = new VectorSource({ + format: new GeoJSON(), + url: () => { + const params = new URLSearchParams({ + SERVICE: 'WFS', + VERSION: '1.1.0', + REQUEST: 'GetFeature', + TYPENAME: typeName, + SRSNAME: srsName, + OUTPUTFORMAT: 'application/json', + FEATUREID: featureId, + }); + return `${baseUrl}?${params.toString()}`; + }, + strategy: all, + }); + + return source; + }, + /** + * 여러 개의 검색 결과를 GeoJSON으로 표시 + * @param {string} lyrTyNm 레이어 타입명 + * @param {Array|string} featureIds 피처 ID 배열 또는 단일 ID + */ + showMultipleSearchResultsFromGeoJSON: function (lyrTyNm, featureIds) { + const layer = this.ensureSearchLayer(); + + const prev = layer.getSource(); + if (prev) prev.clear(true); + + const layerCode = GisApp.layerCode; + const matched = Object.values(layerCode).find(obj => obj.lyrTyNm === lyrTyNm); + layer.set("gobjTy", matched.gobjTy); + + const source = this.getMultiFeatureSourceFromGeoserver(lyrTyNm, featureIds); + + const onLoadEnd = async () => { + const feats = source.getFeatures(); + if (feats && feats.length > 0) { + const select = GisApp.Module.select; + await select.resetSelectedFeature(); + for (const feature of feats) select.selectMultiFeature(feature, true); + const geometries = feats.map(f => f.getGeometry()); + if (geometries.length > 0) { + const extent = this.combineExtents(geometries.map(g => g?.getExtent())); + + let map = GisApp.Module.core.map; + map.getView().fit(extent, { + duration: 300, + padding: [50, 50, 50, 50], + maxZoom: 16 + }); + } + } else { + console.warn('[search-layer] GeoJSON에 피처가 없습니다.'); + } + source.un('featuresloadend', onLoadEnd); + source.un('featuresloaderror', onLoadError); + }; + + const onLoadError = (e) => { + console.error('[search-layer] GeoJSON 로드 실패:', e); + source.un('featuresloadend', onLoadEnd); + source.un('featuresloaderror', onLoadError); + }; + + source.once('featuresloadend', onLoadEnd); + source.once('featuresloaderror', onLoadError); + + layer.setSource(source); + + source.refresh(); + }, + /** + * 피쳐 ID로 지오서버에서 검색해서 여러개 피처의 벡터소스를 리턴 + * @param {} lyrTyNm + * @param {*} featureId + * @returns + */ + getMultiFeatureSourceFromGeoserver: function (lyrTyNm, featureId) { + + const typeName = this.storageNm + lyrTyNm; + const srsName = GisApp.TO_PROJ; + const baseUrl = GisApp.GEOSERVER_URL; + + const ids = Array.isArray(featureId) ? featureId : [featureId]; + + const source = new VectorSource({ + format: new GeoJSON(), + url: () => { + const params = new URLSearchParams({ + SERVICE: 'WFS', + VERSION: '1.1.0', + REQUEST: 'GetFeature', + TYPENAME: typeName, + SRSNAME: srsName, + OUTPUTFORMAT: 'application/json', + FEATUREID: ids.join(','), + }); + return `${baseUrl}?${params.toString()}`; + }, + strategy: all, + }); + + return source; + }, + /** + * 여러 extent 배열을 받아 결합된 extent를 반환 + * @param {Array>} extents [[minX,minY,maxX,maxY], ...] + * @returns {Array|null} + */ + combineExtents: function (extents) { + if (!extents || extents.length === 0) return null; + let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; + for (const e of extents) { + if (!e || e.length < 4) continue; + minX = Math.min(minX, e[0]); + minY = Math.min(minY, e[1]); + maxX = Math.max(maxX, e[2]); + maxY = Math.max(maxY, e[3]); + } + if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) return null; + return [minX, minY, maxX, maxY]; } };