From 0387abaad5ec83256f63467d9795aecd457ab948 Mon Sep 17 00:00:00 2001 From: temi Date: Thu, 1 Feb 2024 15:20:05 +1100 Subject: [PATCH] - checking in for testing --- grails-app/assets/javascripts/MapUtilities.js | 4 +- grails-app/assets/javascripts/pwa-index.js | 109 +++++++++++++++--- grails-app/assets/javascripts/sw.js | 11 +- grails-app/conf/application.groovy | 7 +- grails-app/conf/application.yml | 7 +- grails-app/views/bioActivity/pwa.gsp | 10 +- .../pwaBioActivityCreateOrEdit.gsp | 2 +- .../views/bioActivity/pwaBioActivityIndex.gsp | 2 +- 8 files changed, 122 insertions(+), 30 deletions(-) diff --git a/grails-app/assets/javascripts/MapUtilities.js b/grails-app/assets/javascripts/MapUtilities.js index b33f28484..1f29f251b 100644 --- a/grails-app/assets/javascripts/MapUtilities.js +++ b/grails-app/assets/javascripts/MapUtilities.js @@ -191,11 +191,11 @@ Biocollect.MapUtilities = { break; case 'maptilersatellite': option = { - url: url || 'https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=O11Deo7fBLatChkUYGIH', + url: url || 'https://api.maptiler.com/maps/hybrid/256/{z}/{x}/{y}.jpg?key=O11Deo7fBLatChkUYGIH', options: { attribution: '© MapTiler © OpenStreetMap contributors', maxZoom: 21, - maxNativeZoom: 13 + maxNativeZoom: 21 } }; layer = L.tileLayer(option.url, option.options); diff --git a/grails-app/assets/javascripts/pwa-index.js b/grails-app/assets/javascripts/pwa-index.js index b5b5ea8dc..5cd13d8cd 100644 --- a/grails-app/assets/javascripts/pwa-index.js +++ b/grails-app/assets/javascripts/pwa-index.js @@ -1,8 +1,9 @@ async function downloadMapTiles(bounds, tileUrl, minZoom, maxZoom, callback) { minZoom = minZoom || 0; // Minimum zoom level maxZoom = maxZoom || 20; // Maximum zoom level + const MAX_PARALLEL_REQUESTS = 10; - var deferred = $.Deferred(); + var deferred = $.Deferred(), requestArray = []; // Check if the browser supports the Cache API if ('caches' in window) { // Function to fetch and cache the vector basemap tiles for a bounding box at different zoom levels @@ -30,14 +31,19 @@ async function downloadMapTiles(bounds, tileUrl, minZoom, maxZoom, callback) { if (!cachedResponse) { console.log(`Tile at zoom ${zoom}, x ${x}, y ${y} not found in cache. Fetching and caching...`); - // Fetch the tile from the server - const response = await fetch(requestUrl); - - // Clone the response, as it can only be consumed once - const responseClone = response.clone(); + // run x number of queries in parallel + if (requestArray.length <= MAX_PARALLEL_REQUESTS) { + requestArray.push(fetch(requestUrl).then(function (response) { + // Clone the response, as it can only be consumed once + const responseClone = response.clone(); - // Cache the response - await cache.put(requestUrl, responseClone); + // Cache the response + cache.put(requestUrl, responseClone); + })); + } else { + await Promise.all(requestArray); + requestArray = []; + } console.log(`Tile at zoom ${zoom}, x ${x}, y ${y} cached.`); } else { @@ -52,6 +58,9 @@ async function downloadMapTiles(bounds, tileUrl, minZoom, maxZoom, callback) { } } + if (requestArray.length > 0) { + await Promise.all(requestArray); + } console.log('Vector basemap tiles cached for the bounding box.'); deferred.resolve(); } catch (error) { @@ -184,18 +193,25 @@ function OfflineViewModel(config) { minZoom = config.minZoom || 0, maxZoom = config.maxZoom || 20, mapId = config.mapId, + overlayLayersMapControlConfig = Biocollect.MapUtilities.getOverlayConfig(), mapOptions = { autoZIndex: false, + zoomToObject: true, preserveZIndex: true, addLayersControlHeading: false, drawControl: false, showReset: false, draggableMarkers: false, useMyLocation: true, + maxAutoZoom: maxZoom, + maxZoom: maxZoom, + minZoom: minZoom, allowSearchLocationByAddress: true, allowSearchRegionByAddress: true, trackWindowHeight: false, - baseLayer: L.tileLayer(config.baseMapUrl, config.baseMapOptions) + baseLayer: L.tileLayer(config.baseMapUrl, config.baseMapOptions), + wmsFeatureUrl: overlayLayersMapControlConfig.wmsFeatureUrl, + wmsLayerUrl: overlayLayersMapControlConfig.wmsLayerUrl }, alaMap = new ALA.Map(mapId, mapOptions), mapImpl = alaMap.getMapImpl(), @@ -390,33 +406,90 @@ function OfflineViewModel(config) { return area; } + /** + * Downloads base map tiles and wms layer of a site for offline use. + * It is done for all sites of a project activity. + * @returns {Promise} + */ async function startDownloadingSites() { - var sites = pa.sites || [], zoom = 15; + const TIMEOUT = 3000, // 3 seconds + MAP_LOAD_TIMEOUT = 2000, // 2 seconds + MAX_ZOOM=20, + MIN_ZOOM= 10; + var sites = pa.sites || [], zoom = 15, mapZoomedInIndicator, tileLoadedPromise, cancelTimer, + callback = function () { + cancelTimer && clearTimeout(cancelTimer); + cancelTimer = null; + // resolve it in the next event loop + if(mapZoomedInIndicator && mapZoomedInIndicator.state() == 'pending') { + // setTimeout(function () { + mapZoomedInIndicator && mapZoomedInIndicator.resolve(); + // }, 0); + } + }; self.currentStage(self.stages.sites); self.sitesStatus(self.statuses.doing); + alaMap.registerListener('dataload', callback); + try { self.numberOfSiteTilesDownloaded(0); self.totalSiteTilesDownload(sites.length); for (var i = 0; i < sites.length; i++) { var site = sites[i], + zoomIntoMap = true, geoJson = Biocollect.MapUtilities.featureToValidGeoJson(site.extent.geometry), - layer = L.geoJson(geoJson), - bounds = layer.getBounds(); - - alaMap.addLayer(layer); - alaMap.fitBounds(); - zoom = mapImpl.getZoom(); - alaMap.removeLayer(layer); - await downloadMapTiles(bounds, config.baseMapUrl, zoom, zoom); + geoJsonLayer = alaMap.setGeoJSON(geoJson, { + wmsFeatureUrl: overlayLayersMapControlConfig.wmsFeatureUrl, + wmsLayerUrl: overlayLayersMapControlConfig.wmsLayerUrl, + maxZoom: MAX_ZOOM + }); + + geoJsonLayer.on('tileload', function () { + if(tileLoadedPromise && tileLoadedPromise.state() == 'pending') { + tileLoadedPromise.resolve(); + } + }); + // so that layer zooms beyond default max zoom of 18 + geoJsonLayer.options.maxZoom = MAX_ZOOM; + mapZoomedInIndicator = $.Deferred(); + // cancel waiting for map to load feature data + cancelTimer = setTimeout(function (){ + zoomIntoMap = false; + mapZoomedInIndicator && mapZoomedInIndicator.resolve(); + }, TIMEOUT); + + // no need to wait if promise is resolved. + if (mapZoomedInIndicator && mapZoomedInIndicator.state() == 'pending') { + // wait for map layer to load feature data from spatial server for pid. + await mapZoomedInIndicator.promise(); + } + + if (zoomIntoMap) { + // zoom into to map to get tiles and feature from spatial server + for (zoom = MIN_ZOOM; zoom <= MAX_ZOOM; zoom++) { + tileLoadedPromise = $.Deferred(); + mapImpl.setZoom(zoom, {animate: false}); + timer(MAP_LOAD_TIMEOUT, tileLoadedPromise); + await tileLoadedPromise.promise(); + } + } + + alaMap.clearLayers(); self.numberOfSiteTilesDownloaded(self.numberOfSiteTilesDownloaded() + 1); } + alaMap.removeListener('dataload', callback); completedSitesDownload(); } catch (e) { + console.error(e); errorSitesDownload(); } } + function timer(ms, deferred) { + return setTimeout(deferred.resolve, ms); + } + function completedSitesDownload() { updateSitesProgressBar(self.totalCount(), self.totalCount()); self.sitesStatus(self.statuses.done); diff --git a/grails-app/assets/javascripts/sw.js b/grails-app/assets/javascripts/sw.js index e0a86d84d..75244aa4c 100644 --- a/grails-app/assets/javascripts/sw.js +++ b/grails-app/assets/javascripts/sw.js @@ -54,7 +54,11 @@ self.addEventListener('fetch', e => { return res; } else if (isFetchingBaseMap(e.request.url)) { - return fetch(pwaConfig.noCacheTileFile); + return caches.match(pwaConfig.noCacheTileFile).then(res => { + if (res) { + return res; + } + }); } }); } @@ -97,6 +101,11 @@ function isFetchingBaseMap (url) { async function precache() { const cache = await caches.open(pwaConfig.cacheName); + + for(var i = 0; i < pwaConfig.filesToPreCache.length; i++) { + await cache.delete(pwaConfig.filesToPreCache[i]); + } + return cache.addAll(pwaConfig.filesToPreCache); } console.debug("SW Script: end reading"); \ No newline at end of file diff --git a/grails-app/conf/application.groovy b/grails-app/conf/application.groovy index 62830ce6d..00e855417 100644 --- a/grails-app/conf/application.groovy +++ b/grails-app/conf/application.groovy @@ -676,6 +676,9 @@ if (!app.file.script.path) { script.read.extensions.list = ['js','min.js','png', 'json', 'jpg', 'jpeg'] // yml interpreter doesn't evaluate expression in deep nested objects such as baseLayers below -if (pwa.mapConfig.baseLayers?.size() > 1) { - pwa.mapConfig.baseLayers[0].url = pwa.baseMapUrl +pwaMapConfig = { def config -> + Map pwa = config.getProperty('pwa', Map) + Map mapConfig = pwa.mapConfig + mapConfig.baseLayers.getAt(0).url = pwa.baseMapUrl + pwa.apiKey + mapConfig } \ No newline at end of file diff --git a/grails-app/conf/application.yml b/grails-app/conf/application.yml index 81ebd051f..7c4606799 100644 --- a/grails-app/conf/application.yml +++ b/grails-app/conf/application.yml @@ -315,7 +315,7 @@ pwa: cache: ignore: ["/image/upload", "/ws/attachment/upload"] maxAreaInKm: 25 - tileSize: 512 + tileSize: 256 apiKey: "" cacheVersion: "v3" oldCacheToDelete: "v2" @@ -323,14 +323,15 @@ pwa: pathsToIgnoreCache: [ "/image/upload", "/ws/attachment/upload", "/ajax/keepSessionAlive", "/noop", '/pwa/sw.js', '/pwa/config.js', "/ws/species/speciesDownload" ] cachePathForRequestsStartingWith: [ "/pwa/bioActivity/edit/", "/pwa/createOrEditFragment/", "/pwa/bioActivity/index/", "/pwa/indexFragment/", "/pwa/offlineList" ] filesToPreCache: ["webjars/leaflet/0.7.7/dist/images/layers.png", "webjars/leaflet/0.7.7/dist/images/layers-2x.png", "webjars/leaflet/0.7.7/dist/images/marker-icon.png", "webjars/leaflet/0.7.7/dist/images/marker-icon-2x.png", "webjars/leaflet/0.7.7/dist/images/marker-shadow.png", "map-not-cached.png", "font-awesome/5.15.4/svgs/regular/image.svg"] - baseMapPrefixUrl: "https://api.maptiler.com/maps/hybrid" + baseMapPrefixUrl: "https://api.maptiler.com/maps/hybrid/256" noCacheTileFile: "map-not-cached.png" - baseMapUrl: "${pwa.serviceWorkerConfig.baseMapPrefixUrl}/{z}/{x}/{y}.jpg?key=${pwa.apiKey}" + baseMapUrl: "${pwa.serviceWorkerConfig.baseMapPrefixUrl}/{z}/{x}/{y}.jpg?key=" mapConfig: baseLayers: - code: 'maptilersatellite' displayText: 'Satellite' isSelected: true + attribution: '© MapTiler © OpenStreetMap contributors' overlays: [ ] --- diff --git a/grails-app/views/bioActivity/pwa.gsp b/grails-app/views/bioActivity/pwa.gsp index 98c8754be..e26fcfb0d 100644 --- a/grails-app/views/bioActivity/pwa.gsp +++ b/grails-app/views/bioActivity/pwa.gsp @@ -25,9 +25,15 @@ var params = getParams(), initialized = false; var fcConfig = { + intersectService: "${createLink(controller: 'proxy', action: 'intersect')}", + featuresService: "${createLink(controller: 'proxy', action: 'features')}", + featureService: "${createLink(controller: 'proxy', action: 'feature')}", + spatialWms: "${grailsApplication.config.spatial.geoserverUrl}", + layersStyle: "${createLink(controller: 'regions', action: 'layersStyle')}", createActivityUrl: "/pwa/bioActivity/edit/" + params.projectActivityId + "?cache=true", indexActivityUrl: "/pwa/bioActivity/index/" + params.projectActivityId+ "?cache=true", - baseMapUrl: "${grailsApplication.config.getProperty("pwa.baseMapUrl")}", + baseMapUrl: "${grailsApplication.config.getProperty("pwa.baseMapUrl")}${grailsApplication.config.getProperty("pwa.apiKey")}", + baseMapAttribution: "${grailsApplication.config.getProperty("pwa.mapConfig.baseLayers", List)?.getAt(0)?.attribution?.encodeAsJavaScript()}", fetchSpeciesUrl: "${createLink(controller: 'search', action: 'searchSpecies')}", metadataURL: "/ws/projectActivity/activity", siteUrl: '${createLink(controller: 'site', action: 'index' )}', @@ -78,7 +84,7 @@ totalUrl: fcConfig.totalUrl, downloadSpeciesUrl: fcConfig.downloadSpeciesUrl, baseMapOptions: { - attribution: 'Tiles from Esri — Sources: Esri, DigitalGlobe, Earthstar Geographics, CNES/Airbus DS, GeoEye, USDA FSA, USGS, Aerogrid, IGN, IGP, and the GIS User Community', + attribution: fcConfig.baseMapAttribution, maxZoom: 20 } }); diff --git a/grails-app/views/bioActivity/pwaBioActivityCreateOrEdit.gsp b/grails-app/views/bioActivity/pwaBioActivityCreateOrEdit.gsp index 345b072b0..3d76d8036 100644 --- a/grails-app/views/bioActivity/pwaBioActivityCreateOrEdit.gsp +++ b/grails-app/views/bioActivity/pwaBioActivityCreateOrEdit.gsp @@ -53,7 +53,7 @@ getOutputSpeciesIdUrl : "${createLink(controller: 'output', action: 'getOutputSpeciesIdentifier')}", getGuidForOutputSpeciesUrl : "${createLink(controller: 'record', action: 'getGuidForOutputSpeciesIdentifier')}", imageLeafletViewer: '${createLink(controller: 'resource', action: 'imageviewer', absolute: true)}', - mapLayersConfig: ${ grailsApplication.config.getProperty('pwa.mapConfig', Map) as JSON }, + mapLayersConfig: ${ grailsApplication.config.getProperty('pwaMapConfig', Closure)(grailsApplication.config) as JSON }, excelOutputTemplateUrl: "${createLink(controller: 'proxy', action:'excelOutputTemplate')}", uploadImagesUrl: "${createLink(controller: 'image', action: 'upload')}", originUrl: "${grailsApplication.config.getProperty('server.serverURL')}", diff --git a/grails-app/views/bioActivity/pwaBioActivityIndex.gsp b/grails-app/views/bioActivity/pwaBioActivityIndex.gsp index 786236e71..81e564180 100644 --- a/grails-app/views/bioActivity/pwaBioActivityIndex.gsp +++ b/grails-app/views/bioActivity/pwaBioActivityIndex.gsp @@ -44,7 +44,7 @@ speciesProfileUrl: "${createLink(controller: 'proxy', action: 'speciesProfile')}", noImageUrl: '${asset.assetPath(src: "font-awesome/5.15.4/svgs/regular/image.svg")}', speciesImageUrl:"${createLink(controller:'species', action:'speciesImage')}", - mapLayersConfig: ${ grailsApplication.config.getProperty('pwa.mapConfig', Map) as JSON }, + mapLayersConfig: ${ grailsApplication.config.getProperty('pwaMapConfig', Closure)(grailsApplication.config) as JSON }, excelOutputTemplateUrl: "${createLink(controller: 'proxy', action:'excelOutputTemplate')}", pwaAppUrl: "${grailsApplication.config.getProperty('pwa.appUrl')}", bulkUpload: false,