geosolutions-it / MapStore2

The solution to create and share maps, dashboards, geostories with 3D support on the web. And it is open-source too!
https://mapstore.geosolutionsgroup.com/
Other
503 stars 391 forks source link

support loading COG layers from query params #9527

Closed landryb closed 5 months ago

landryb commented 11 months ago

Description

now that we have COG support since #9320 im looking at how to implement support for loading such data type from the url params eg http://localhost:8081/#/viewer/new?actions=[{%22type%22:%22CATALOG:ADD_LAYERS_FROM_CATALOGS%22,%22layers%22:[%22title%22],%22sources%22:[{%22type%22:%22cog%22,%22url%22:%22https%3A%2F%2Fcogeo.craig.fr%2Fopendata%2Fortho%2Forthocraig3_vichy_2021.cog.tif%22}]}]

for now i only changed one thing so that the layer title is not looked for in the catalog records like its done for 3dtiles (#9021) & geojson (#9276):

--- a/web/client/epics/catalog.js
+++ b/web/client/epics/catalog.js
@@ -163,7 +163,7 @@ export default (API) => ({
                         const layerOptionsParam = get(options, i, searchOptionsSelector(state));
                         // use the selected layer text as title for 3d tiles
                         // because currently we get only a single record for this service type
-                        const layerOptions = format === '3dtiles' || format === 'geojson'
+                        const layerOptions = format === '3dtiles' || format === 'geojson' || format === 'cog'
                             ? {
                                 ...layerOptionsParam,
                                 title: isObject(layerOptionsParam?.title)

but no layer is loaded. From my digging in the browser debugger, i understand that's because service.records is empty in https://github.com/geosolutions-it/MapStore2/blob/master/web/client/api/catalog/COG.js#L65, probably because searchAndPaginate hasnt been called yet, and that method will return no records anyway since layers is an empty array ? so ... does it make sense to have a searchAndPaginate method in a COG catalog type ? afaict this method isnt present for geojson and 3dtiles formats... @dsuren1, any idea ?

landryb commented 11 months ago

after more digging.. service.records is populated in GetRecords when coming from the regular 'add layers' widget, but i can't figure out yet what populates it. That's definitely puzzling.

dsuren1 commented 11 months ago

@landryb Try passing this url.

URL http://localhost:8081/#/viewer/new?actions=[{%22type%22:%22CATALOG:ADD_LAYERS_FROM_CATALOGS%22,%22layers%22:[%22%22],%22sources%22:[{%22type%22:%22cog%22,%20%22records%22:[{%22title%22:%20%22orthocraig3_vichy_2021.cog%22,%20%22url%22:%22https://cogeo.craig.fr/opendata/ortho/orthocraig3_vichy_2021.cog.tif%22}]}]}]

It needs some update on catalog epic too. But if you like to try it locally, you can modify this line to something like this const record = head(records.filter(rec => rec.identifier || rec.name === text || (rec.type === 'cog' && text ? rec.title.inludes(text) : true)))

landryb commented 11 months ago

thanks, with that tweak and that url it 'works' (but wont zoom on the layer nor allow zoom to layer)

i see where this is going, but it feels wrong to have to construct a specific url for cog format while we should be able to use the same pattern as for other catalog types.. ofc, the goal is to have a generic API to be able to build the URL from client code (in my case, from geonetwork) without specific cases.

wouldn't it be possible to rework COG.js catalog type to be 'like' the others and have records array directly populated with the single entry that is available, constructed from url & title ? Unless there are plans to support multiple layers in a tiff but i doubt that's a usecase...

and loading a COG directly from queryparams, to me we want to directly zoom on it so fetchMetadata should default to true in that case.

landryb commented 11 months ago

tried with this to have a default layer entry created on the fly (because _layers is empty in the 'add from query param' case)

--- a/web/client/api/catalog/COG.js
+++ b/web/client/api/catalog/COG.js
@@ -110,6 +110,13 @@ export const getRecords = (_url, startPosition, maxRecords, text, info = {}) =>
         });
     }
     return Promise.all([...layers]).then((_layers) => {
+        if (!_layers.length) {
+             _layers = [{
+                title: text,
+                type: COG_LAYER_TYPE,
+                url: _url
+             }];
+        }
         return searchAndPaginate(_layers, startPosition, maxRecords, text);
     });
 };

but that crashes when geotiff tries to load it:

Uncaught TypeError: this.sourceInfo_ is undefined
    GeoTIFFSource webpack://mapstore2/./node_modules/ol/source/GeoTIFF.js?:401
    create webpack://mapstore2/./web/client/components/map/openlayers/plugins/COGLayer.js?:23
    createLayer webpack://mapstore2/./web/client/utils/openlayers/Layers.js?:28
    OpenlayersLayer webpack://mapstore2/./web/client/components/map/openlayers/Layer.jsx?:96
    componentDidMount webpack://mapstore2/./web/client/components/map/openlayers/Layer.jsx?:271
    React 5
vendors-node_modules_eventlistener_eventlistener_js-node_modules_lrucache_index_js-node_modul-2ce0e3.js line 333 > eval:401:24
    GeoTIFFSource webpack://mapstore2/./node_modules/ol/source/GeoTIFF.js?:401
    create webpack://mapstore2/./web/client/components/map/openlayers/plugins/COGLayer.js?:23
    createLayer webpack://mapstore2/./web/client/utils/openlayers/Layers.js?:28
    OpenlayersLayer webpack://mapstore2/./web/client/components/map/openlayers/Layer.jsx?:96
    componentDidMount webpack://mapstore2/./web/client/components/map/openlayers/Layer.jsx?:271
    React 6
    unstable_runWithPriority webpack://mapstore2/./node_modules/scheduler/cjs/scheduler.development.js?:815
    React 5
    unstable_runWithPriority webpack://mapstore2/./node_modules/scheduler/cjs/scheduler.development.js?:815
    React 6
    Redux 3
    routerMiddleware webpack://mapstore2/./node_modules/connected-react-router/esm/middleware.js?:27
    Redux 4
    RxJS 37
    Redux 4
    RxJS 56
dsuren1 commented 11 months ago

@landryb Try setting sources instead of directly url

if (!_layers.length) {
       _layers = [{
       title: text,
       type: COG_LAYER_TYPE,
       sources: [{url: _url}]
      }];
 }
landryb commented 11 months ago

so it works with that suggestion, but isnt satisfying since it wont allow to zoom to layer.

my idea then is to move the metadata/crs/bbox fetching to a separate function, and reuse that... but sadly my wip branch doesnt work yet. I'm probably doing something wrong with promises since sometimes _layers is defined and sometimes it's pending

landryb commented 11 months ago

@landryb Try setting sources instead of directly url

if (!_layers.length) {
       _layers = [{
       title: text,
       type: COG_LAYER_TYPE,
       sources: [{url: _url}]
      }];
 }

on this, eslint will complain (rightfully?) that one isnt supposed to assign to a function parameter, per https://github.com/geosolutions-it/MapStore2/actions/runs/6379936002/job/17313379000