@@ -302,13 +302,22 @@ function parseEPSGFromURN(urnString) {
302302 return match ? match [ 1 ] : null ;
303303}
304304
305- function mapTileMatrixSetToProjection ( crsCode ) {
305+ function mapTileMatrixSetToProjection ( crsCode , firstTileMatrix = null ) {
306306 const epsgMap = {
307307 '3857' : 'OSMTILE' ,
308+ '900913' : 'OSMTILE' , // Old "Google" code for Web Mercator
308309 '3978' : 'CBMTILE' ,
309- '4326' : 'WGS84' ,
310310 '5936' : 'APSTILE'
311311 } ;
312+
313+ // EPSG:4326 requires validation - must have 2x1 tiles at zoom 0 to be WGS84
314+ if ( crsCode === '4326' && firstTileMatrix ) {
315+ if ( firstTileMatrix . matrixWidth === 2 && firstTileMatrix . matrixHeight === 1 ) {
316+ return 'WGS84' ;
317+ }
318+ return null ; // EPSG:4326 but not WGS84-compliant tiling scheme
319+ }
320+
312321 return epsgMap [ crsCode ] || null ;
313322}
314323
@@ -336,18 +345,28 @@ function extractWMTSInfo(xmlDoc, baseUrl) {
336345
337346 tmsElements . forEach ( tmsEl => {
338347 const identifier = queryOWS ( tmsEl , 'Identifier' ) ?. textContent ;
348+ // Skip TileMatrixSet elements without an Identifier (these are references, not definitions)
349+ if ( ! identifier ) return ;
350+
339351 const crsURN = queryOWS ( tmsEl , 'SupportedCRS' ) ?. textContent ;
340352 const epsgCode = parseEPSGFromURN ( crsURN ) ;
341- const projection = epsgCode ? mapTileMatrixSetToProjection ( epsgCode ) : null ;
342353
343354 const tileMatrices = [ ] ;
344355 const tmElements = queryAllOWS ( tmsEl , 'TileMatrix' ) ;
345356 tmElements . forEach ( tm => {
357+ const matrixWidth = parseInt ( queryOWS ( tm , 'MatrixWidth' ) ?. textContent ) ;
358+ const matrixHeight = parseInt ( queryOWS ( tm , 'MatrixHeight' ) ?. textContent ) ;
346359 tileMatrices . push ( {
347- identifier : queryOWS ( tm , 'Identifier' ) ?. textContent
360+ identifier : queryOWS ( tm , 'Identifier' ) ?. textContent ,
361+ matrixWidth : matrixWidth || null ,
362+ matrixHeight : matrixHeight || null
348363 } ) ;
349364 } ) ;
350365
366+ // Pass first TileMatrix for validation (especially for EPSG:4326)
367+ const firstTileMatrix = tileMatrices . length > 0 ? tileMatrices [ 0 ] : null ;
368+ const projection = epsgCode ? mapTileMatrixSetToProjection ( epsgCode , firstTileMatrix ) : null ;
369+
351370 tileMatrixSets [ identifier ] = {
352371 identifier,
353372 crs : crsURN ,
@@ -750,7 +769,9 @@ function displayWMTSInfo(info, source, url) {
750769
751770 const supportedCount = Object . values ( info . tileMatrixSets ) . filter ( tms => tms . supported ) . length ;
752771
753- serviceDetails . innerHTML = sourceNote + '<p><strong>Title:</strong> ' + info . title + ' ' + serviceTypeBadge + '</p><p><strong>Version:</strong> ' + info . version + '</p><p><strong>TileMatrixSets:</strong> ' + Object . keys ( info . tileMatrixSets ) . length + ' (' + supportedCount + ' supported)</p><details class="service-abstract"><summary><strong>Abstract</strong></summary><p>' + info . abstract + '</p></details><h3>Available Layers (' + info . layers . length + ')</h3><div class="layers-list">' + layersList + '</div>' ;
772+ const urlNote = source !== 'file' ? '<p><strong>Loaded URL:</strong> <a href="' + url + '" target="_blank" rel="noopener noreferrer">' + url + '</a></p>' : '' ;
773+
774+ serviceDetails . innerHTML = sourceNote + '<p><strong>Title:</strong> ' + info . title + ' ' + serviceTypeBadge + '</p><p><strong>Version:</strong> ' + info . version + '</p><p><strong>TileMatrixSets:</strong> ' + Object . keys ( info . tileMatrixSets ) . length + ' (' + supportedCount + ' supported)</p><details class="service-abstract"><summary><strong>Abstract</strong></summary><p>' + info . abstract + '</p></details>' + urlNote + '<h3>Available Layers (' + info . layers . length + ')</h3><div class="layers-list">' + layersList + '</div>' ;
754775
755776 serviceInfo . classList . remove ( 'hidden' ) ;
756777
@@ -895,6 +916,7 @@ function displayServiceInfo(info, source, loadedUrl) {
895916 const sourceNote = source === 'file'
896917 ? '<p><em>(Loaded from file)</em></p>'
897918 : '' ;
919+ const serviceTypeBadge = '<span class="service-type-badge" style="background: #2196F3; color: white; padding: 2px 8px; border-radius: 3px; font-size: 0.9em; margin-left: 10px;">WMS</span>' ;
898920
899921 const formatOptions = info . getFeatureInfoFormats . map ( fmt =>
900922 `<option value="${ fmt } ">${ fmt } </option>`
@@ -979,7 +1001,7 @@ function displayServiceInfo(info, source, loadedUrl) {
9791001
9801002 serviceDetails . innerHTML = `
9811003 ${ sourceNote }
982- <p><strong>Title:</strong> ${ info . title } </p>
1004+ <p><strong>Title:</strong> ${ info . title } ${ serviceTypeBadge } </p>
9831005 <p><strong>Version:</strong> ${ info . version } </p>
9841006 <details class="service-abstract">
9851007 <summary><strong>Abstract</strong></summary>
@@ -1255,8 +1277,13 @@ function createQueryLink(layer, version, projectionCode, infoFormat, layerIndex,
12551277 tref += `&INFO_FORMAT=${ encodeURIComponent ( infoFormat ) } ` ;
12561278
12571279 // Add STYLES parameter if a style is selected
1280+ // Use empty STYLES= for 'default-' prefixed styles to work around GeoServer LayerGroup issues
12581281 if ( styleName ) {
1259- tref += '&STYLES={style}' ;
1282+ if ( styleName . toLowerCase ( ) . startsWith ( 'default-' ) ) {
1283+ tref += '&STYLES=' ;
1284+ } else {
1285+ tref += '&STYLES={style}' ;
1286+ }
12601287 }
12611288
12621289 // Add dimension parameters to query link only if enabled in UI
@@ -1769,8 +1796,27 @@ function addWMTSLayerToViewer(viewer, layer, tileMatrixSet, selectedFormat, quer
17691796 mapExtent . setAttribute ( 'units' , viewerProjection ) ;
17701797 mapExtent . setAttribute ( 'checked' , '' ) ;
17711798
1772- const minZoom = tileMatrixSet . tileMatrices . length > 0 ? tileMatrixSet . tileMatrices [ 0 ] . identifier : '0' ;
1773- const maxZoom = tileMatrixSet . tileMatrices . length > 0 ? tileMatrixSet . tileMatrices [ tileMatrixSet . tileMatrices . length - 1 ] . identifier : '18' ;
1799+ // Extract zoom levels from TileMatrix identifiers, handling prefixes like "EPSG:900913:0"
1800+ let minZoom = '0' ;
1801+ let maxZoom = '18' ;
1802+
1803+ if ( tileMatrixSet . tileMatrices . length > 0 ) {
1804+ const firstId = tileMatrixSet . tileMatrices [ 0 ] . identifier ;
1805+ const lastId = tileMatrixSet . tileMatrices [ tileMatrixSet . tileMatrices . length - 1 ] . identifier ;
1806+
1807+ // Try to extract numeric suffix from identifiers like "EPSG:900913:0"
1808+ const firstMatch = firstId ? firstId . match ( / : ( \d + ) $ / ) : null ;
1809+ const lastMatch = lastId ? lastId . match ( / : ( \d + ) $ / ) : null ;
1810+
1811+ if ( firstMatch && lastMatch ) {
1812+ minZoom = firstMatch [ 1 ] ;
1813+ maxZoom = lastMatch [ 1 ] ;
1814+ } else {
1815+ // Use the identifiers as-is if they're already numeric
1816+ minZoom = firstId || '0' ;
1817+ maxZoom = lastId || '18' ;
1818+ }
1819+ }
17741820
17751821 const zoomInput = document . createElement ( 'map-input' ) ;
17761822 zoomInput . setAttribute ( 'name' , 'z' ) ;
@@ -1838,7 +1884,25 @@ function addWMTSLayerToViewer(viewer, layer, tileMatrixSet, selectedFormat, quer
18381884
18391885 let tref = tileResource . template ;
18401886 tref = tref . replace ( / { T i l e M a t r i x S e t } / g, tileMatrixSet . identifier ) ;
1841- tref = tref . replace ( / { T i l e M a t r i x } / g, '{z}' ) ;
1887+
1888+ // Handle TileMatrix identifiers that may have a prefix (e.g., "EPSG:900913:0")
1889+ // Extract prefix if all TileMatrix identifiers follow a "prefix:number" pattern
1890+ let tileMatrixReplacement = '{z}' ;
1891+ if ( tileMatrixSet . tileMatrices && tileMatrixSet . tileMatrices . length > 0 ) {
1892+ const firstId = tileMatrixSet . tileMatrices [ 0 ] . identifier ;
1893+ const lastId = tileMatrixSet . tileMatrices [ tileMatrixSet . tileMatrices . length - 1 ] . identifier ;
1894+
1895+ // Check if identifiers end with numbers and have a common prefix
1896+ const firstMatch = firstId ? firstId . match ( / ^ ( .+ ) : ( \d + ) $ / ) : null ;
1897+ const lastMatch = lastId ? lastId . match ( / ^ ( .+ ) : ( \d + ) $ / ) : null ;
1898+
1899+ if ( firstMatch && lastMatch && firstMatch [ 1 ] === lastMatch [ 1 ] ) {
1900+ // All TileMatrix identifiers share the same prefix
1901+ tileMatrixReplacement = firstMatch [ 1 ] + ':{z}' ;
1902+ }
1903+ }
1904+
1905+ tref = tref . replace ( / { T i l e M a t r i x } / g, tileMatrixReplacement ) ;
18421906 tref = tref . replace ( / { T i l e R o w } / g, '{y}' ) ;
18431907 tref = tref . replace ( / { T i l e C o l } / g, '{x}' ) ;
18441908 tref = tref . replace ( / { S t y l e } / g, styleName ) ;
@@ -1859,7 +1923,22 @@ function addWMTSLayerToViewer(viewer, layer, tileMatrixSet, selectedFormat, quer
18591923
18601924 let qtref = queryResource . template ;
18611925 qtref = qtref . replace ( / { T i l e M a t r i x S e t } / g, tileMatrixSet . identifier ) ;
1862- qtref = qtref . replace ( / { T i l e M a t r i x } / g, '{z}' ) ;
1926+
1927+ // Handle TileMatrix identifiers that may have a prefix (e.g., "EPSG:900913:0")
1928+ let tileMatrixReplacement = '{z}' ;
1929+ if ( tileMatrixSet . tileMatrices && tileMatrixSet . tileMatrices . length > 0 ) {
1930+ const firstId = tileMatrixSet . tileMatrices [ 0 ] . identifier ;
1931+ const lastId = tileMatrixSet . tileMatrices [ tileMatrixSet . tileMatrices . length - 1 ] . identifier ;
1932+
1933+ const firstMatch = firstId ? firstId . match ( / ^ ( .+ ) : ( \d + ) $ / ) : null ;
1934+ const lastMatch = lastId ? lastId . match ( / ^ ( .+ ) : ( \d + ) $ / ) : null ;
1935+
1936+ if ( firstMatch && lastMatch && firstMatch [ 1 ] === lastMatch [ 1 ] ) {
1937+ tileMatrixReplacement = firstMatch [ 1 ] + ':{z}' ;
1938+ }
1939+ }
1940+
1941+ qtref = qtref . replace ( / { T i l e M a t r i x } / g, tileMatrixReplacement ) ;
18631942 qtref = qtref . replace ( / { T i l e R o w } / g, '{y}' ) ;
18641943 qtref = qtref . replace ( / { T i l e C o l } / g, '{x}' ) ;
18651944 qtref = qtref . replace ( / { S t y l e } / g, styleName ) ;
0 commit comments