diff --git a/index.html b/index.html index 0403a1d04..75005964b 100644 --- a/index.html +++ b/index.html @@ -4,8 +4,10 @@ - - + + Origo exempel diff --git a/package-lock.json b/package-lock.json index c8e97eadf..d670ad46b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3459,9 +3459,9 @@ } }, "dom-to-image-more": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.9.1.tgz", - "integrity": "sha512-yKNHXkJZJhwdJ+D112qi9rbA8jw7ENAe1zxdwE0pTCF039yWBT8gZ229cC2ic/zsPBp3ODvBc7ft4vDfX5tEkw==" + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/dom-to-image-more/-/dom-to-image-more-2.8.0.tgz", + "integrity": "sha512-YqlHI1i+TMuaKwkFRO5oDPjC3eWf+6Hln9rHZcnFYvmoXwCrGZmZ7BYXBJOjw5utYg2Lp+QF9YO96F7CsDC4eQ==" }, "domain-browser": { "version": "1.2.0", diff --git a/package.json b/package.json index f29c004c9..d41435fb1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "copyfiles": "^2.4.1", "core-js": "^3.8.2", "cuid": "^2.1.4", - "dom-to-image-more": "^2.8.0", + "dom-to-image-more": "2.8.0", "downloadjs": "^1.4.7", "elm-pep": "^1.0.6", "html2canvas": "^1.0.0-rc.7", diff --git a/src/controls/print/index.js b/src/controls/print/index.js index e5c72b95b..ca1854ad4 100644 --- a/src/controls/print/index.js +++ b/src/controls/print/index.js @@ -47,7 +47,8 @@ const Print = function Print(options = {}) { rotationStep = 1, leftFooterText = '', filename, - mapInteractionsActive = false + mapInteractionsActive = false, + supressResolutionsRecalculation = false } = options; let { showNorthArrow = true @@ -107,7 +108,8 @@ const Print = function Print(options = {}) { rotation, rotationStep, leftFooterText, - mapInteractionsActive + mapInteractionsActive, + supressResolutionsRecalculation }); if (placement.indexOf('screen') > -1) { mapTools = `${viewer.getMain().getMapTools().getId()}`; diff --git a/src/controls/print/print-component.js b/src/controls/print/print-component.js index 97ef0196d..144c4fb1a 100644 --- a/src/controls/print/print-component.js +++ b/src/controls/print/print-component.js @@ -1,6 +1,10 @@ import olAttribution from 'ol/control/Attribution'; import olScaleLine from 'ol/control/ScaleLine'; import { getPointResolution } from 'ol/proj'; +import TileImage from 'ol/source/TileImage'; +import TileWMSSource from 'ol/source/TileWMS'; +import TileGrid from 'ol/tilegrid/TileGrid'; +import { Group } from 'ol/layer'; import { Button, Component, cuid, dom } from '../../ui'; @@ -11,6 +15,7 @@ import PrintInteractionToggle from './print-interaction-toggle'; import PrintToolbar from './print-toolbar'; import { downloadPNG, downloadPDF, printToScalePDF } from '../../utils/download'; import { afterRender, beforeRender } from './download-callback'; +import maputils from '../../maputils'; const PrintComponent = function PrintComponent(options = {}) { const { @@ -34,13 +39,13 @@ const PrintComponent = function PrintComponent(options = {}) { sizeCustomMinWidth, sizeCustomMaxWidth, resolutions, - scales, scaleInitial, createdPrefix, rotation, rotationStep, leftFooterText, - mapInteractionsActive + mapInteractionsActive, + supressResolutionsRecalculation } = options; let { @@ -51,6 +56,7 @@ const PrintComponent = function PrintComponent(options = {}) { size, orientation, resolution, + scales, showMargins, showCreated, showScale, @@ -70,6 +76,111 @@ const PrintComponent = function PrintComponent(options = {}) { let printScale = 0; let widthImage = 0; let heightImage = 0; + const originalResolutions = viewer.getResolutions().map(item => item); + const originalGrids = new Map(); + + if (!Array.isArray(scales) || scales.length === 0) { + scales = originalResolutions.map(currRes => maputils.resolutionToFormattedScale(currRes, viewer.getProjection())); + } + + /** + * Recalulates a resoultions array to reflect dpi changes + * @param {number []} src the array of resolutions + * @returns {number []} A new array with recalculated values + */ + const recalculateResolutionsArray = function recalculateResolutionsArray(src) { + const retval = []; + for (let ix = 0; ix < src.length; ix += 1) { + // Do the calculation the same way as when setting the scale. Otherwise there will be rounding errors and the scale bar will have another scale than selected + // (and probably not an even nice looking number) + const scale = maputils.resolutionToScale(src[ix], viewer.getProjection()) / 1000; + const scaleResolution = scale / getPointResolution(viewer.getProjection(), resolution / 25.4, map.getView().getCenter()); + retval.push(scaleResolution); + } + return retval; + }; + + /** + * Recursively flattens group layers in an array of layers + * @param {any []} layers Array of layers + * @returns {any []} Array of layers with group layers flattened + */ + const flattenLayers = function flattenLayers(layers) { + return layers.reduce((acc, currLayer) => { + if (currLayer instanceof Group) { + // eslint-disable-next-line no-param-reassign + acc = acc.concat(flattenLayers(currLayer.getLayers().getArray())); + } else { + acc.push(currLayer); + } + return acc; + }, []); + }; + + /** Recalculate the grid of tiled layers when resolution has changed as more tiles may be needed to cover the extent */ + const updateTileGrids = function updateTileGrids() { + const layers = flattenLayers(viewer.getLayers()); + for (let i = 0; i < layers.length; i += 1) { + const currLayer = layers[i]; + if (currLayer.getSource() instanceof TileImage) { + const grid = currLayer.getSource().getTileGrid(); + let needNewGrid = false; + // Store original grid if we don't have it already + // Don't always copy as it is only the first time it is original grid + if (!originalGrids.has(currLayer)) { + originalGrids.set(currLayer, grid); + } + + // Deep copy grid so we can exchange it without messing with anything else + const newgridOptions = {}; + // If layer uses the grid from the viewer, it is a shared instance and has been changed when resolutions were recalculated + // as the grid uses a pointer to resolutions + if (grid === viewer.getTileGrid()) { + // No need for deep copy yet + newgridOptions.resolutions = originalResolutions; + needNewGrid = true; + } else { + // No need for deep copy yet + newgridOptions.resolutions = originalGrids.get(currLayer).getResolutions(); + } + + // TileWms is actually not a tile service. So we can create a new dynamic grid to create crisp tiles matching the new + // resolutions, it will just cost a few cache misses on the server. All other tile sources are tiled, so we can't assume that there exist tiles for a "random" resolution + // so the new grid is actually the same as before, but it may have been reverted to the resolutions it had before map resolutions was recalculated + // An alternative (better) solution would have been to deep copy resolutions when making the grid in viewer, but that might upset someone else. + // In theory we could have added a flag to which layers should be recalculated. Someone might have multiple grids to support different resolutions. + if (currLayer.getSource() instanceof TileWMSSource) { + // This is actually a deep copy + newgridOptions.resolutions = recalculateResolutionsArray(newgridOptions.resolutions); + needNewGrid = true; + } + // Would be silly to create a deep clone if it is exactly the same. + if (needNewGrid) { + newgridOptions.extent = grid.getExtent(); + newgridOptions.minZoom = grid.getMinZoom(); + newgridOptions.origin = grid.getOrigin(); + newgridOptions.tileSize = grid.getTileSize(); + const newGrid = new TileGrid(newgridOptions); + // Set our brand new grid on current layer + currLayer.getSource().tileGrid = newGrid; + } + } + } + }; + + /** Recalculate the array of allowed zoomlevels to reflect changes in DPI and updates the view */ + const updateResolutions = function updateResolutions() { + const viewerResolutions = viewer.getResolutions(); + const newResolutions = recalculateResolutionsArray(originalResolutions); + for (let ix = 0; ix < viewerResolutions.length; ix += 1) { + viewerResolutions[ix] = newResolutions[ix]; + } + // As we do a "dirty" update of resolutions we have to trigger a re-read of the limits, otherwise the outer limits still apply. + map.getView().setMinZoom(0); + map.getView().setMaxZoom(viewerResolutions.length - 1); + // Have to recalculate tiles extents as well. + updateTileGrids(); + }; const setCustomSize = function setCustomSize(sizeObj) { if ('width' in sizeObj) { @@ -157,7 +268,8 @@ const PrintComponent = function PrintComponent(options = {}) { showScale, showNorthArrow, rotation, - rotationStep + rotationStep, + viewerResolutions: originalResolutions }); const printInteractionToggle = PrintInteractionToggle({ map, target, mapInteractionsActive, pageSettings: viewer.getViewerOptions().pageSettings }); const printToolbar = PrintToolbar(); @@ -170,6 +282,7 @@ const PrintComponent = function PrintComponent(options = {}) { name: 'printComponent', onInit() { this.on('render', this.onRender); + this.addComponent(printSettings); this.addComponent(printInteractionToggle); this.addComponent(printToolbar); @@ -246,6 +359,10 @@ const PrintComponent = function PrintComponent(options = {}) { }, changeResolution(evt) { resolution = evt.resolution; + if (!supressResolutionsRecalculation) { + updateResolutions(); + } + this.updatePageSize(); if (printScale > 0) { this.changeScale({ scale: printScale }); @@ -276,6 +393,22 @@ const PrintComponent = function PrintComponent(options = {}) { printMapComponent.dispatch('change:toggleNorthArrow', { showNorthArrow }); }, close() { + // Restore scales + if (!supressResolutionsRecalculation) { + const viewerResolutions = viewer.getResolutions(); + for (let ix = 0; ix < viewerResolutions.length; ix += 1) { + viewerResolutions[ix] = originalResolutions[ix]; + } + originalGrids.forEach((value, key) => { + // Sorry, but there is no setter and a map does not allow indexing. + // eslint-disable-next-line no-param-reassign + key.getSource().tileGrid = value; + }); + // As we do a "dirty" update of resolutions we have to trigger a re-read of the limits, otherwise the outer limits still apply. + map.getView().setMinZoom(0); + map.getView().setMaxZoom(viewerResolutions.length - 1); + originalGrids.clear(); + } printMapComponent.removePrintControls(); if (map.getView().getRotation() !== 0) { map.getView().setRotation(0); @@ -354,6 +487,9 @@ const PrintComponent = function PrintComponent(options = {}) { map.setTarget(printMapComponent.getId()); this.removeViewerControls(); printMapComponent.addPrintControls(); + if (!supressResolutionsRecalculation) { + updateResolutions(); + } printMapComponent.dispatch('change:toggleScale', { showScale }); this.updatePageSize(); }, diff --git a/src/controls/print/set-scale-control.js b/src/controls/print/set-scale-control.js index 5368f63c8..7bc0d7df8 100644 --- a/src/controls/print/set-scale-control.js +++ b/src/controls/print/set-scale-control.js @@ -7,12 +7,14 @@ export default function SetScaleControl(options = {}, map) { initialScale } = options; - let projection; - let resolutions; let selectScale; - function getScales() { - return resolutions.map(resolution => mapUtils.resolutionToFormattedScale(resolution, projection)); + /** + * Parses a formatted scale string and returns the denominator as a number + * @param {any} scale + */ + function parseScale(scale) { + return parseInt(scale.replace(/\s+/g, '').split(':').pop(), 10); } return Component({ @@ -26,21 +28,15 @@ export default function SetScaleControl(options = {}, map) { text: 'Välj skala' }); this.addComponents([selectScale]); - projection = map.getView().getProjection(); - resolutions = map.getView().getResolutions(); }, onChangeScale(evt) { - const scaleDenominator = parseInt(evt.replace(/\s+/g, '').split(':').pop(), 10); + const scaleDenominator = parseScale(evt); this.dispatch('change:scale', { scale: scaleDenominator / 1000 }); selectScale.setButtonText(evt); }, onRender() { this.dispatch('render'); - if (Array.isArray(scales) && scales.length) { - selectScale.setItems(scales); - } else { - selectScale.setItems(getScales()); - } + selectScale.setItems(scales); document.getElementById(selectScale.getId()).addEventListener('dropdown:select', (evt) => { this.onChangeScale(evt.target.textContent); }); @@ -48,8 +44,10 @@ export default function SetScaleControl(options = {}, map) { this.onChangeScale(initialScale); } else { const viewResolution = map.getView().getResolution(); - const closest = resolutions.reduce((prev, curr) => (Math.abs(curr - viewResolution) < Math.abs(prev - viewResolution) ? curr : prev)); - this.onChangeScale(mapUtils.resolutionToFormattedScale(closest, projection)); + const projection = map.getView().getProjection(); + const mapScale = mapUtils.resolutionToScale(viewResolution, projection); + const closest = scales.reduce((prev, curr) => (Math.abs(parseScale(curr) - mapScale) < Math.abs(parseScale(prev) - mapScale) ? curr : prev)); + this.onChangeScale(closest); } }, render() { diff --git a/src/layer/wms.js b/src/layer/wms.js index fe61a4c09..976542348 100644 --- a/src/layer/wms.js +++ b/src/layer/wms.js @@ -66,6 +66,7 @@ const wms = function wms(layerOptions, viewer) { sourceOptions.tileGrid = viewer.getTileGrid(); if (wmsOptions.extent) { + // FIXME: there is no "extent" property to set. Code has no effect. Probably must create a new grid from viewer.getTileGridSettings . sourceOptions.tileGrid.extent = wmsOptions.extent; } } diff --git a/src/maputils.js b/src/maputils.js index da9ba84ac..699e73c58 100644 --- a/src/maputils.js +++ b/src/maputils.js @@ -3,10 +3,14 @@ import TileGrid from 'ol/tilegrid/TileGrid'; import Feature from 'ol/Feature'; import Point from 'ol/geom/Point'; import Vector from 'ol/source/Vector'; +import VectorLayer from 'ol/layer/Vector'; +import Overlay from 'ol/Overlay'; import GeoJSON from 'ol/format/GeoJSON'; import { getTopLeft, getBottomLeft } from 'ol/extent'; import WKT from 'ol/format/WKT'; import numberFormatter from './utils/numberformatter'; +import Style from './style'; +import Popup from './popup'; const maputils = { isWithinVisibleScales: function isWithinVisibleScales(scale, maxScale, minScale) { @@ -130,6 +134,62 @@ const maputils = { scaleValue += (10 - differens); } return scaleValue; + }, + createMarker: function createMarker(coordinates, title, content, viewer) { + const featureStyles = { + stroke: { + color: [100, 149, 237, 1], + width: 4, + lineDash: null + }, + fill: { + color: [100, 149, 237, 0.2] + }, + circle: { + radius: 7, + stroke: { + color: [100, 149, 237, 1], + width: 2 + }, + fill: { + color: [255, 255, 255, 1] + } + } + }; + const vectorStyles = Style.createStyleRule(featureStyles); + const layer = new VectorLayer({ + source: new Vector({ + features: [ + new Feature({ + geometry: new Point(coordinates) + }) + ] + }), + style: vectorStyles + }); + const popup = Popup(`#${viewer.getId()}`); + popup.setContent({ + content, + title + }); + popup.setTitle(title); + popup.setVisibility(true); + const popupEl = popup.getEl(); + const popupHeight = document.querySelector('.o-popup').offsetHeight + 10; + popupEl.style.height = `${popupHeight}px`; + const overlay = new Overlay({ + element: popupEl, + autoPan: { + margin: 55, + animation: { + duration: 500 + } + }, + positioning: 'bottom-center' + }); + viewer.getMap().addOverlay(overlay); + overlay.setPosition(coordinates); + return layer; } }; diff --git a/src/viewer.js b/src/viewer.js index 1deb96b15..dc6bf040e 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -288,12 +288,12 @@ const Viewer = function Viewer(targetOption, options = {}) { if (capabilitiesLayers && Object.keys(capabilitiesLayers).length > 0) { return layerlist.map(layer => { let secure; + let layername = layer.name; + // remove workspace if syntax is workspace:layername + layername = layername.split(':').pop(); // remove double underscore plus a suffix from layer name - let layername = ''; - if (layer.name.includes('__')) { - layername = layer.name.substring(0, layer.name.lastIndexOf('__')); - } else { - layername = layer.name; + if (layername.includes('__')) { + layername = layername.substring(0, layername.lastIndexOf('__')); } const layerSourceOptions = layer.source ? getSource2(layer.source) : undefined; if (layerSourceOptions && layerSourceOptions.capabilitiesURL) { @@ -440,6 +440,11 @@ const Viewer = function Viewer(targetOption, options = {}) { } }; + const addMarker = function addMarker(coordinates, title, content) { + const layer = maputils.createMarker(coordinates, title, content, this); + map.addLayer(layer); + }; + const getUrlParams = function getUrlParams() { return urlParams; }; @@ -556,6 +561,7 @@ const Viewer = function Viewer(targetOption, options = {}) { addLayers, addSource, addStyle, + addMarker, getBreakPoints, getCenter, getClusterOptions,