From 89581d23c847210b9b3c1591b044e329434128e9 Mon Sep 17 00:00:00 2001 From: Benjamin Gerber Date: Mon, 7 Oct 2024 20:46:20 +0200 Subject: [PATCH] Remove offline --- DEVELOPING.md | 2 +- src/offline/AbstractLocalforageWrapper.js | 147 ------ src/offline/Configuration.js | 398 --------------- src/offline/Downloader.js | 196 -------- src/offline/LocalforageAndroidWrapper.js | 53 -- src/offline/LocalforageCordovaWrapper.js | 39 -- src/offline/LocalforageIosWrapper.js | 66 --- src/offline/Mask.js | 109 ---- src/offline/Mode.js | 107 ---- src/offline/NetworkStatus.js | 224 --------- src/offline/Restorer.js | 79 --- src/offline/SerializerDeserializer.js | 271 ---------- src/offline/ServiceManager.js | 134 ----- src/offline/TilesDownloader.js | 223 --------- src/offline/component.html.js | 182 ------- src/offline/component.js | 578 ---------------------- src/offline/index.js | 81 --- src/offline/module.js | 44 -- src/offline/utils.js | 53 -- src/options.js | 6 - srcapi/store/config.ts | 6 - 21 files changed, 1 insertion(+), 2997 deletions(-) delete mode 100644 src/offline/AbstractLocalforageWrapper.js delete mode 100644 src/offline/Configuration.js delete mode 100644 src/offline/Downloader.js delete mode 100644 src/offline/LocalforageAndroidWrapper.js delete mode 100644 src/offline/LocalforageCordovaWrapper.js delete mode 100644 src/offline/LocalforageIosWrapper.js delete mode 100644 src/offline/Mask.js delete mode 100644 src/offline/Mode.js delete mode 100644 src/offline/NetworkStatus.js delete mode 100644 src/offline/Restorer.js delete mode 100644 src/offline/SerializerDeserializer.js delete mode 100644 src/offline/ServiceManager.js delete mode 100644 src/offline/TilesDownloader.js delete mode 100644 src/offline/component.html.js delete mode 100644 src/offline/component.js delete mode 100644 src/offline/index.js delete mode 100644 src/offline/module.js delete mode 100644 src/offline/utils.js diff --git a/DEVELOPING.md b/DEVELOPING.md index f791ce76b1b7..1e4bb376ed56 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -9,7 +9,7 @@ make serve-ngeo Use the _ONE_EXAMPLE_ environment variable to build (and rebuild on each change) only one example. ``` -ONE_EXAMPLE=offline make serve-ngeo +ONE_EXAMPLE=simple make serve-ngeo ``` The ngeo examples are now available on your https://localhost:3000/examples/. diff --git a/src/offline/AbstractLocalforageWrapper.js b/src/offline/AbstractLocalforageWrapper.js deleted file mode 100644 index 6082cbd821e8..000000000000 --- a/src/offline/AbstractLocalforageWrapper.js +++ /dev/null @@ -1,147 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -/** - * @typedef {Object} Action - * @property {number} id - * @property {string} plugin - * @property {string} command - * @property {!*[]} args - * @property {?} context - * @property {string} [msg] - */ - -/** - * @abstract - */ -const exports = class AbstractLocalforageWrapper { - constructor() { - this.waitingPromises_ = new Map(); - this.currentId_ = 0; - } - - /** - * @param {...unknown} args Some arguments. - * @returns {Promise} - */ - setItem(...args) { - return this.createAction('setItem', ...args); - } - - /** - * @param {...unknown} args Some arguments. - * @returns {Promise} - */ - getItem(...args) { - return this.createAction('getItem', ...args); - } - - /** - * @returns {Promise} - */ - clear() { - return this.createAction('clear'); - } - - /** - * @param {...unknown} args Some arguments. - * @returns {Promise} - */ - config(...args) { - return this.createAction('config', ...args); - } - - /** - * @export - * @param {string} command . - * @param {...unknown} args . - * @returns {Promise} . - */ - createAction(command, ...args) { - const id = ++this.currentId_; - /** - * @type {Action} - */ - const action = { - plugin: 'localforage', - command: command, - args: args, - id: id, - context: null, - }; - const waitingPromise = { - /** - * @param {any} _any - */ - resolve(_any) {}, - /** - * @param {any} _any - */ - reject(_any) {}, - }; - const promise = new Promise((resolve, reject) => { - waitingPromise.resolve = resolve; - waitingPromise.reject = reject; - }); - this.waitingPromises_.set(id, waitingPromise); - this.postToBackend(action); - return promise; - } - - /** - * @export - * @param {*} event . - */ - receiveMessage(event) { - /** - * @type {Action} - */ - const action = event.data; - const id = action.id; - const command = action.command; - const args = action.args || []; - const context = action.context; - const msg = action.msg; - - const waitingPromise = this.waitingPromises_.get(id); - if (command === 'error') { - console.error(msg, args, context); - if (waitingPromise) { - waitingPromise.reject(args, context); - this.waitingPromises_.delete(id); - } - } else if (command === 'response') { - waitingPromise.resolve(...args); - this.waitingPromises_.delete(id); - } else { - console.error('Unhandled command', JSON.stringify(action, null, '\t')); - } - } - - /** - * @abstract - * @protected - * @param {Action} action . - */ - postToBackend(action) {} -}; - -export default exports; diff --git a/src/offline/Configuration.js b/src/offline/Configuration.js deleted file mode 100644 index f27709e45805..000000000000 --- a/src/offline/Configuration.js +++ /dev/null @@ -1,398 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import olObservable from 'ol/Observable'; -import olLayerLayer from 'ol/layer/Layer'; -import olLayerVector from 'ol/layer/Vector'; -import olLayerTile from 'ol/layer/WebGLTile'; -import olLayerImage from 'ol/layer/Image'; -import * as olProj from 'ol/proj'; -import {defaultImageLoadFunction} from 'ol/source/Image'; -import olSourceImageWMS from 'ol/source/ImageWMS'; -import olSourceTileWMS from 'ol/source/TileWMS'; -import {createForProjection as createTileGridForProjection} from 'ol/tilegrid'; -import SerializerDeserializer from 'ngeo/offline/SerializerDeserializer'; -import LocalforageCordovaWrapper from 'ngeo/offline/LocalforageCordovaWrapper'; -import LocalforageAndroidWrapper from 'ngeo/offline/LocalforageAndroidWrapper'; -import LocalforageIosWrapper from 'ngeo/offline/LocalforageIosWrapper'; -import ngeoCustomEvent from 'ngeo/CustomEvent'; -import {normalizeURL, traverseLayer} from 'ngeo/offline/utils'; -// @ts-ignore -import localforage from 'localforage/src/localforage'; - -/** - * implements {import('ngeo/offline/index').OfflineOnTileDownload} - */ -export default class _ngInjectAnonymousClass extends olObservable { - /** - * @param {!angular.IScope} $rootScope The rootScope provider. - * @param {!import('ngeo/map/BackgroundLayerMgr').MapBackgroundLayerManager} ngeoBackgroundLayerMgr - * Background layer manager. - * @param {number} ngeoOfflineGutter A gutter around the tiles to download (to avoid cut symbols) - */ - constructor($rootScope, ngeoBackgroundLayerMgr, ngeoOfflineGutter) { - super(); - this.localforage_ = this.createLocalforage(); - this.configureLocalforage(); - - /** - * @private - * @type {!angular.IScope} - */ - this.rootScope_ = $rootScope; - - /** - * @protected - * @type {boolean} - */ - this.hasData = false; - this.initializeHasOfflineData(); - - /** - * @private - * @type {!import('ngeo/map/BackgroundLayerMgr').MapBackgroundLayerManager} - */ - this.ngeoBackgroundLayerMgr_ = ngeoBackgroundLayerMgr; - - /** - * @private - * @type {SerializerDeserializer} - */ - // @ts-ignore - this.serDes_ = new SerializerDeserializer({ - gutter: ngeoOfflineGutter, - }); - - /** - * @private - * @type {number} - */ - this.gutter_ = ngeoOfflineGutter; - } - - /** - * @private - * @param {number} progress new progress. - */ - dispatchProgress_(progress) { - this.dispatchEvent( - new ngeoCustomEvent('progress', { - 'progress': progress, - }), - ); - } - - /** - * @protected - */ - initializeHasOfflineData() { - this.getItem('offline_content').then((value) => this.setHasOfflineData(!!value)); - } - - /** - * @export - * @returns {boolean} whether some offline data is available in the storage - */ - hasOfflineData() { - return this.hasData; - } - - /** - * @param {boolean} value whether there is offline data available in the storage. - */ - setHasOfflineData(value) { - const needDigest = value !== this.hasData; - this.hasData = value; - if (needDigest) { - this.rootScope_.$applyAsync(); // force update of the UI - } - } - - /** - * Hook to allow measuring get/set item performance. - * - * @param {string} msg A message - * @param {string} key The key to work on - * @param {Promise} promise A promise - * @returns {Promise} The promise we passed - */ - traceGetSetItem(msg, key, promise) { - return promise; - } - createLocalforage() { - if (location.search.includes('localforage=cordova')) { - console.log('Using cordova localforage'); - return new LocalforageCordovaWrapper(); - } else if (location.search.includes('localforage=android')) { - console.log('Using android localforage'); - return new LocalforageAndroidWrapper(); - } else if (location.search.includes('localforage=ios')) { - console.log('Using ios localforage'); - return new LocalforageIosWrapper(); - } - return localforage; - } - configureLocalforage() { - this.localforage_.config({ - 'name': 'ngeoOfflineStorage', - 'version': 1.0, - 'storeName': 'offlineStorage', - }); - } - - /** - * @param {string} key The key - * @returns {Promise} A promise - */ - getItem(key) { - const promise = this.localforage_['getItem'](key); - return this.traceGetSetItem('getItem', key, promise); - } - - /** - * @param {string} key . - * @returns {Promise} . - */ - removeItem(key) { - const promise = this.localforage_['removeItem'](key); - return this.traceGetSetItem('removeItem', key, promise); - } - - /** - * @param {string} key The key - * @param {*} value A value - * @returns {Promise} A promise - */ - setItem(key, value) { - const promise = this.localforage_['setItem'](key, value); - return this.traceGetSetItem('setItem', key, promise); - } - - /** - * @returns {Promise} A promise - */ - clear() { - this.setHasOfflineData(false); - const promise = this.localforage_.clear(); - return this.traceGetSetItem('clear', '', promise); - } - - /** - * @param {!import('ol/Map').default} map A map - * @returns {number} An "estimation" of the size of the data to download - */ - estimateLoadDataSize(map) { - return 50; - } - - /** - * @param {import('./index').OfflineLayerMetadata} layerItem The layer metadata - * @returns {string} A key identifying an offline layer and used during restore. - */ - getLayerKey(layerItem) { - return /** @type {string} */ layerItem.layer.get('label'); - } - - /** - * @param {number} progress The download progress - * @param {import('./index').OfflineTile} tile The tile - * @returns {Promise} A promise - */ - onTileDownloadSuccess(progress, tile) { - this.dispatchProgress_(progress); - if (tile.response) { - return this.setItem(normalizeURL(tile.url), tile.response); - } - return Promise.resolve(); - } - - /** - * @param {number} progress The progress - * @returns {Promise} A promise - */ - onTileDownloadError(progress) { - this.dispatchProgress_(progress); - return Promise.resolve(); - } - - /** - * @param {import('ol/Map').default} map A map - * @param {import('ol/layer/Layer').default} layer A layer - * @param {import('ol/layer/Group').default[]} ancestors The ancestors of that layer - * @param {import('ol/extent').Extent} userExtent The extent selected by the user. - * @returns {import('./index').OfflineExtentByZoom[]} The extent to download per zoom level - */ - getExtentByZoom(map, layer, ancestors, userExtent) { - const currentZoom = map.getView().getZoom(); - if (currentZoom === undefined) { - throw new Error('Missing currentZoom'); - } - /** - * @type {import('./index').OfflineExtentByZoom[]} - */ - const results = []; - [0, 1, 2, 3, 4].forEach((dz) => { - results.push({ - zoom: currentZoom + dz, - extent: userExtent, - }); - }); - return results; - } - - /** - * @protected - * @param {import('ol/source/Source').default} source An ImageWMS source - * @param {!import('ol/proj/Projection').default} projection The projection - * @returns {import('ol/source/Source').default} A tiled equivalent source - */ - sourceImageWMSToTileWMS(source, projection) { - if ( - source instanceof olSourceImageWMS && - source.getUrl() && - source.getImageLoadFunction() === defaultImageLoadFunction - ) { - const tileGrid = createTileGridForProjection(source.getProjection() || projection, 42, 256); - const attributions = source.getAttributions() || ''; - const url = source.getUrl(); - if (!url || !attributions) { - throw new Error('Invalid values'); - } - source = new olSourceTileWMS({ - gutter: this.gutter_, - url, - tileGrid, - attributions, - projection: source.getProjection(), - params: source.getParams(), - }); - } - return source; - } - - /** - * @param {import('ol/Map').default} map The map to work on. - * @param {import('ol/extent').Extent} userExtent The extent selected by the user. - * @returns {!import('./index').OfflineLayerMetadata[]} the downloadable layers and metadata. - */ - createLayerMetadatas(map, userExtent) { - /** - * @type {import('./index').OfflineLayerMetadata[]} - */ - const layersItems = []; - - /** - * @param {import('ol/layer/Base').default} layer . - * @param {import('ol/layer/Group').default[]} ancestors . - * @returns {boolean} whether to traverse this layer children. - */ - const visitLayer = (layer, ancestors) => { - if (layer instanceof olLayerLayer) { - const extentByZoom = this.getExtentByZoom(map, layer, ancestors, userExtent); - const projection = olProj.get(map.getView().getProjection()); - const source = this.sourceImageWMSToTileWMS(layer.getSource(), projection); - /** - * @type {string|undefined} - */ - let layerType; - /** - * @type {string|undefined} - */ - let layerSerialization; - if (layer instanceof olLayerTile || layer instanceof olLayerImage) { - layerType = 'tile'; - // @ts-ignore - layerSerialization = this.serDes_.serializeTileLayer(layer, source); - } else if (layer instanceof olLayerVector) { - layerType = 'vector'; - } - const backgroundLayer = this.ngeoBackgroundLayerMgr_.get(map) === layer; - layersItems.push({ - backgroundLayer, - map, - extentByZoom, - layerType, - layerSerialization, - layer, - source, - ancestors, - }); - } - return true; - }; - map.getLayers().forEach((root) => { - traverseLayer(root, [], visitLayer); - }); - return layersItems; - } - - /** - * @private - * @param {import('./index').OfflinePersistentLayer} offlineLayer The offline layer - * @returns {function(import('ol/ImageTile').default, string)} the tile function - */ - createTileLoadFunction_(offlineLayer) { - /** - * Load the tile from persistent storage. - * - * @param {import('ol/ImageTile').default} imageTile The image tile - * @param {string} src The tile URL - */ - const tileLoadFunction = (imageTile, src) => { - this.getItem(normalizeURL(src)).then((content) => { - if (!content) { - // use a transparent 1x1 image to make the map consistent - /* eslint-disable-next-line */ - content = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; - } - /** @type {HTMLImageElement} */ - imageTile.getImage().src = content; - }); - }; - return tileLoadFunction; - } - - /** - * @param {import('./index').OfflinePersistentLayer} offlineLayer The layer to recreate - * @returns {?import('ol/layer/Layer').default} the layer. - */ - recreateOfflineLayer(offlineLayer) { - if (offlineLayer.layerType === 'tile') { - const serialization = offlineLayer.layerSerialization; - if (serialization) { - const tileLoadFunction = this.createTileLoadFunction_(offlineLayer); - // @ts-ignore - const layer = this.serDes_.deserializeTileLayer(serialization, tileLoadFunction); - return layer; - } - } - return null; - } - - /** - * @returns {number} The number - */ - getMaxNumberOfParallelDownloads() { - return 11; - } -} -_ngInjectAnonymousClass.$inject = ['$rootScope', 'ngeoBackgroundLayerMgr', 'ngeoOfflineGutter']; diff --git a/src/offline/Downloader.js b/src/offline/Downloader.js deleted file mode 100644 index c0d01bcbca65..000000000000 --- a/src/offline/Downloader.js +++ /dev/null @@ -1,196 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import {DEVICE_PIXEL_RATIO} from 'ol/has'; -import olSourceTileWMS from 'ol/source/TileWMS'; -import olSourceWMTS from 'ol/source/WMTS'; -import TilesDownloader from 'ngeo/offline/TilesDownloader'; -import angular from 'angular'; - -/** - * @param {import('ol/coordinate').Coordinate} a Some coordinates. - * @param {import('ol/coordinate').Coordinate} b Some other coordinates. - * @returns {number} The squared magnitude. - */ -function magnitude2(a, b) { - let magnitudeSquared = 0; - for (let i = 0; i < a.length; ++i) { - magnitudeSquared += Math.pow(a[i] - b[i], 2); - } - return magnitudeSquared; -} -const Downloader = class { - /** - * @param {import('ngeo/offline/Configuration').default} ngeoOfflineConfiguration - * A service for customizing offline behavior. - */ - constructor(ngeoOfflineConfiguration) { - /** - * @private - * @type {import('ngeo/offline/Configuration').default} - */ - this.configuration_ = ngeoOfflineConfiguration; - - /** - * @type {?TilesDownloader} - * @private - */ - this.tileDownloader_ = null; - } - cancel() { - if (this.tileDownloader_) { - this.tileDownloader_.cancel(); - } - } - - /** - * @param {import('./index').OfflineLayerMetadata} layerMetadata Layers metadata. - * @param {import('./index').OfflineTile[]} queue Queue of tiles to download. - */ - queueLayerTiles_(layerMetadata, queue) { - const source = /** @type {olSourceTileWMS|olSourceWMTS} */ layerMetadata.source; - const {map, extentByZoom} = layerMetadata; - if (!source) { - return; - } - console.assert(source instanceof olSourceTileWMS || source instanceof olSourceWMTS); - const projection = map.getView().getProjection(); - const tileGrid = source.getTileGrid(); - const tileUrlFunction = source.getTileUrlFunction(); - console.assert(extentByZoom); - for (const extentZoom of extentByZoom) { - const z = extentZoom.zoom; - const extent = extentZoom.extent; - /** - * @type {import('./index').OfflineTile[]} - */ - const queueByZ = []; - /** - * @type {number} - */ - let minX; - /** - * @type {number} - */ - let minY; - /** - * @type {number} - */ - let maxX; - /** - * @type {number} - */ - let maxY; - tileGrid.forEachTileCoord(extent, z, (coord) => { - maxX = coord[1]; - maxY = coord[2]; - if (minX === undefined || minY === undefined) { - minX = coord[1]; - minY = coord[2]; - } - const url = tileUrlFunction(coord, DEVICE_PIXEL_RATIO, projection); - console.assert(url); - if (url) { - /** - * @type {import('./index').OfflineTile} - */ - const tile = { - coord, - url, - response: null, - }; - queueByZ.push(tile); - } - }); - const centerTileCoord = [z, (minX + maxX) / 2, (minY + maxY) / 2]; - queueByZ.sort((a, b) => magnitude2(a.coord, centerTileCoord) - magnitude2(b.coord, centerTileCoord)); - queue.push(...queueByZ); - } - } - - /** - * @param {import('ol/extent').Extent} extent The extent to download. - * @param {import('ol/Map').default} map The map to work on. - * @returns {Promise<*>} A promise resolving when save is finished. - */ - save(extent, map) { - /** - * @type {!import('./index').OfflineLayerMetadata[]} - */ - const layersMetadatas = this.configuration_.createLayerMetadatas(map, extent); - - /** - * @type {!import('./index').OfflinePersistentLayer[]} - */ - const persistentLayers = []; - /** - * @type {import('./index').OfflineTile[]} - */ - const queue = []; - /** - * @type {number[]} - */ - const zooms = []; - for (const layerItem of layersMetadatas) { - if (layerItem.layerType === 'tile') { - /** - * @type {import('./index').OfflineTile[]} - */ - const tiles = []; - this.queueLayerTiles_(layerItem, tiles); - queue.push(...tiles); - } - persistentLayers.push({ - backgroundLayer: layerItem.backgroundLayer, - layerType: layerItem.layerType, - layerSerialization: layerItem.layerSerialization, - key: this.configuration_.getLayerKey(layerItem), - }); - layerItem.extentByZoom.forEach((obj) => { - const zoom = obj.zoom; - if (!zooms.includes(zoom)) { - zooms.push(zoom); - } - }); - } - - /** - * @type {import('./index').OfflinePersistentContent} - */ - const persistentObject = { - extent: extent, - layers: persistentLayers, - zooms: zooms.sort((a, b) => (a < b ? -1 : 1)), - }; - const setOfflineContentPromise = this.configuration_.setItem('offline_content', persistentObject); - const maxDownloads = this.configuration_.getMaxNumberOfParallelDownloads(); - this.tileDownloader_ = new TilesDownloader(queue, this.configuration_, maxDownloads); - const tileDownloadPromise = this.tileDownloader_.download(); - const allPromise = Promise.all([setOfflineContentPromise, tileDownloadPromise]); - const setHasOfflineData = () => this.configuration_.setHasOfflineData(true); - allPromise.then(setHasOfflineData, setHasOfflineData); - return allPromise; - } -}; -const name = 'offlineDownloader'; -Downloader.module = angular.module(name, []).service(name, Downloader); -const exports = Downloader; -export default exports; diff --git a/src/offline/LocalforageAndroidWrapper.js b/src/offline/LocalforageAndroidWrapper.js deleted file mode 100644 index 626480c9c95b..000000000000 --- a/src/offline/LocalforageAndroidWrapper.js +++ /dev/null @@ -1,53 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import AbstractWrapper from 'ngeo/offline/AbstractLocalforageWrapper'; - -const exports = class AndroidWrapper extends AbstractWrapper { - constructor() { - super(); - // @ts-ignore - window.androidWrapper = this; - } - - /** - * @param {unknown} action - * @override - */ - postToBackend(action) { - const stringified = JSON.stringify(action); - // @ts-ignore - window.ngeoHost.postMessageToAndroid(stringified); - } - - /** - * @export - * @param {string} actionString . - */ - receiveFromAndroid(actionString) { - const action = JSON.parse(actionString); - this.receiveMessage({ - 'data': action, - }); - } -}; - -export default exports; diff --git a/src/offline/LocalforageCordovaWrapper.js b/src/offline/LocalforageCordovaWrapper.js deleted file mode 100644 index a4ac026877c2..000000000000 --- a/src/offline/LocalforageCordovaWrapper.js +++ /dev/null @@ -1,39 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import AbstractWrapper from 'ngeo/offline/AbstractLocalforageWrapper'; - -const exports = class CordovaWrapper extends AbstractWrapper { - constructor() { - super(); - window.addEventListener('message', this.receiveMessage.bind(this), false); - } - - /** - * @param {unknown} action - * @override - */ - postToBackend(action) { - window.parent.postMessage(action, '*'); - } -}; - -export default exports; diff --git a/src/offline/LocalforageIosWrapper.js b/src/offline/LocalforageIosWrapper.js deleted file mode 100644 index 83838bb15a7a..000000000000 --- a/src/offline/LocalforageIosWrapper.js +++ /dev/null @@ -1,66 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import AbstractWrapper from 'ngeo/offline/AbstractLocalforageWrapper'; - -/** - * @typedef {Object} Action - * @property {string} command - * @property {string[]} args - */ -const exports = class IosWrapper extends AbstractWrapper { - constructor() { - super(); - // @ts-ignore - window.iosWrapper = this; - } - - /** - * @param {Action} action - * @override - */ - postToBackend(action) { - if (action.command === 'setItem') { - action.args[1] = JSON.stringify(action.args[1]); - } - const stringified = JSON.stringify(action); - // @ts-ignore - window.webkit.messageHandlers.ios.postMessage(stringified); - } - - /** - * @export - * @param {string} actionString . - */ - receiveFromIos(actionString) { - const action = JSON.parse(actionString); - /** - * @type {string[]} - */ - const args = action['args'] || []; - action['args'] = args.map((item) => JSON.parse(item)); - this.receiveMessage({ - 'data': action, - }); - } -}; - -export default exports; diff --git a/src/offline/Mask.js b/src/offline/Mask.js deleted file mode 100644 index 4d891dec5fbd..000000000000 --- a/src/offline/Mask.js +++ /dev/null @@ -1,109 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import Layer from 'ol/layer/Layer'; -import {createCanvasContext2D} from 'ol/dom'; -import {DEVICE_PIXEL_RATIO} from 'ol/has'; - -/** - * @typedef {Object} Options - * @property {number} [margin] - * @property {number} [extentInMeters] - */ - -/** - * @augments {Layer} - */ -export default class Mask extends Layer { - /** - * @param {import('ol/layer/Layer').Options} layerOptions - * @param {Options} maskOptions - */ - constructor(layerOptions = {}, maskOptions = {}) { - super(layerOptions); - - /** - * @private - */ - this.context_ = createCanvasContext2D(); - - this.context_.canvas.style.opacity = '0.5'; - this.context_.canvas.style.position = 'absolute'; - - this.margin_ = maskOptions.margin || 100; - this.extentInMeters_ = maskOptions.extentInMeters || 0; - } - - /** - * @param {import('ol/coordinate').Coordinate} center center, a xy point. - * @param {number} halfLength a half length of a square's side. - * @returns {import('ol/extent').Extent} an extent. - */ - createExtent(center, halfLength) { - const minx = center[0] - halfLength; - const miny = center[1] - halfLength; - const maxx = center[0] + halfLength; - const maxy = center[1] + halfLength; - return [minx, miny, maxx, maxy]; - } - - /** - * @param {import("ol/Map").FrameState} frameState - * @returns {HTMLElement} - */ - render(frameState) { - const context = this.context_; - const cwidth = frameState.size[0]; - context.canvas.width = cwidth; - const cheight = frameState.size[1]; - context.canvas.height = cheight; - - // background (clockwise) - context.beginPath(); - context.moveTo(0, 0); - context.lineTo(cwidth, 0); - context.lineTo(cwidth, cheight); - context.lineTo(0, cheight); - context.lineTo(0, 0); - context.closePath(); - - let extentLength = Math.min(cwidth, cheight) - this.margin_ * 2; - if (this.extentInMeters_) { - extentLength = (DEVICE_PIXEL_RATIO * this.extentInMeters_) / frameState.viewState.resolution; - } - - // Draw the get data zone - const extent = this.createExtent([cwidth / 2, cheight / 2], Math.ceil(extentLength / 2)); - - context.moveTo(extent[0], extent[1]); - context.lineTo(extent[0], extent[3]); - context.lineTo(extent[2], extent[3]); - context.lineTo(extent[2], extent[1]); - context.lineTo(extent[0], extent[1]); - context.closePath(); - - // Fill the mask - context.fillStyle = 'rgba(0, 5, 25, 0.5)'; - context.fill(); - - return context.canvas; - } -} diff --git a/src/offline/Mode.js b/src/offline/Mode.js deleted file mode 100644 index 29f46e5b0229..000000000000 --- a/src/offline/Mode.js +++ /dev/null @@ -1,107 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import angular from 'angular'; -class Mode { - /** - * @param {import('ngeo/offline/Configuration').default} ngeoOfflineConfiguration - * ngeo offline configuration service. - * @ngdoc service - * @ngname ngeoOfflineState - */ - constructor(ngeoOfflineConfiguration) { - /** - * Offline mode is enabled or not. - * - * @type {boolean} - * @private - */ - this.enabled_ = false; - - /** - * Offline component. - * - * @type {?import('ngeo/offline/component').Controller} - * @private - */ - this.component_ = null; - - /** - * @private - * @type {import('ngeo/offline/Configuration').default} - */ - this.ngeoOfflineConfiguration_ = ngeoOfflineConfiguration; - } - - /** - * Return if we are in offline mode. - * - * @returns {boolean} whether offline mode is enabled - * @export - */ - isEnabled() { - return this.enabled_; - } - - /** - * Enable offline mode. ATM we cannot escape from the offline mode. - * - * @export - */ - enable() { - this.enabled_ = true; - } - - /** - * - * @param {import('ngeo/offline/component').Controller} component Offline component. - * @export - */ - registerComponent(component) { - this.component_ = component; - } - - /** - * @export - */ - activateOfflineMode() { - if (!this.component_) { - throw new Error('The component is not registered'); - } - this.component_.activateOfflineMode(); - } - - /** - * @returns {boolean} True if data are accessible offline. - * @export - */ - hasData() { - return this.ngeoOfflineConfiguration_.hasOfflineData(); - } -} -Mode.$inject = ['ngeoOfflineConfiguration']; -/** - * @type {!angular.IModule} - */ -const myModule = angular.module('ngeoOfflineMode', []); -myModule.service('ngeoOfflineMode', Mode); -Mode.module = myModule; -export default Mode; diff --git a/src/offline/NetworkStatus.js b/src/offline/NetworkStatus.js deleted file mode 100644 index 2610409b0eac..000000000000 --- a/src/offline/NetworkStatus.js +++ /dev/null @@ -1,224 +0,0 @@ -configFunction_.$inject = ['$httpProvider']; -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import ngeoMiscDebounce from 'ngeo/misc/debounce'; -import angular from 'angular'; -const Service = class { - /** - * This service watches the status of network connection. - * - * Currently it watches every $http and $.ajax requests errors, if an error - * occurs we wait 2 sec then we make an http request on the checker file. - * If the checker responds that means we are online, otherwise we make a - * 2nd request 2 sec later, if the 2nd requests failed that means we - * are offline. - * - * A timeout of 1 sec is set for the checker file, so if we have a bad - * connection, we consider we are offline. - * - * During offline mode we test every 2 sec if we are back online. - * - * @param {!jQuery} $document Angular document service. - * @param {angular.IWindowService} $window Angular window service. - * @param {angular.ITimeoutService} $timeout Angular timeout service. - * @param {angular.IScope} $rootScope The root scope. - * @param {import('ngeo/options').ngeoOfflineTestUrl} ngeoOfflineTestUrl URL of the test page. - */ - constructor($document, $window, $timeout, $rootScope, ngeoOfflineTestUrl) { - /** - * @private - * @type {!jQuery} - */ - this.$document_ = $document; - - /** - * @private - * @type {!Window} - */ - this.$window_ = $window; - - /** - * @private - * @type {!angular.ITimeoutService} - */ - this.$timeout_ = $timeout; - - /** - * @private - * @type {angular.IScope} - */ - this.$rootScope_ = $rootScope; - - /** - * @private - * @type {import('ngeo/options').ngeoOfflineTestUrl} - */ - this.ngeoOfflineTestUrl_ = ngeoOfflineTestUrl; - - /** - * @private - * @type {!number} - */ - this.count_ = 0; - - /** - * @type {!boolean|undefined} - * @private - */ - this.offline_; - - /** - * @private - * @type {angular.IPromise|undefined} - */ - this.promise_; - this.initialize_(); - } - initialize_() { - this.offline_ = !this.$window_.navigator.onLine; - - // airplane mode, works offline(firefox) - this.$window_.addEventListener('offline', () => { - this.triggerChangeStatusEvent_(true); - }); - - // online event doesn't means we have a internet connection, that means we - // have possibly one (connected to a router ...) - this.$window_.addEventListener('online', () => { - this.check(undefined); - }); - - // We catch every $.ajax request errors or (canceled request). - // @ts-ignore - if (this.$document_.ajaxError) { - /** - * @param {unknown} evt - * @param {unknown} jqxhr - * @param {unknown} settings - * @param {string} thrownError - */ - const onAjaxError = (evt, jqxhr, settings, thrownError) => { - // Filter out canceled requests - if (!/^(canceled|abort)$/.test(thrownError)) { - this.check(2000); - } - }; - // @ts-ignore - this.$document_.ajaxError(onAjaxError); - } - } - - /** - * Check fir network status - * - * @param {number} [timeout] Delay for timeout. - */ - check(timeout) { - if (this.promise_) { - this.$timeout_.cancel(this.promise_); - this.promise_ = undefined; - } - if (timeout !== undefined) { - this.count_++; - this.promise_ = this.$timeout_(() => this.check(), timeout); - return; - } - $.ajax({ - method: 'GET', - url: this.ngeoOfflineTestUrl_, - timeout: 1000, - success: () => { - this.count_ = 0; - if (this.offline_) { - this.triggerChangeStatusEvent_(false); - } - }, - error: () => { - this.count_++; - // We consider we are offline after 3 requests failed - if (this.count_ > 2 && !this.offline_) { - this.triggerChangeStatusEvent_(true); - } - }, - }); - } - - /** - * @param {boolean} offline whether it's offline or not. - * @private - */ - triggerChangeStatusEvent_(offline) { - this.offline_ = offline; - // this.$rootScope_.$broadcast('ngeoNetworkStatusChange', net.offline); - this.$rootScope_.$digest(); - } - - /** - * @returns {boolean} True if we are disconnected. - * @export - */ - isDisconnected() { - return !!this.offline_; - } -}; -const name = 'ngeoNetworkStatus'; -Service.module = angular.module(name, [ngeoMiscDebounce.name]); -Service.module.service(name, Service); - -/** - * @param {angular.IQService} $q The Angular $q service. - * @param {import('ngeo/misc/debounce').miscDebounce} ngeoDebounce ngeo debounce service. - * @param {Service} ngeoNetworkStatus ngeo network status service. - * @returns {angular.IHttpInterceptor} the interceptor - */ -const httpInterceptor = function ($q, ngeoDebounce, ngeoNetworkStatus) { - const debouncedCheck = ngeoDebounce(() => ngeoNetworkStatus.check(undefined), 2000, false); - return { - request(config) { - return config; - }, - requestError(rejection) { - return $q.reject(rejection); - }, - response(response) { - return response; - }, - responseError(rejection) { - debouncedCheck(); - return $q.reject(rejection); - }, - }; -}; -httpInterceptor.$inject = ['$q', 'ngeoDebounce', 'ngeoNetworkStatus']; -httpInterceptor.$inject = ['$q', 'ngeoDebounce', 'ngeoNetworkStatus']; -Service.module.factory('httpInterceptor', httpInterceptor); - -/** - * @private - * @param {angular.IHttpProvider} $httpProvider . - */ -function configFunction_($httpProvider) { - $httpProvider.interceptors.push('httpInterceptor'); -} -Service.module.config(configFunction_); -const exports = Service; -export default exports; diff --git a/src/offline/Restorer.js b/src/offline/Restorer.js deleted file mode 100644 index e93aa2124b18..000000000000 --- a/src/offline/Restorer.js +++ /dev/null @@ -1,79 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import ngeoMapBackgroundLayerMgr from 'ngeo/map/BackgroundLayerMgr'; -import angular from 'angular'; -class Restorer { - /** - * @param {import('ngeo/offline/Configuration').default} ngeoOfflineConfiguration - * A service for customizing offline behavior. - * @param {import('ngeo/map/BackgroundLayerMgr').MapBackgroundLayerManager} ngeoBackgroundLayerMgr - * The background layer manager. - */ - constructor(ngeoOfflineConfiguration, ngeoBackgroundLayerMgr) { - /** - * @private - * @type {import('ngeo/offline/Configuration').default} - */ - this.configuration_ = ngeoOfflineConfiguration; - - /** - * @private - * @type {import('ngeo/map/BackgroundLayerMgr').MapBackgroundLayerManager} - */ - this.ngeoBackgroundLayerMgr_ = ngeoBackgroundLayerMgr; - } - - /** - * @param {import('ol/Map').default} map The map to work on. - * @returns {Promise} A promise to the extent of the restored area. - */ - restore(map) { - return this.configuration_ - .getItem('offline_content') - .then((offlineContent) => this.doRestore(map, offlineContent)); - } - - /** - * @protected - * @param {import('ol/Map').default} map A map - * @param {import('./index').OfflinePersistentContent} offlineContent The offline content - * @returns {import('ol/extent').Extent} The extent of the restored area - */ - doRestore(map, offlineContent) { - map.getLayerGroup().getLayers().clear(); - for (const offlineLayer of offlineContent.layers) { - const layer = this.configuration_.recreateOfflineLayer(offlineLayer); - if (layer) { - map.addLayer(layer); - if (offlineLayer.backgroundLayer) { - this.ngeoBackgroundLayerMgr_.set(map, layer); - } - } - } - return offlineContent.extent; - } -} -Restorer.$inject = ['ngeoOfflineConfiguration', 'ngeoBackgroundLayerMgr']; -const name = 'ngeoOfflineRestorer'; -Restorer.module = angular.module(name, [ngeoMapBackgroundLayerMgr.name]).service(name, Restorer); -const exports = Restorer; -export default exports; diff --git a/src/offline/SerializerDeserializer.js b/src/offline/SerializerDeserializer.js deleted file mode 100644 index 0ef2927c3525..000000000000 --- a/src/offline/SerializerDeserializer.js +++ /dev/null @@ -1,271 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import OlTilegridTileGrid from 'ol/tilegrid/TileGrid'; -import OlTilegridWMTS from 'ol/tilegrid/WMTS'; -import * as olProj from 'ol/proj'; -import OlSourceTileWMS from 'ol/source/TileWMS'; -import OlSourceWMTS from 'ol/source/WMTS'; -import OlLayerTile from 'ol/layer/WebGLTile'; - -const SerDes = class { - /** - * @param {unknown} options The options - */ - // @ts-ignore - constructor({gutter}) { - /** - * @private - */ - this.gutter_ = gutter; - } - - /** - * @private - * @param {import('ol/Object').default} olObject An OL object - * @returns {Object} The serializable properties of the object - */ - createBaseObject_(olObject) { - const properties = olObject.getProperties(); - /** - * @type {Object} - */ - const obj = {}; - for (const key in properties) { - const value = properties[key]; - const typeOf = typeof value; - if (typeOf === 'string' || typeOf === 'number') { - obj[key] = value; - } - } - return obj; - } - - /** - * @param {OlTilegridTileGrid} tilegrid . - * @returns {string} . - */ - serializeTilegrid(tilegrid) { - /** - * @type {Object} - */ - const obj = {}; - obj.extent = tilegrid.getExtent(); - obj.minZoom = tilegrid.getMinZoom(); - obj.origin = tilegrid.getOrigin(0); // hack - obj.resolutions = tilegrid.getResolutions(); - obj.tileSize = tilegrid.getTileSize(tilegrid.getMinZoom()); - return JSON.stringify(obj); - } - - /** - * @param {string} serialization . - * @returns {OlTilegridTileGrid} tilegrid - */ - deserializeTilegrid(serialization) { - /** - * @type {import ("ol/tilegrid/WMTS").Options} - */ - const options = JSON.parse(serialization); - return new OlTilegridTileGrid(options); - } - - /** - * @param {OlTilegridWMTS} tilegrid . - * @returns {string|undefined} . - */ - serializeTilegridWMTS(tilegrid) { - if (!tilegrid) { - return undefined; - } - /** - * @type {{ - * 'extent': import('ol/extent').Extent, - * 'minZoom': number, - * 'matrixIds': string[], - * 'resolutions': number[], - * 'origins': import('ol/coordinate').Coordinate[], - * }} - */ - const obj = {}; - const resolutions = tilegrid.getResolutions(); - obj.extent = tilegrid.getExtent(); - obj.minZoom = tilegrid.getMinZoom(); - obj.matrixIds = tilegrid.getMatrixIds(); - obj.resolutions = resolutions; - - obj.origins = []; - for (let z = 0; z < resolutions.length; ++z) { - obj.origins.push(tilegrid.getOrigin(z)); - } - return JSON.stringify(obj); - } - - /** - * @param {string} serialization . - * @returns {OlTilegridWMTS} tilegrid . - */ - deserializeTilegridWMTS(serialization) { - /** - * @type {import ("ol/tilegrid/WMTS").Options} - */ - const options = JSON.parse(serialization); - return new OlTilegridWMTS(options); - } - - /** - * @param {OlSourceTileWMS} source . - * @returns {string} . - */ - serializeSourceTileWMS(source) { - const obj = this.createBaseObject_(source); - obj.params = source.getParams(); - obj.urls = source.getUrls(); - obj.tileGrid = this.serializeTilegrid(source.getTileGrid()); - const projection = source.getProjection(); - if (projection) { - obj.projection = olProj.get(source.getProjection()).getCode(); - } - - return JSON.stringify(obj); - } - - /** - * @param {string} serialization . - * @param {function(import('ol/ImageTile').default, string): void} [tileLoadFunction] . - * @returns {OlSourceTileWMS} source . - */ - deserializeSourceTileWMS(serialization, tileLoadFunction) { - /** - * @type {import ("ol/source/TileWMS").Options} - */ - const options = JSON.parse(serialization); - // @ts-ignore - options.tileLoadFunction = tileLoadFunction; - if (options.tileGrid) { - options.tileGrid = this.deserializeTilegrid(/** @type {any} */ (options).tileGrid); - } - options.gutter = this.gutter_; - return new OlSourceTileWMS(options); - } - - /** - * @param {OlSourceWMTS} source . - * @returns {string} . - */ - serializeSourceWMTS(source) { - const obj = this.createBaseObject_(source); - obj.dimensions = source.getDimensions(); - obj.format = source.getFormat(); - obj.urls = source.getUrls(); - obj.version = source.getVersion(); - obj.layer = source.getLayer(); - obj.style = source.getStyle(); - obj.matrixSet = source.getMatrixSet(); - // The OL getTileGrid method is expected to return a WMTS tilegrid so it is OK to cast here. - const tileGridWMTS = /** @type {OlTilegridWMTS} */ (source.getTileGrid()); - obj.tileGrid = this.serializeTilegridWMTS(tileGridWMTS); - obj.requestEncoding = source.getRequestEncoding(); - const projection = source.getProjection(); - if (projection) { - obj.projection = olProj.get(source.getProjection()).getCode(); - } - - return JSON.stringify(obj); - } - - /** - * @param {string} serialization . - * @param {function(import('ol/ImageTile').default, string): void} [tileLoadFunction] . - * @returns {OlSourceWMTS} . - */ - deserializeSourceWMTS(serialization, tileLoadFunction) { - /** - * @type {import("ol/source/WMTS").Options} - */ - const options = JSON.parse(serialization); - // @ts-ignore - options.tileLoadFunction = tileLoadFunction; - if (options.tileGrid) { - options.tileGrid = this.deserializeTilegridWMTS(/** @type {any} */ (options).tileGrid); - } - return new OlSourceWMTS(options); - } - - /** - * @private - * @param {number} number Some number which may be Infinity - * @returns {number} The same number or an arbitrary big number instead of Infinity - */ - makeInfinitySerializable_(number) { - if (number === Infinity) { - return 1000; - } - return number; - } - - /** - * @param {!import('ol/layer/WebGLTile').default|import('ol/layer/Image').default} layer . - * @param {import('ol/source/Source').default} [source] . - * @returns {string} . - */ - serializeTileLayer(layer, source) { - const obj = this.createBaseObject_(layer); - obj.opacity = layer.getOpacity(); - obj.visible = layer.getVisible(); - obj.minResolution = layer.getMinResolution(); - obj.maxResolution = this.makeInfinitySerializable_(layer.getMaxResolution()); - obj.zIndex = layer.getZIndex(); - source = source || layer.getSource(); - if (source instanceof OlSourceTileWMS) { - obj.source = this.serializeSourceTileWMS(source); - obj.sourceType = 'tileWMS'; - } else if (source instanceof OlSourceWMTS) { - obj.source = this.serializeSourceWMTS(source); - obj.sourceType = 'WMTS'; - } - return JSON.stringify(obj); - } - - /** - * @param {string} serialization . - * @param {function(import('ol/ImageTile').default, string): void} [tileLoadFunction] . - * @returns {!import('ol/layer/WebGLTile').default} . - */ - deserializeTileLayer(serialization, tileLoadFunction) { - /** - * @type {import('ol/layer/BaseTile').Options} - */ - const options = JSON.parse(serialization); - // @ts-ignore - const sourceType = options.sourceType; - if (sourceType === 'tileWMS') { - options.source = this.deserializeSourceTileWMS(/** @type {any} */ (options).source, tileLoadFunction); - } else if (sourceType === 'WMTS') { - options.source = this.deserializeSourceWMTS(/** @type {any} */ (options).source, tileLoadFunction); - } - return new OlLayerTile(options); - } -}; - -const exports = SerDes; - -export default exports; diff --git a/src/offline/ServiceManager.js b/src/offline/ServiceManager.js deleted file mode 100644 index c21d45d6b05a..000000000000 --- a/src/offline/ServiceManager.js +++ /dev/null @@ -1,134 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import angular from 'angular'; -class ServiceManager { - /** - * @param {angular.auto.IInjectorService} $injector Main injector. - * @struct - * @ngdoc service - * @ngname ngeoOfflineServiceManager - */ - constructor($injector) { - /** - * @type {angular.auto.IInjectorService} - * @private - */ - this.$injector_ = $injector; - - /** - * @type {*} - * @private - */ - this.saveService_ = null; - - /** - * @type {*} - * @private - */ - this.restoreService_ = null; - } - - /** - * @param {string|unknown} serviceLike A service like. - * @param {string} method A method. - * @returns {unknown} A returned object. - */ - getOfflineService_(serviceLike, method) { - if (typeof serviceLike === 'string') { - if (!this.$injector_.has(serviceLike)) { - console.error(`The offline ${method} service could not be found`); - return; - } - const service = this.$injector_.get(serviceLike); - if (!service[method]) { - console.error(`The offline service ${serviceLike} does not have a ${method} method`); - return; - } - return service; - } - // @ts-ignore - if (!serviceLike[method]) { - console.error(`The provided offline service does not have a ${method} method`); - return; - } - return serviceLike; - } - - /** - * Set the service to call on 'save'. - * - * @param {string|{save: Function}} saveLikeService - * A service name that can be injected or an object that have a 'save' method. - */ - setSaveService(saveLikeService) { - this.saveService_ = this.getOfflineService_(saveLikeService, 'save'); - } - - /** - * Set the service to call on 'restore' - * - * @param {string|{restore: Function}} restoreLikeService - * A service name that can be injected or an object that have a 'restore' method. - */ - setRestoreService(restoreLikeService) { - this.restoreService_ = this.getOfflineService_(restoreLikeService, 'restore'); - } - cancel() { - if (!this.saveService_) { - console.warn('You must register a saveService first'); - return; - } - this.saveService_.cancel(); - } - - /** - * Ask the provided service to save the data to an offline purpose - * - * @param {import('ol/extent').Extent} extent The extent to download. - * @param {import("ol/Map").default} map The map to work on. - */ - save(extent, map) { - if (!this.saveService_) { - console.warn('You must register a saveService first'); - return; - } - this.saveService_.save(extent, map); - } - - /** - * Ask the provided service to restore the saved data on the map - * - * @param {import('ol/Map').default} map The map to work on. - * @returns {Promise} A promise to the extent of the downloaded area - */ - restore(map) { - if (!this.restoreService_) { - console.warn('You must register a restoreService first'); - return Promise.reject(); - } - return this.restoreService_.restore(map); - } -} -ServiceManager.$inject = ['$injector']; -ServiceManager.module = angular.module('ngeoOfflineServiceManager', []); -ServiceManager.module.service('ngeoOfflineServiceManager', ServiceManager); -export default ServiceManager; diff --git a/src/offline/TilesDownloader.js b/src/offline/TilesDownloader.js deleted file mode 100644 index 850716a22adc..000000000000 --- a/src/offline/TilesDownloader.js +++ /dev/null @@ -1,223 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -/** - * @param {!Blob} blob A blob - * @returns {Promise} data URL - */ -function blobToDataUrl(blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = function () { - resolve(/** @type {string} */ (reader.result)); - }; - reader.onerror = reject; - reader.readAsDataURL(blob); - }); -} - -export default class TileDownloader { - /** - * @param {import('./index').OfflineTile[]} tiles An array of tiles to download. - * @param {import('./index').OfflineOnTileDownload} callbacks The callbacks. - * @param {number} workers The maximum number of workers. - */ - constructor(tiles, callbacks, workers) { - /** - * @private - * @type {number} - */ - this.maxNumberOfWorkers_ = workers; - - /** - * @private - */ - this.wasStarted_ = false; - - /** - * @type {import('./index').OfflineTile[]} - * @private - */ - this.tiles_ = tiles; - - /** - * @private - * @type {import('./index').OfflineOnTileDownload} - */ - this.callbacks_ = callbacks; - - /** - * @private - */ - this.allCount_ = 0; - - /** - * @private - */ - this.okCount_ = 0; - - /** - * @private - */ - this.koCount_ = 0; - - /** - * @private - */ - this.requestedCount_ = 0; - - /** - * @private - * @type {?function(): unknown} - */ - this.resolvePromise_ = null; - - /** - * @private - * @type {?Promise} - */ - this.promise_ = null; - - /** - * @type {number} - * @private - */ - this.tileIndex_ = 0; - - /** - * @type {boolean} - * @private - */ - this.cancel_ = false; - } - - cancel() { - this.cancel_ = true; - } - - /** - * @returns {Promise} A promise that resolves when the downloads are complete (failing or not) - */ - download() { - if (this.promise_) { - return this.promise_; - } - - this.promise_ = new Promise((resolve, reject) => { - this.resolvePromise_ = resolve; - }); - - console.assert(this.tiles_); - if (this.tiles_.length === 0) { - this.callbacks_.onTileDownloadError(1); // forcing progress update - if (this.resolvePromise_) { - this.resolvePromise_(); - } - } else { - for (let i = 0; i < this.maxNumberOfWorkers_; ++i) { - this.downloadTile_(); - } - } - - return this.promise_; - } - - /** - * @private - */ - downloadTile_() { - if (this.cancel_ || this.tileIndex_ >= this.tiles_.length) { - return; - } - const tile = this.tiles_[this.tileIndex_++]; - const tileUrl = tile.url; - const xhr = new XMLHttpRequest(); - xhr.open('GET', tileUrl, true); - xhr.responseType = 'blob'; - const onTileDownloaded = () => { - if (this.allCount_ === this.tiles_.length && this.resolvePromise_) { - this.resolvePromise_(); - } - this.downloadTile_(); - }; - - /** - * @param {unknown} _ Unused. - */ - const errorCallback = (_) => { - if (this.cancel_) { - return; - } - ++this.allCount_; - ++this.koCount_; - const progress = this.allCount_ / this.tiles_.length; - this.callbacks_.onTileDownloadError(progress).then(onTileDownloaded, onTileDownloaded); - }; - - /** - * - * @param {!ProgressEvent} e The load event. - */ - const onloadCallback = (e) => { - /** - * @type {?Blob} - */ - const response = xhr.response; - if (response && response.size !== 0) { - // non-empty tile - blobToDataUrl(response).then( - (dataUrl) => { - if (this.cancel_) { - return; - } - ++this.allCount_; - ++this.okCount_; - tile.response = dataUrl; - const progress = this.allCount_ / this.tiles_.length; - this.callbacks_.onTileDownloadSuccess(progress, tile).then(onTileDownloaded, onTileDownloaded); - }, - () => { - if (this.cancel_) { - return; - } - errorCallback(e); - }, - ); - } else { - if (this.cancel_) { - return; - } - ++this.allCount_; - ++this.okCount_; - this.callbacks_ - .onTileDownloadSuccess(this.allCount_ / this.tiles_.length, tile) - .then(onTileDownloaded, onTileDownloaded); - } - }; - - xhr.onload = onloadCallback; - xhr.onerror = errorCallback; - xhr.onabort = errorCallback; - xhr.ontimeout = errorCallback; - xhr.send(); - ++this.requestedCount_; - } -} diff --git a/src/offline/component.html.js b/src/offline/component.html.js deleted file mode 100644 index 6ef1f175da22..000000000000 --- a/src/offline/component.html.js +++ /dev/null @@ -1,182 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -export default `
- -
-
- -
-
-
- -
-
- Save map -
-
Abort
-
- -
-
{{$ctrl.progressPercents}}%
-
- - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/src/offline/component.js b/src/offline/component.js deleted file mode 100644 index 3828567d7769..000000000000 --- a/src/offline/component.js +++ /dev/null @@ -1,578 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import ngeoMapFeatureOverlayMgr from 'ngeo/map/FeatureOverlayMgr'; -import ngeoMessageModalComponent from 'ngeo/message/modalComponent'; -import {extentToRectangle} from 'ngeo/utils'; -import olCollection from 'ol/Collection'; -import Feature from 'ol/Feature'; -import Polygon from 'ol/geom/Polygon'; -import {DEVICE_PIXEL_RATIO} from 'ol/has'; -import angular from 'angular'; -import MaskLayer from './Mask'; -import htmlTemplate from './component.html'; - -/** - * @type {!angular.IModule} - */ -const myModule = angular.module('ngeoOffline', [ngeoMessageModalComponent.name]); -myModule.value( - 'ngeoOfflineTemplateUrl', - /** - * @param {JQuery} element Element. - * @param {angular.IAttributes} attrs Attributes. - * @returns {string} Template URL. - */ - (element, attrs) => { - const templateUrl = attrs['ngeoOfflineTemplateurl']; - return templateUrl !== undefined ? templateUrl : 'ngeo/offline/component.html'; - }, -); -myModule.run( - /** - * @param {angular.ITemplateCacheService} $templateCache - */ - [ - '$templateCache', - ($templateCache) => { - // @ts-ignore: webpack - $templateCache.put('ngeo/offline/component.html', htmlTemplate); - }, - ], -); - -/** - * @param {!JQuery} $element Element. - * @param {!angular.IAttributes} $attrs Attributes. - * @param {!function(!JQuery, !angular.IAttributes): string} ngeoOfflineTemplateUrl Template function. - * @returns {string} Template URL. - */ -ngeoOfflineTemplateUrl.$inject = ['$element', '$attrs', 'ngeoOfflineTemplateUrl']; -function ngeoOfflineTemplateUrl($element, $attrs, ngeoOfflineTemplateUrl) { - return ngeoOfflineTemplateUrl($element, $attrs); -} - -/** - * Provides the "offline" component. - * - * Example: - * - * - * ngeo-offline-mask-margin="::100" - * ngeo-offline-min_zoom="::11" - * ngeo-offline-max_zoom="::15" - * - * - * See our live example: [../examples/offline.html](../examples/offline.html) - * - * @htmlAttribute {import('ol/Map').default} ngeo-offline-map The map. - * @htmlAttribute {number} ngeo-offline-extentsize The size, in map units, of a side of the extent. - * @private - * @ngdoc component - * @ngname ngeoOffline - */ -const component = { - bindings: { - 'map': '>} - * @private - */ - this.overlayCollection_ = new olCollection(); - this.featuresOverlay_.setFeatures(this.overlayCollection_); - - /** - * @type {?Polygon} - * @private - */ - this.dataPolygon_ = null; - - /** - * Whether the current view is the extent selection. - * - * @type {boolean} - * @export - */ - this.selectingExtent = false; - - /** - * Whether the current view is downloading one. - * - * @type {boolean} - * @export - */ - this.downloading = false; - - /** - * The progression of the data loading (0-100%). - * - * @type {number} - * @export - */ - this.progressPercents = 0; - - /** - * Whether the menu is currently displayed. - * - * @type {boolean} - * @export - */ - this.menuDisplayed = false; - - /** - * Whether the cancel download modal is displayed. - * - * @type {boolean} - * @export - */ - this.displayAlertAbortDownload = false; - - /** - * Whether the load data modal is displayed. - * - * @type {boolean} - * @export - */ - this.displayAlertLoadData = false; - - /** - * Whether the "no layer" modal is displayed. - * - * @type {boolean} - * @export - */ - this.displayAlertNoLayer = false; - - /** - * Offline mask minimum margin in pixels. - * - * @type {number} - * @export - */ - this.maskMargin = 0; - - /** - * Minimum zoom where offline is enable. - * - * @type {number} - * @export - */ - this.minZoom; - - /** - * Maximum zoom where offline is enable. - * - * @type {number} - * @export - */ - this.maxZoom; - - /** - * Map view max zoom constraint. - * - * @type {number} - * @export - */ - this.originalMinZoom; - - /** - * Map view min zoom constraint. - * - * @type {number} - * @export - */ - this.originalMaxZoom; - - /** - * @type {number} - * @export - */ - this.estimatedLoadDataSize = 0; - - /** - * @type {boolean} - */ - this.rotateMask = false; - - /** - * @private - * @param {import("ngeo/CustomEvent").default<{'progress': number}>} event the progress event. - */ - this.progressCallback_ = (event) => { - const progress = event.detail.progress; - this.progressPercents = Math.floor(progress * 100); - if (progress === 1) { - this.finishDownload_(); - } - this.$timeout_(() => {}, 0); // FIXME: force redraw - }; - } - $onInit() { - this.offlineMode.registerComponent(this); - this.ngeoOfflineConfiguration_.on( - /** @type {import('ol/Observable').EventTypes} */ 'progress', - /** @type {function(?): ?} */ this.progressCallback_, - ); - this.maskMargin = this.maskMargin || 100; - this.minZoom = this.minZoom || 10; - this.maxZoom = this.maxZoom || 15; - // @ts-ignore: extentInMeters does not exists... - this.maskLayer_ = new MaskLayer( - { - extentInMeters: this.extentSize, - }, - { - margin: this.maskMargin, - }, - ); - } - $onDestroy() { - this.ngeoOfflineConfiguration_.un( - /** @type {import('ol/Observable').EventTypes} */ 'progress', - /** @type {function(?): ?} */ this.progressCallback_, - ); - } - - /** - * @returns {boolean} True if data are accessible offline. - * @export - */ - hasData() { - return this.ngeoOfflineConfiguration_.hasOfflineData(); - } - - /** - * @export - */ - computeSizeAndDisplayAlertLoadData() { - this.estimatedLoadDataSize = this.ngeoOfflineConfiguration_.estimateLoadDataSize(this.map); - if (this.estimatedLoadDataSize > 0) { - this.displayAlertLoadData = true; - } else { - this.displayAlertNoLayer = true; - } - } - /** - * Toggle the selecting extent view. - * - * @param {boolean} [finished] If just finished downloading. - * @export - */ - toggleViewExtentSelection(finished) { - this.menuDisplayed = false; - this.selectingExtent = !this.selectingExtent; - this.map.removeLayer(this.maskLayer_); - this.removeZoomConstraints_(); - if (this.selectingExtent && !this.map.getLayers().getArray().includes(this.maskLayer_)) { - this.addZoomConstraints_(); - this.map.addLayer(this.maskLayer_); - } - this.map.render(); - } - - /** - * Validate the current extent and download data. - * - * @export - */ - validateExtent() { - this.progressPercents = 0; - const extent = this.getDowloadExtent_(); - this.downloading = true; - this.ngeoOfflineServiceManager_.save(extent, this.map); - } - - /** - * @private - */ - finishDownload_() { - this.downloading = false; - this.toggleViewExtentSelection(true); - } - - /** - * Ask to abort the download of data. - * - * @export - */ - askAbortDownload() { - this.displayAlertAbortDownload = true; - } - - /** - * Abort the download of data. - * - * @export - */ - abortDownload() { - this.downloading = false; - this.ngeoOfflineServiceManager_.cancel(); - this.deleteData(); - } - - /** - * Show the main menu. - * - * @export - */ - showMenu() { - this.menuDisplayed = true; - } - - /** - * Activate offline mode. - * Zoom to the extent of that data and restore the data. - * - * @export - */ - activateOfflineMode() { - this.ngeoOfflineServiceManager_.restore(this.map).then((extent) => { - this.dataPolygon_ = this.createPolygonFromExtent_(extent); - const size = this.map.getSize(); - if (size === undefined) { - throw new Error('Missing size'); - } - this.map.getView().fit(extent, { - size, - }); - this.menuDisplayed = false; - this.displayExtent_(); - this.offlineMode.enable(); - }); - } - - /** - * - * Deactivate offline mode. - * Reload the page. - * - * @export - */ - deactivateOfflineMode() { - window.location.reload(); - } - - /** - * Toggle the visibility of the data's extent. - * - * @export - */ - toggleExtentVisibility() { - if (this.isExtentVisible()) { - this.overlayCollection_.clear(); - } else { - this.displayExtent_(); - } - } - - /** - * @returns {boolean} True if the extent is currently visible. False otherwise. - * @export - */ - isExtentVisible() { - return this.overlayCollection_.getLength() > 0; - } - - /** - * Delete the saved data. - * - * @export - */ - deleteData() { - this.overlayCollection_.clear(); - this.dataPolygon_ = null; - if (this.networkStatus.isDisconnected()) { - this.menuDisplayed = false; - } - const reloadIfInOfflineMode = () => { - if (this.offlineMode.isEnabled()) { - this.deactivateOfflineMode(); - } - }; - this.ngeoOfflineConfiguration_.clear().then(reloadIfInOfflineMode); - } - - /** - * @private - */ - displayExtent_() { - if (!this.isExtentVisible() && this.dataPolygon_) { - const feature = new Feature(this.dataPolygon_); - this.overlayCollection_.push(feature); - } - } - - /** - * When enabling mask extent, zoom the view to the defined zoom range and - * add constraints to the view to not allow user to move out of this range. - * - * @private - */ - addZoomConstraints_() { - const view = this.map.getView(); - const zoom = view.getZoom() || 0; - this.originalMinZoom = view.getMinZoom(); - this.originalMaxZoom = view.getMaxZoom(); - if (zoom < this.minZoom) { - view.setZoom(this.minZoom); - } else if (zoom > this.maxZoom) { - view.setZoom(this.maxZoom); - } - view.setMaxZoom(this.maxZoom); - view.setMinZoom(this.minZoom); - } - - /** - * @private - */ - removeZoomConstraints_() { - const view = this.map.getView(); - if (this.originalMaxZoom !== undefined && this.originalMinZoom !== undefined) { - view.setMaxZoom(this.originalMaxZoom); - view.setMinZoom(this.originalMinZoom); - } - } - - /** - * A polygon on the whole extent of the projection, with a hole for the offline extent. - * - * @param {import('ol/extent').Extent} extent An extent - * @returns {Polygon} Polygon to save, based on the projection extent, the center of the map and - * the extentSize property. - * @private - */ - createPolygonFromExtent_(extent) { - const projExtent = this.map.getView().getProjection().getExtent(); - return new Polygon([extentToRectangle(projExtent), extentToRectangle(extent)], 'XY'); - } - - /** - * @returns {import('ol/extent').Extent} the download extent. - * @private - */ - getDowloadExtent_() { - const center = this.map.getView().getCenter(); - const halfLength = Math.ceil(this.extentSize || this.getExtentSize_()) / 2; - return this.maskLayer_.createExtent(center, halfLength); - } - getExtentSize_() { - const mapSize = this.map.getSize() || [150, 150]; - const maskSizePixel = DEVICE_PIXEL_RATIO * Math.min(mapSize[0], mapSize[1]) - this.maskMargin * 2; - const maskSizeMeter = (maskSizePixel * (this.map.getView().getResolution() || 1)) / DEVICE_PIXEL_RATIO; - return maskSizeMeter; - } -} -Controller.$inject = [ - '$timeout', - 'ngeoOfflineServiceManager', - 'ngeoOfflineConfiguration', - 'ngeoOfflineMode', - 'ngeoNetworkStatus', -]; -myModule.controller('ngeoOfflineController', Controller); -export default myModule; diff --git a/src/offline/index.js b/src/offline/index.js deleted file mode 100644 index 91b6b54e4098..000000000000 --- a/src/offline/index.js +++ /dev/null @@ -1,81 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2019-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -/** - * @typedef {Object} OfflineExtentByZoom - * @property {number} zoom - * @property {import('ol/extent').Extent} extent - */ - -/** - * @typedef {Object} OfflineLayerMetadata - * @property {import('ol/Map').default} map - * @property {OfflineExtentByZoom[]} extentByZoom - * @property {import('ol/layer/Layer').default} layer - * @property {import('ol/source/Source').default} source - * @property {string|undefined} layerType - * @property {string|undefined} layerSerialization - * @property {boolean} backgroundLayer - * @property {import('ol/layer/Group').default[]} ancestors - */ - -/** - * @typedef {Object} OfflinePersistentLayer - * @property {string|undefined} layerType - * @property {string|undefined} layerSerialization - * @property {boolean} backgroundLayer - * @property {string} key - */ - -/** - * @typedef {Object} OfflinePersistentContent - * @property {import('ol/extent').Extent} extent - * @property {!OfflinePersistentLayer[]} layers - * @property {!number[]} zooms - */ - -/** - * @typedef {Object} OfflineTile - * @property {import('ol/coordinate').Coordinate} coord - * @property {string} url - * @property {?string} response - */ - -/** - * @callback onTileDownloadSuccess - * @param {number} progress - * @param {OfflineTile} tile - * @returns {Promise} - */ - -/** - * @callback onTileDownloadError - * @param {number} progress - * @returns {Promise} - */ - -/** - * @typedef {Object} OfflineOnTileDownload - * @property {onTileDownloadSuccess} onTileDownloadSuccess - * @property {onTileDownloadError} onTileDownloadError - */ - -export default {}; diff --git a/src/offline/module.js b/src/offline/module.js deleted file mode 100644 index f14a4e6d14cd..000000000000 --- a/src/offline/module.js +++ /dev/null @@ -1,44 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import ngeoOfflineComponent from 'ngeo/offline/component'; -import ngeoOfflineNetworkStatus from 'ngeo/offline/NetworkStatus'; -import ngeoOfflineServiceManager from 'ngeo/offline/ServiceManager'; -import downloader from 'ngeo/offline/Downloader'; -import restorer from 'ngeo/offline/Restorer'; -import mode from 'ngeo/offline/Mode'; -import angular from 'angular'; - -/** - * @type {!angular.IModule} - */ -const exports = angular.module('ngeoOfflineModule', [ - ngeoOfflineComponent.name, - ngeoOfflineNetworkStatus.module.name, - ngeoOfflineServiceManager.module.name, - downloader.module.name, - restorer.module.name, - mode.module.name, -]); - -exports.value('ngeoOfflineGutter', 96); - -export default exports; diff --git a/src/offline/utils.js b/src/offline/utils.js deleted file mode 100644 index 8a16b732b986..000000000000 --- a/src/offline/utils.js +++ /dev/null @@ -1,53 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2018-2024 Camptocamp SA -// -// Permission is hereby granted, free of charge, to any person obtaining a copy of -// this software and associated documentation files (the "Software"), to deal in -// the Software without restriction, including without limitation the rights to -// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -// the Software, and to permit persons to whom the Software is furnished to do so, -// subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import olLayerGroup from 'ol/layer/Group'; - -/** - * @param {import('ol/layer/Base').default} layer A layer tree. - * @param {!import('ol/layer/Group').default[]} ancestors The groups to which the layer belongs to. - * @param {function(import('ol/layer/Base').default, import('ol/layer/Group').default[]): boolean} visitor - * A function which will return false if descend must stop. - */ -export function traverseLayer(layer, ancestors, visitor) { - const descend = visitor(layer, ancestors); - if (descend && layer instanceof olLayerGroup) { - layer.getLayers().forEach((childLayer) => { - traverseLayer(childLayer, [...ancestors, layer], visitor); - }); - } -} - -const extractor = new RegExp('[^/]*//[^/]+/(.*)'); - -/** - * Extract the part after the URL authority. - * - * @param {string} url A URL to normalize - * @returns {string} The normalized string. - */ -export function normalizeURL(url) { - const matches = extractor.exec(url); - if (!matches) { - throw new Error('Could not normalize url ' + url); - } - return matches[1]; -} diff --git a/src/options.js b/src/options.js index d1924c16dad8..7a678e4ef89a 100644 --- a/src/options.js +++ b/src/options.js @@ -153,12 +153,6 @@ export function buildStyle(styleDescriptor) { * @typedef {import('ol/style/Style').StyleLike|Style[]|Style} StyleLike */ -/** - * URL of the test page to detect online/offline. - * - * @typedef {string} ngeoOfflineTestUrl - */ - /** * URL to the WFS server. * diff --git a/srcapi/store/config.ts b/srcapi/store/config.ts index 285cd0e02571..3e0b462cfb87 100644 --- a/srcapi/store/config.ts +++ b/srcapi/store/config.ts @@ -250,11 +250,6 @@ export function buildStyle(styleDescriptor: StyleLike): OlStyleStyleStyleLike { } } -/** - * URL of the test page to detect online/offline. - */ -export type ngeoOfflineTestUrl = string; - /** * URL to the WFS server. */ @@ -1405,7 +1400,6 @@ export type gmfDatasourceOptions = { */ export type Configuration = { authenticationBaseUrl: authenticationBaseUrl; - ngeoOfflineTestUrl: ngeoOfflineTestUrl; ngeoPermalinkOgcserverUrl: ngeoPermalinkOgcserverUrl; ngeoNominatimSearchDefaultParams: ngeoNominatimSearchDefaultParams; ngeoQueryOptions: ngeoQueryOptions;