Skip to content

Commit 31f461b

Browse files
author
Peter Rushforth
committed
Change tref string for custom dimensionns to include 'DIM_' per WMS spec.
Add bounds checkbox and listener Hardcode bounds for CBMTILE basemap layer Use <BoundingBox> element for CBMTILE, OSMTILE bpunds when available, otherwise use default min/max latitude and longitude (which doesn't work well for non-orthogonal projections)
1 parent e82cfa6 commit 31f461b

2 files changed

Lines changed: 184 additions & 33 deletions

File tree

src/script/main.js

Lines changed: 160 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22

33
const 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)
619
function 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
}

src/style/main.css

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,30 @@ button:active {
190190
cursor: pointer;
191191
}
192192

193+
.bounds-selector {
194+
display: flex;
195+
align-items: center;
196+
gap: 0.5rem;
197+
margin-bottom: 0.5rem;
198+
font-size: 0.85rem;
199+
justify-content: flex-start;
200+
}
201+
202+
.bounds-checkbox {
203+
width: 16px;
204+
height: 16px;
205+
cursor: pointer;
206+
margin: 0;
207+
}
208+
209+
.bounds-label {
210+
font-size: 0.85rem;
211+
font-weight: normal;
212+
color: #666;
213+
cursor: pointer;
214+
margin: 0;
215+
}
216+
193217
.query-format-selector {
194218
display: flex;
195219
align-items: center;

0 commit comments

Comments
 (0)