diff --git a/anemui-core/src/LayerManager.ts b/anemui-core/src/LayerManager.ts index 1903853..334f055 100644 --- a/anemui-core/src/LayerManager.ts +++ b/anemui-core/src/LayerManager.ts @@ -37,7 +37,11 @@ export type AnemuiLayer={ credit?: string, wmsParams?: { [key: string]: string }, cssFilter?: string, - format?: string + format?: string, + /** URL WMS equivalente para usar en la exportación del mapa (cuando el tipo no es WMS) */ + wmsExportUrl?: string, + /** Nombre de capa WMS para la exportación */ + wmsExportLayer?: string } const baseStyle= new Style({ @@ -88,9 +92,8 @@ export class LayerManager { // CAPAS BASE // ------ Global this.addBaseLayer({name:"Mapa topográfico nacional (IGN)",url: 'https://www.ign.es/wms-inspire/ign-base?',type:AL_TYPE_WMS,layer:'IGNBaseTodo', global:true, credit:ign}) - this.addBaseLayer({name:"Foto satélite global ARCGIS",url:"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",type:AL_TYPE_OSM, global:true, credit:'© Esri'}) + this.addBaseLayer({name:"Foto satélite global ARCGIS",url:"https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",type:AL_TYPE_OSM, global:true, credit:'© Esri', wmsExportUrl:'https://services.arcgisonline.com/ArcGIS/services/World_Imagery/MapServer/WMSServer?', wmsExportLayer:'0'}) this.addBaseLayer({name:"Mapa global OpenStreet Map",url:undefined,type:AL_TYPE_OSM, global:true, credit:'© OpenStreetMap contributors'}) - this.addBaseLayer({name:"Capa fondo global EUMETSAT",url:'https://view.eumetsat.int/geoserver/wms?',type:AL_TYPE_WMS,layer:'backgrounds:ne_background', global:true, credit:'© EUMETSAT'}) this.addBaseLayer({name:"Fondo relieve global GEBCO (IGN)",url: 'https://www.ign.es/wmts/mapa-raster?',type:AL_TYPE_WMTS,layer:'MTN_Fondo', global:true, credit:ign, format:'image/jpeg'}) // ------ Estatal this.addBaseLayer({name:"Ortofoto nacional (PNOA)",url: 'https://www.ign.es/wms-inspire/pnoa-ma?',type:AL_TYPE_WMS,layer:'OI.OrthoimageCoverage', global:false, credit:ign_pnoa}) @@ -216,12 +219,21 @@ export class LayerManager { return this.baseLayers[this.baseSelected[layer]].source } - /** Returns the WMS-capable selected base layers in order (bottom to top), for use in map export */ + /** Devuelve las capas base seleccionadas con info WMS para la exportación del mapa. + * Para capas de tipo WMS usa su URL directamente; para otros tipos (OSM, WMTS) + * usa wmsExportUrl/wmsExportLayer si están definidos. */ public getBaseLayerWmsInfo(): Array<{url: string, layer: string, transparent: boolean}> { - return this.baseSelected - .map(name => this.baseLayers[name]) - .filter(l => l && l.type === AL_TYPE_WMS) - .map((l, idx) => ({ url: l.url, layer: l.layer, transparent: idx > 0 })); + const result: Array<{url: string, layer: string, transparent: boolean}> = []; + this.baseSelected.forEach((name, idx) => { + const l = this.baseLayers[name]; + if (!l) return; + if (l.type === AL_TYPE_WMS) { + result.push({ url: l.url, layer: l.layer, transparent: idx > 0 }); + } else if (l.wmsExportUrl && l.wmsExportLayer) { + result.push({ url: l.wmsExportUrl, layer: l.wmsExportLayer, transparent: idx > 0 }); + } + }); + return result; } //TopLayer diff --git a/anemui-core/src/OpenLayersMap.ts b/anemui-core/src/OpenLayersMap.ts index 234c55c..5bcc443 100644 --- a/anemui-core/src/OpenLayersMap.ts +++ b/anemui-core/src/OpenLayersMap.ts @@ -758,6 +758,16 @@ export class OpenLayerMap implements CsMapController { // Esperar a que se construyan las imágenes antes de registrar las capas await buildImages(promises, this.uncertaintyLayer, state, timesJs, app, this.ncExtents, true); + // Si buildImages falló internamente (p.ej. fichero .bin inexistente), las capas quedan + // sin source. Limpiar y desactivar para no reintentar en cada cambio de zoom. + const hasData = this.uncertaintyLayer.some(l => (l as any).getSource() !== null); + if (!hasData) { + this.safelyRemoveUncertaintyLayers(); + this.uncertaintyLayer = []; + state.uncertaintyLayer = false; + return; + } + // Registrar las capas en LayerManager después de construirlas lmgr.setUncertaintyLayer(this.uncertaintyLayer); @@ -1389,27 +1399,51 @@ export class OpenLayerMap implements CsMapController { capture.height = height; const ctx = capture.getContext('2d'); - const olCanvases = this.map.getViewport().querySelectorAll('.ol-layer canvas') as NodeListOf; + // Usar el mismo selector que el ejemplo oficial de OL para capturar todas las capas + const olCanvases = this.map.getViewport().querySelectorAll('.ol-layer canvas, canvas.ol-layer') as NodeListOf; olCanvases.forEach((canvas) => { if (canvas.width > 0) { ctx.save(); - const opacity = (canvas.parentNode as HTMLElement).style.opacity || '1'; - ctx.globalAlpha = parseFloat(opacity); + const parent = canvas.parentNode as HTMLElement; + const opacity = parent?.style?.opacity || canvas.style.opacity || '1'; + ctx.globalAlpha = opacity === '' ? 1 : parseFloat(opacity); + const transform = canvas.style.transform; - const matrix = transform.match(/^matrix\(([^\(]*)\)$/); - if (matrix) { - const values = matrix[1].split(',').map(Number); - ctx.setTransform(values[0], values[1], values[2], values[3], values[4], values[5]); + let matrix: number[]; + const matrixMatch = transform && transform.match(/^matrix\(([^\(]*)\)$/); + if (matrixMatch) { + matrix = matrixMatch[1].split(',').map(Number); + } else { + // Fallback para capas HiDPI: la transformación real es el ratio style.width/canvas.width + const sw = parseFloat(canvas.style.width); + const sh = parseFloat(canvas.style.height); + matrix = [ + sw > 0 ? sw / canvas.width : 1, 0, 0, + sh > 0 ? sh / canvas.height : 1, 0, 0 + ]; } + ctx.setTransform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); + + // Aplicar backgroundColor del contenedor si existe (algunos tiles tienen fondo definido) + if (parent?.style?.backgroundColor) { + ctx.fillStyle = parent.style.backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + ctx.drawImage(canvas, 0, 0); ctx.restore(); } }); + + // Restaurar transform a identidad antes de devolver + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.globalAlpha = 1; return capture; } private buildWmsUrlForLayer(url: string, layer: string, bbox4326: number[], width: number, height: number, transparent: boolean): string { - return url + + const base = url.endsWith('?') || url.includes('?') ? url : url + '?'; + return base + 'SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap' + '&LAYERS=' + encodeURIComponent(layer) + '&STYLES=' + '&SRS=EPSG:4326' + @@ -1423,7 +1457,9 @@ export class OpenLayerMap implements CsMapController { canBbox: number[], canW: number, canH: number, callback: (mainBg: CanvasImageSource | null, canBg: CanvasImageSource | null) => void ): void { - // Obtain WMS-capable selected base layers from LayerManager; fallback to IGN base if none + // Obtener capas base con info WMS para la exportación. Incluye capas WMS nativas y + // capas OSM/WMTS que tengan wmsExportUrl configurado (p.ej. ESRI satélite). + // Si ninguna capa tiene equivalente WMS, usar IGN base como fallback cartográfico. let wmsLayers = LayerManager.getInstance().getBaseLayerWmsInfo(); if (wmsLayers.length === 0) { wmsLayers = [{ url: 'https://www.ign.es/wms-inspire/ign-base?', layer: 'IGNBaseTodo', transparent: false }];