22
33const CORS_PROXY = 'https://corsproxy.io/?' ;
44
5+ // Format dimension name as WMS parameter (time/elevation unchanged, others get DIM_ prefix)
6+ function formatDimensionParam ( dimensionName ) {
7+ const name = dimensionName . toLowerCase ( ) ;
8+ if ( name === 'time' || name === 'elevation' ) {
9+ return dimensionName . toUpperCase ( ) ;
10+ }
11+ // Check if DIM_ is already prefixed to avoid double-prefixing
12+ if ( name . startsWith ( 'dim_' ) ) {
13+ return dimensionName . toUpperCase ( ) ;
14+ }
15+ return 'DIM_' + dimensionName . toUpperCase ( ) ;
16+ }
17+
518// Transform WGS84 coordinates to Web Mercator (EPSG:3857)
619function wgs84ToWebMercator ( lon , lat ) {
720 const x = ( lon * 20037508.34 ) / 180 ;
@@ -398,20 +411,36 @@ function extractServiceInfo(xmlDoc, baseUrl) {
398411 }
399412 }
400413
414+ // Extract projection-specific BoundingBox elements
415+ const boundingBoxes = { } ;
416+ const bboxElements = parentLayer . querySelectorAll ( ':scope > BoundingBox' ) ;
417+ bboxElements . forEach ( bboxEl => {
418+ const crs = bboxEl . getAttribute ( 'CRS' ) || bboxEl . getAttribute ( 'SRS' ) ;
419+ if ( crs ) {
420+ boundingBoxes [ crs ] = {
421+ minx : bboxEl . getAttribute ( 'minx' ) ,
422+ miny : bboxEl . getAttribute ( 'miny' ) ,
423+ maxx : bboxEl . getAttribute ( 'maxx' ) ,
424+ maxy : bboxEl . getAttribute ( 'maxy' )
425+ } ;
426+ }
427+ } ) ;
428+
401429 if ( minx && miny && maxx && maxy ) {
402430 layers . push ( {
403431 name,
404432 title : title || name ,
405433 abstract : abstract || '' ,
406434 bbox : { minx, miny, maxx, maxy } ,
435+ boundingBoxes,
407436 queryable,
408437 styles,
409438 licenseUrl,
410439 licenseTitle,
411440 supportedProjections,
412441 dimensions,
413442 } ) ;
414- console . log ( 'Layer:' , name , 'Projections:' , supportedProjections . join ( ', ' ) || 'none' , 'Dimensions:' , dimensions . length ) ;
443+ console . log ( 'Layer:' , name , 'Projections:' , supportedProjections . join ( ', ' ) || 'none' , 'Dimensions:' , dimensions . length , 'BoundingBoxes:' , Object . keys ( boundingBoxes ) . join ( ', ' ) || 'none' ) ;
415444 }
416445 } ) ;
417446
@@ -460,6 +489,10 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
460489 <p>${ layer . abstract } </p>
461490 </details>
462491 ` : '' }
492+ <div class="bounds-selector">
493+ <input type="checkbox" id="bounds-${ index } " class="bounds-checkbox" title="Include layer bounds (disable if WMS bounds are incorrect)" checked />
494+ <label for="bounds-${ index } " class="bounds-label">Include Bounds</label>
495+ </div>
463496 ${ layer . queryable ? `
464497 <div class="query-format-selector">
465498 <input type="checkbox" id="query-${ index } " class="query-checkbox" title="Enable GetFeatureInfo queries" />
@@ -540,7 +573,9 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
540573 const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
541574 const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
542575 const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
543- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection ) ;
576+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
577+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
578+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection , boundsEnabled ) ;
544579 } else {
545580 removeViewerForLayer ( index ) ;
546581 }
@@ -575,6 +610,29 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
575610 }
576611 }
577612
613+ // Add event listener to bounds checkbox
614+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
615+ if ( boundsCheckbox ) {
616+ boundsCheckbox . addEventListener ( 'change' , ( e ) => {
617+ const layerCheckbox = document . getElementById ( `layer-${ index } ` ) ;
618+ if ( layerCheckbox . checked ) {
619+ const queryCheckbox = document . getElementById ( `query-${ index } ` ) ;
620+ const queryEnabled = queryCheckbox ? queryCheckbox . checked : false ;
621+ const formatSelect = document . getElementById ( `format-${ index } ` ) ;
622+ const selectedFormat = formatSelect ? formatSelect . value : info . getFeatureInfoFormats [ 0 ] ;
623+ const styleSelect = document . getElementById ( `style-${ index } ` ) ;
624+ const selectedStyle = styleSelect ? styleSelect . value : ( layer . styles && layer . styles . length > 0 ? layer . styles [ 0 ] . name : '' ) ;
625+ const imgFormatSelect = document . getElementById ( `img-format-${ index } ` ) ;
626+ const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
627+ const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
628+ const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
629+ // Recreate viewer with new bounds setting
630+ removeViewerForLayer ( index ) ;
631+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection , e . target . checked ) ;
632+ }
633+ } ) ;
634+ }
635+
578636 // Add event listener to style selector if available
579637 if ( layer . styles && layer . styles . length > 0 ) {
580638 const styleSelect = document . getElementById ( `style-${ index } ` ) ;
@@ -597,9 +655,11 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
597655 const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
598656 const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
599657 const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
658+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
659+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
600660 // Recreate viewer with new style
601661 removeViewerForLayer ( index ) ;
602- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , e . target . value , selectedImgFormat , selectedProjection ) ;
662+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , e . target . value , selectedImgFormat , selectedProjection , boundsEnabled ) ;
603663 }
604664 } ) ;
605665 }
@@ -620,9 +680,11 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
620680 const selectedStyle = styleSelect ? styleSelect . value : ( layer . styles && layer . styles . length > 0 ? layer . styles [ 0 ] . name : '' ) ;
621681 const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
622682 const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
683+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
684+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
623685 // Recreate viewer with new image format
624686 removeViewerForLayer ( index ) ;
625- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , e . target . value , selectedProjection ) ;
687+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , e . target . value , selectedProjection , boundsEnabled ) ;
626688 }
627689 } ) ;
628690 }
@@ -643,9 +705,11 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
643705 const selectedStyle = styleSelect ? styleSelect . value : ( layer . styles && layer . styles . length > 0 ? layer . styles [ 0 ] . name : '' ) ;
644706 const imgFormatSelect = document . getElementById ( `img-format-${ index } ` ) ;
645707 const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
708+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
709+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
646710 // Recreate viewer with new projection
647711 removeViewerForLayer ( index ) ;
648- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , e . target . value ) ;
712+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , e . target . value , boundsEnabled ) ;
649713 }
650714 } ) ;
651715 }
@@ -671,9 +735,11 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
671735 const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
672736 const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
673737 const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
738+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
739+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
674740 // Recreate viewer with new dimension value
675741 removeViewerForLayer ( index ) ;
676- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection ) ;
742+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection , boundsEnabled ) ;
677743 }
678744 } ) ;
679745 }
@@ -692,9 +758,11 @@ function displayServiceInfo(info, usedProxy, loadedUrl) {
692758 const selectedImgFormat = imgFormatSelect ? imgFormatSelect . value : ( info . getMapFormats && info . getMapFormats . length > 0 ? info . getMapFormats [ 0 ] : 'image/png' ) ;
693759 const projectionSelect = document . getElementById ( `projection-${ index } ` ) ;
694760 const selectedProjection = projectionSelect ? projectionSelect . value : 'OSMTILE' ;
761+ const boundsCheckbox = document . getElementById ( `bounds-${ index } ` ) ;
762+ const boundsEnabled = boundsCheckbox ? boundsCheckbox . checked : true ;
695763 // Recreate viewer when dimension is enabled/disabled
696764 removeViewerForLayer ( index ) ;
697- createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection ) ;
765+ createViewerForLayer ( index , layer , info . version , selectedFormat , queryEnabled , selectedStyle , selectedImgFormat , selectedProjection , boundsEnabled ) ;
698766 }
699767 } ) ;
700768 }
@@ -762,7 +830,8 @@ function createQueryLink(layer, version, projectionCode, infoFormat, layerIndex,
762830 const isDimensionEnabled = ! dimensionCheckbox || dimensionCheckbox . checked ;
763831
764832 if ( isDimensionEnabled ) {
765- tref += `&${ dimension . name } ={${ dimension . name } }` ;
833+ const paramName = formatDimensionParam ( dimension . name ) ;
834+ tref += `&${ paramName } ={${ dimension . name } }` ;
766835 }
767836 } ) ;
768837 }
@@ -839,7 +908,7 @@ function updateLayerQuery(layerName, queryEnabled, layer, version, selectedForma
839908 }
840909}
841910
842- function createViewerForLayer ( index , layer , version , selectedFormat , queryEnabled , selectedStyle , imageFormat , projection ) {
911+ function createViewerForLayer ( index , layer , version , selectedFormat , queryEnabled , selectedStyle , imageFormat , projection , boundsEnabled ) {
843912 const container = document . getElementById ( `viewer-container-${ index } ` ) ;
844913 if ( ! container ) return ;
845914
@@ -848,6 +917,9 @@ function createViewerForLayer(index, layer, version, selectedFormat, queryEnable
848917
849918 // Default to OSMTILE if no projection specified
850919 const selectedProjection = projection || 'OSMTILE' ;
920+
921+ // Default to true if not specified
922+ const includeBounds = boundsEnabled !== undefined ? boundsEnabled : true ;
851923
852924 // Create mapml-viewer element
853925 const viewer = document . createElement ( 'mapml-viewer' ) ;
@@ -873,6 +945,13 @@ function createViewerForLayer(index, layer, version, selectedFormat, queryEnable
873945 baseLayer . setAttribute ( 'checked' , '' ) ;
874946 baseExtent . setAttribute ( 'units' , 'OSMTILE' ) ;
875947
948+ // Add license link
949+ const licenseLink = document . createElement ( 'map-link' ) ;
950+ licenseLink . setAttribute ( 'rel' , 'license' ) ;
951+ licenseLink . setAttribute ( 'href' , 'https://open.canada.ca/en/open-government-licence-canada' ) ;
952+ licenseLink . setAttribute ( 'title' , 'Open Government Licence - Canada' ) ;
953+ baseExtent . appendChild ( licenseLink ) ;
954+
876955 // Add zoom input
877956 const zoomInput = document . createElement ( 'map-input' ) ;
878957 zoomInput . setAttribute ( 'name' , 'z' ) ;
@@ -915,11 +994,17 @@ function createViewerForLayer(index, layer, version, selectedFormat, queryEnable
915994 baseExtent . setAttribute ( 'units' , 'CBMTILE' ) ;
916995 baseExtent . setAttribute ( 'label' , 'Canada Base Map - Transportation' ) ;
917996
997+ // Add extent bounds
998+ const mapMeta = document . createElement ( 'map-meta' ) ;
999+ mapMeta . setAttribute ( 'name' , 'extent' ) ;
1000+ mapMeta . setAttribute ( 'content' , 'top-left-easting=-5388605, top-left-northing=7005413, bottom-right-easting=3895643, bottom-right-northing=-4427255' ) ;
1001+ baseExtent . appendChild ( mapMeta ) ;
1002+
9181003 // Add license link
9191004 const licenseLink = document . createElement ( 'map-link' ) ;
9201005 licenseLink . setAttribute ( 'rel' , 'license' ) ;
921- licenseLink . setAttribute ( 'href' , 'https://www.nrcan.gc. ca/earth-sciences/geography/topographic-information/free-data-geogratis/ licence/17285 ' ) ;
922- licenseLink . setAttribute ( 'title' , 'Canada Base Map © Natural Resources Canada' ) ;
1006+ licenseLink . setAttribute ( 'href' , 'https://open.canada. ca/en/open-government- licence-canada ' ) ;
1007+ licenseLink . setAttribute ( 'title' , 'Open Government Licence - Canada' ) ;
9231008 baseExtent . appendChild ( licenseLink ) ;
9241009
9251010 // Add zoom input
@@ -1003,7 +1088,7 @@ function createViewerForLayer(index, layer, version, selectedFormat, queryEnable
10031088 }
10041089
10051090 // Add the layer to this viewer
1006- addLayerToViewer ( viewer , layer , version , selectedFormat , queryEnabled , selectedStyle , imgFormat , index ) ;
1091+ addLayerToViewer ( viewer , layer , version , selectedFormat , queryEnabled , selectedStyle , imgFormat , index , includeBounds ) ;
10071092
10081093 // Add to container
10091094 container . appendChild ( viewer ) ;
@@ -1020,7 +1105,7 @@ function removeViewerForLayer(index) {
10201105 console . log ( 'Removed viewer for layer index:' , index ) ;
10211106}
10221107
1023- function addLayerToViewer ( viewer , layer , version , selectedFormat , queryEnabled , selectedStyle , imageFormat , layerIndex ) {
1108+ function addLayerToViewer ( viewer , layer , version , selectedFormat , queryEnabled , selectedStyle , imageFormat , layerIndex , boundsEnabled ) {
10241109 const viewerProjection = viewer . getAttribute ( 'projection' ) || 'OSMTILE' ;
10251110 const { bbox } = layer ;
10261111
@@ -1053,21 +1138,49 @@ function addLayerToViewer(viewer, layer, version, selectedFormat, queryEnabled,
10531138
10541139 // Default to image/png if not specified
10551140 const imgFormat = imageFormat || 'image/png' ;
1141+
1142+ // Default to true if not specified
1143+ const includeBounds = boundsEnabled !== undefined ? boundsEnabled : true ;
10561144
1057- // Transform bbox if using Web Mercator
1058- const useWebMercator = viewerProjection === 'OSMTILE' ;
1059- let transformedBbox ;
1060- if ( useWebMercator ) {
1061- const minCoords = wgs84ToWebMercator ( parseFloat ( bbox . minx ) , parseFloat ( bbox . miny ) ) ;
1062- const maxCoords = wgs84ToWebMercator ( parseFloat ( bbox . maxx ) , parseFloat ( bbox . maxy ) ) ;
1063- transformedBbox = {
1064- minx : minCoords . x . toString ( ) ,
1065- miny : minCoords . y . toString ( ) ,
1066- maxx : maxCoords . x . toString ( ) ,
1067- maxy : maxCoords . y . toString ( ) ,
1068- } ;
1069- } else {
1070- transformedBbox = bbox ;
1145+ // Determine which bounding box to use based on projection
1146+ let extentBbox = null ;
1147+ let extentCRS = null ;
1148+
1149+ // Check for projection-specific BoundingBox elements
1150+ if ( layer . boundingBoxes ) {
1151+ if ( viewerProjection === 'OSMTILE' && ( layer . boundingBoxes [ 'EPSG:3857' ] || layer . boundingBoxes [ 'MapML:OSMTILE' ] ) ) {
1152+ extentBbox = layer . boundingBoxes [ 'EPSG:3857' ] || layer . boundingBoxes [ 'MapML:OSMTILE' ] ;
1153+ extentCRS = 'EPSG:3857' ;
1154+ } else if ( viewerProjection === 'CBMTILE' && ( layer . boundingBoxes [ 'EPSG:3978' ] || layer . boundingBoxes [ 'MapML:CBMTILE' ] ) ) {
1155+ extentBbox = layer . boundingBoxes [ 'EPSG:3978' ] || layer . boundingBoxes [ 'MapML:CBMTILE' ] ;
1156+ extentCRS = 'EPSG:3978' ;
1157+ } else if ( viewerProjection === 'WGS84' && ( layer . boundingBoxes [ 'EPSG:4326' ] || layer . boundingBoxes [ 'CRS:84' ] || layer . boundingBoxes [ 'MapML:WGS84' ] ) ) {
1158+ extentBbox = layer . boundingBoxes [ 'EPSG:4326' ] || layer . boundingBoxes [ 'CRS:84' ] || layer . boundingBoxes [ 'MapML:WGS84' ] ;
1159+ extentCRS = 'EPSG:4326' ;
1160+ } else if ( viewerProjection === 'APSTILE' && ( layer . boundingBoxes [ 'EPSG:5936' ] || layer . boundingBoxes [ 'MapML:APSTILE' ] ) ) {
1161+ extentBbox = layer . boundingBoxes [ 'EPSG:5936' ] || layer . boundingBoxes [ 'MapML:APSTILE' ] ;
1162+ extentCRS = 'EPSG:5936' ;
1163+ }
1164+ }
1165+
1166+ // Fall back to transforming EX_GeographicBoundingBox if no projection-specific bbox found
1167+ if ( ! extentBbox ) {
1168+ if ( viewerProjection === 'OSMTILE' ) {
1169+ // Transform WGS84 to Web Mercator
1170+ const minCoords = wgs84ToWebMercator ( parseFloat ( bbox . minx ) , parseFloat ( bbox . miny ) ) ;
1171+ const maxCoords = wgs84ToWebMercator ( parseFloat ( bbox . maxx ) , parseFloat ( bbox . maxy ) ) ;
1172+ extentBbox = {
1173+ minx : minCoords . x . toString ( ) ,
1174+ miny : minCoords . y . toString ( ) ,
1175+ maxx : maxCoords . x . toString ( ) ,
1176+ maxy : maxCoords . y . toString ( ) ,
1177+ } ;
1178+ extentCRS = 'EPSG:3857' ;
1179+ } else {
1180+ // Use geographic bbox as-is
1181+ extentBbox = bbox ;
1182+ extentCRS = 'EPSG:4326' ;
1183+ }
10711184 }
10721185
10731186 // Create map-layer element
@@ -1076,11 +1189,24 @@ function addLayerToViewer(viewer, layer, version, selectedFormat, queryEnabled,
10761189 mapLayer . setAttribute ( 'checked' , '' ) ;
10771190 mapLayer . setAttribute ( 'data-wms-layer' , layer . name ) ;
10781191
1079- // Add map-meta extent using geographic bounding box (WGS84)
1080- const mapMeta = document . createElement ( 'map-meta' ) ;
1081- mapMeta . setAttribute ( 'name' , 'extent' ) ;
1082- mapMeta . setAttribute ( 'content' , `top-left-longitude=${ bbox . minx } , top-left-latitude=${ bbox . maxy } , bottom-right-longitude=${ bbox . maxx } , bottom-right-latitude=${ bbox . miny } ` ) ;
1083- mapLayer . appendChild ( mapMeta ) ;
1192+ // Add map-meta extent if enabled
1193+ if ( includeBounds && extentBbox ) {
1194+ const mapMeta = document . createElement ( 'map-meta' ) ;
1195+ mapMeta . setAttribute ( 'name' , 'extent' ) ;
1196+
1197+ // Use appropriate coordinate names based on CRS
1198+ let content ;
1199+ if ( extentCRS === 'EPSG:4326' || extentCRS === 'CRS:84' ) {
1200+ // Geographic coordinates (longitude/latitude)
1201+ content = `top-left-longitude=${ extentBbox . minx } , top-left-latitude=${ extentBbox . maxy } , bottom-right-longitude=${ extentBbox . maxx } , bottom-right-latitude=${ extentBbox . miny } ` ;
1202+ } else {
1203+ // Projected coordinates (easting/northing)
1204+ content = `top-left-easting=${ extentBbox . minx } , top-left-northing=${ extentBbox . maxy } , bottom-right-easting=${ extentBbox . maxx } , bottom-right-northing=${ extentBbox . miny } ` ;
1205+ }
1206+
1207+ mapMeta . setAttribute ( 'content' , content ) ;
1208+ mapLayer . appendChild ( mapMeta ) ;
1209+ }
10841210
10851211 // Add license link if available (before map-extent)
10861212 if ( layer . licenseUrl ) {
@@ -1232,7 +1358,8 @@ function addLayerToViewer(viewer, layer, version, selectedFormat, queryEnabled,
12321358 const isDimensionEnabled = ! dimensionCheckbox || dimensionCheckbox . checked ;
12331359
12341360 if ( isDimensionEnabled ) {
1235- tref += `&${ dimension . name } ={${ dimension . name } }` ;
1361+ const paramName = formatDimensionParam ( dimension . name ) ;
1362+ tref += `&${ paramName } ={${ dimension . name } }` ;
12361363 }
12371364 } ) ;
12381365 }
0 commit comments