diff --git a/configure/src/metaconfigs/layer-model-config.json b/configure/src/metaconfigs/layer-model-config.json index 717e07f59..ead185103 100644 --- a/configure/src/metaconfigs/layer-model-config.json +++ b/configure/src/metaconfigs/layer-model-config.json @@ -835,6 +835,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-query-config.json b/configure/src/metaconfigs/layer-query-config.json index bdb203a0c..2f43290ed 100644 --- a/configure/src/metaconfigs/layer-query-config.json +++ b/configure/src/metaconfigs/layer-query-config.json @@ -984,6 +984,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-vector-config.json b/configure/src/metaconfigs/layer-vector-config.json index e7522b1af..02eb19b99 100644 --- a/configure/src/metaconfigs/layer-vector-config.json +++ b/configure/src/metaconfigs/layer-vector-config.json @@ -931,6 +931,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/configure/src/metaconfigs/layer-vectortile-config.json b/configure/src/metaconfigs/layer-vectortile-config.json index 130cdced3..bef1c294a 100644 --- a/configure/src/metaconfigs/layer-vectortile-config.json +++ b/configure/src/metaconfigs/layer-vectortile-config.json @@ -892,6 +892,13 @@ "description": "This is a property key already within the features properties. It's value will be searched for in the specified dataset column.", "type": "text", "width": 3 + }, + { + "field": "displayProp", + "name": "Connecting Feature Display Name Property", + "description": "This is a property key within the returned dataset feature properties. This is effectively an optional second-order property key. In the case of multiple dataset row entries being returned, this is useful for users of the UI to be able to distinguish between them. If unset, defaults to the value of 'Connecting Feature Property'.", + "type": "text", + "width": 3 } ] } diff --git a/src/essence/Ancillary/Datasets.js b/src/essence/Ancillary/Datasets.js new file mode 100644 index 000000000..54adc29a1 --- /dev/null +++ b/src/essence/Ancillary/Datasets.js @@ -0,0 +1,62 @@ +import F_ from '../Basics/Formulae_/Formulae_' +import calls from '../../pre/calls' + +const Datasets = { + populateFromDataset(layer, cb) { + if ( + layer.options.layerName && + L_.layers.data[layer.options.layerName] && + L_.layers.data[layer.options.layerName].variables && + L_.layers.data[layer.options.layerName].variables.datasetLinks + ) { + const dl = + L_.layers.data[layer.options.layerName].variables.datasetLinks + let dlFilled = dl + for (let i = 0; i < dlFilled.length; i++) { + dlFilled[i].search = F_.getIn( + layer.feature.properties, + dlFilled[i].prop.split('.') + ) + } + + calls.api( + 'datasets_get', + { + queries: JSON.stringify(dlFilled), + }, + function (data) { + const d = data.body + for (let i = 0; i < d.length; i++) { + if (d[i].type == 'images') { + layer.feature.properties.images = + layer.feature.properties.images || [] + for (let j = 0; j < d[i].results.length; j++) { + layer.feature.properties.images.push( + d[i].results[j] + ) + } + //remove duplicates + layer.feature.properties.images = + F_.removeDuplicatesInArrayOfObjects( + layer.feature.properties.images + ) + } else { + layer.feature.properties._dataset = { + prop: dlFilled[i].displayProp || d[i].prop, + results: d[i].results, + } + } + } + if (cb != null && typeof cb === 'function') cb() + }, + function (data) { + if (cb != null && typeof cb === 'function') cb() + } + ) + } else { + if (cb != null && typeof cb === 'function') cb() + } + }, +} + +export default Datasets diff --git a/src/essence/Basics/Layers_/Layers_.js b/src/essence/Basics/Layers_/Layers_.js index a9d0db491..94fbdb685 100644 --- a/src/essence/Basics/Layers_/Layers_.js +++ b/src/essence/Basics/Layers_/Layers_.js @@ -2054,6 +2054,8 @@ const L_ = { const featureWithout_ = JSON.parse(JSON.stringify(f)) if (featureWithout_.properties?._ != null) delete featureWithout_.properties._ + if (featureWithout_.properties?._dataset != null) + delete featureWithout_.properties._dataset for (let i = 0; i < layerKeys.length; i++) { const l = layerKeys[i] diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 2f739f020..241c8f75a 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -14,6 +14,7 @@ import ToolController_ from '../ToolController_/ToolController_' import CursorInfo from '../../Ancillary/CursorInfo' import Description from '../../Ancillary/Description' import QueryURL from '../../Ancillary/QueryURL' +import Datasets from '../../Ancillary/Datasets' import { Kinds } from '../../../pre/tools' import DataShaders from '../../Ancillary/DataShaders' import calls from '../../../pre/calls' @@ -415,7 +416,9 @@ let Map_ = { Map_.map.addLayer(L_.layers.layer[L_._layersOrdered[hasIndex[i]]]) // If image layer, reorder the z index and redraw the layer - if (L_.layers.data[L_._layersOrdered[hasIndex[i]]].type === 'image') { + if ( + L_.layers.data[L_._layersOrdered[hasIndex[i]]].type === 'image' + ) { L_.layers.layer[L_._layersOrdered[hasIndex[i]]].setZIndex( L_._layersOrdered.length + 1 - @@ -730,60 +733,7 @@ function featureDefaultClick(feature, layer, e) { ToolController_.activeTool.disableLayerInteractions === true ) return - - //Query dataset links if possible and add that data to the feature's properties - if ( - layer.options.layerName && - L_.layers.data[layer.options.layerName] && - L_.layers.data[layer.options.layerName].variables && - L_.layers.data[layer.options.layerName].variables.datasetLinks - ) { - const dl = - L_.layers.data[layer.options.layerName].variables.datasetLinks - let dlFilled = dl - for (let i = 0; i < dlFilled.length; i++) { - dlFilled[i].search = F_.getIn( - layer.feature.properties, - dlFilled[i].prop.split('.') - ) - } - - calls.api( - 'datasets_get', - { - queries: JSON.stringify(dlFilled), - }, - function (data) { - const d = data.body - for (let i = 0; i < d.length; i++) { - if (d[i].type == 'images') { - layer.feature.properties.images = - layer.feature.properties.images || [] - for (let j = 0; j < d[i].results.length; j++) { - layer.feature.properties.images.push( - d[i].results[j] - ) - } - //remove duplicates - layer.feature.properties.images = - F_.removeDuplicatesInArrayOfObjects( - layer.feature.properties.images - ) - } else { - layer.feature.properties._data = d[i].results - } - } - keepGoing() - }, - function (data) { - keepGoing() - } - ) - } else { - keepGoing() - } - - function keepGoing() { + Datasets.populateFromDataset(layer, () => { Kinds.use( L_.layers.data[layer.options.layerName].kind, Map_, @@ -852,7 +802,7 @@ function featureDefaultClick(feature, layer, e) { } QueryURL.writeSearchURL([searchStr], layer.options.layerName) - } + }) } //Pretty much like makePointLayer but without the pointToLayer stuff @@ -1274,27 +1224,28 @@ function makeVectorTileLayer(layerObj) { let ell = { latlng: null } if (e.latlng != null) ell.latlng = JSON.parse(JSON.stringify(e.latlng)) + Datasets.populateFromDataset(layer, () => { + Kinds.use( + L_.layers.data[layerName].kind, + Map_, + L_.layers.layer[layerName].activeFeatures[0], + layer, + layerName, + null, + ell + ) - Kinds.use( - L_.layers.data[layerName].kind, - Map_, - L_.layers.layer[layerName].activeFeatures[0], - layer, - layerName, - null, - ell - ) - - ToolController_.getTool('InfoTool').use( - layer, - layerName, - L_.layers.layer[layerName].activeFeatures, - null, - null, - null, - ell - ) - L_.layers.layer[layerName].activeFeatures = [] + ToolController_.getTool('InfoTool').use( + layer, + layerName, + L_.layers.layer[layerName].activeFeatures, + null, + null, + null, + ell + ) + L_.layers.layer[layerName].activeFeatures = [] + }) } })(layer, layerName, e), 100 @@ -1469,142 +1420,166 @@ function makeImageLayer(layerObj) { ) } - const cogColormap = F_.getIn( - L_.layers.data[layerObj.name], - 'cogColormap' - ) + const cogColormap = F_.getIn(L_.layers.data[layerObj.name], 'cogColormap') - parseGeoraster(layerUrl).then((georaster) => { - let pixelValuesToColorFn = null; - if (F_.getIn( - L_.layers.data[layerObj.name], - 'variables.hideNoDataValue' - ) === true) { - pixelValuesToColorFn = (values) => { - // https://github.com/GeoTIFF/georaster-layer-for-leaflet/issues/16 - return values[0] === georaster.noDataValue ? null : `rgb(${values[0]},${values[1]},${values[2]})` + parseGeoraster(layerUrl) + .then((georaster) => { + let pixelValuesToColorFn = null + if ( + F_.getIn( + L_.layers.data[layerObj.name], + 'variables.hideNoDataValue' + ) === true + ) { + pixelValuesToColorFn = (values) => { + // https://github.com/GeoTIFF/georaster-layer-for-leaflet/issues/16 + return values[0] === georaster.noDataValue + ? null + : `rgb(${values[0]},${values[1]},${values[2]})` + } } - } - const imageInfo = F_.getIn( - L_.layers.data[layerObj.name], - 'variables.image' - ) - - let min = null - let max = null - if (georaster.numberOfRasters === 1) { - min = layerObj.cogMin - max = layerObj.cogMax - - if (isNaN(parseFloat(layerObj.cogMin)) || isNaN(parseFloat(layerObj.cogMax))) { - let path - if (layerObj.url.startsWith('http')) path = layerObj.url - else path = 'Missions/' + L_.mission + '/' + layerObj.url - - // Try to get the min and max values using gdal if the user did not input min/max in the layer config - $.ajax({ - type: calls.getminmax.type, - url: calls.getminmax.url, - data: { - type: 'minmax', - path: calls.getprofile.pathprefix + path, - bands: '[1]', // Assume the geotiff images only have a single band - }, - async: false, - success: function (data) { - if (data && data[0] && data[0].band && data[0].band === 1) { - if (isNaN(parseFloat(layerObj.cogMin))) { - min = data[0].min - layerObj.cogMin = min - } - if (isNaN(parseFloat(layerObj.cogMax))) { - max = data[0].max - layerObj.cogMax = max - } - } - }, - error: function (request, status, error) { - console.warn(`Failed to get gdal minmax info for ${layerObj.name}`, request, status, error) - }, - }) + const imageInfo = F_.getIn( + L_.layers.data[layerObj.name], + 'variables.image' + ) - } + let min = null + let max = null + if (georaster.numberOfRasters === 1) { + min = layerObj.cogMin + max = layerObj.cogMax - // FIXME A lot of this code is duplicated in LayersTool so find some way to consolidate them as functions - var range = max - min - let colormap = null - let reverse = false - if (layerObj.cogTransform === true && 'cogColormap' in layerObj) { - colormap = layerObj.cogColormap - // TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps - if (colormap.toLowerCase().endsWith('_r')) { - colormap = colormap.substring(0, colormap.length - 2) - reverse = true + if ( + isNaN(parseFloat(layerObj.cogMin)) || + isNaN(parseFloat(layerObj.cogMax)) + ) { + let path + if (layerObj.url.startsWith('http')) path = layerObj.url + else path = 'Missions/' + L_.mission + '/' + layerObj.url + + // Try to get the min and max values using gdal if the user did not input min/max in the layer config + $.ajax({ + type: calls.getminmax.type, + url: calls.getminmax.url, + data: { + type: 'minmax', + path: calls.getprofile.pathprefix + path, + bands: '[1]', // Assume the geotiff images only have a single band + }, + async: false, + success: function (data) { + if ( + data && + data[0] && + data[0].band && + data[0].band === 1 + ) { + if (isNaN(parseFloat(layerObj.cogMin))) { + min = data[0].min + layerObj.cogMin = min + } + if (isNaN(parseFloat(layerObj.cogMax))) { + max = data[0].max + layerObj.cogMax = max + } + } + }, + error: function (request, status, error) { + console.warn( + `Failed to get gdal minmax info for ${layerObj.name}`, + request, + status, + error + ) + }, + }) } - let index = Object.keys(colormapData).findIndex(v => { - return v.toLowerCase() === colormap.toLowerCase(); - }); + // FIXME A lot of this code is duplicated in LayersTool so find some way to consolidate them as functions + var range = max - min + let colormap = null + let reverse = false + if ( + layerObj.cogTransform === true && + 'cogColormap' in layerObj + ) { + colormap = layerObj.cogColormap + // TiTiler colormap variables are all lower case so we need to format them correctly for js-colormaps + if (colormap.toLowerCase().endsWith('_r')) { + colormap = colormap.substring(0, colormap.length - 2) + reverse = true + } - if (index > -1) { - colormap = Object.keys(colormapData)[index] + let index = Object.keys(colormapData).findIndex((v) => { + return v.toLowerCase() === colormap.toLowerCase() + }) + + if (index > -1) { + colormap = Object.keys(colormapData)[index] + } else { + colormap = 'binary' // Give it the default value + } } else { colormap = 'binary' // Give it the default value } - } else { - colormap = 'binary' // Give it the default value - } - pixelValuesToColorFn = (values) => { - var pixelValue = values[0]; // single band - // don't return a color - if (georaster.noDataValue && georaster.noDataValue === pixelValue) { - return null; - } + pixelValuesToColorFn = (values) => { + var pixelValue = values[0] // single band + // don't return a color + if ( + georaster.noDataValue && + georaster.noDataValue === pixelValue + ) { + return null + } - // scale from 0 - 1 - var scaledPixelValue = (pixelValue - min) / range; - if (!(scaledPixelValue >= 0 && scaledPixelValue <= 1)) { - if (imageInfo && imageInfo.fillMinMax) { - if (scaledPixelValue <= 0) { - scaledPixelValue = 0 - } else if (scaledPixelValue >= 1.0) { - scaledPixelValue = 1 + // scale from 0 - 1 + var scaledPixelValue = (pixelValue - min) / range + if (!(scaledPixelValue >= 0 && scaledPixelValue <= 1)) { + if (imageInfo && imageInfo.fillMinMax) { + if (scaledPixelValue <= 0) { + scaledPixelValue = 0 + } else if (scaledPixelValue >= 1.0) { + scaledPixelValue = 1 + } + } else { + return null } - } else { - return null } - } - return evaluate_cmap(scaledPixelValue, colormap || IMAGE_DEFAULT_COLOR_RAMP, reverse) + return evaluate_cmap( + scaledPixelValue, + colormap || IMAGE_DEFAULT_COLOR_RAMP, + reverse + ) + } } - } - L_.layers.layer[layerObj.name] = new GeoRasterLayer({ - georaster: georaster, - resolution: 256, - opacity: 1.0, - pixelValuesToColorFn: pixelValuesToColorFn, - }) + L_.layers.layer[layerObj.name] = new GeoRasterLayer({ + georaster: georaster, + resolution: 256, + opacity: 1.0, + pixelValuesToColorFn: pixelValuesToColorFn, + }) - L_.layers.layer[layerObj.name].clearCache() + L_.layers.layer[layerObj.name].clearCache() - L_.layers.layer[layerObj.name].setZIndex( - L_._layersOrdered.length + - 1 - - L_._layersOrdered.indexOf(layerObj.name) - ) + L_.layers.layer[layerObj.name].setZIndex( + L_._layersOrdered.length + + 1 - + L_._layersOrdered.indexOf(layerObj.name) + ) - L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) + L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) - L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true - allLayersLoaded() - }) - .catch((e) => { - console.warn('Unable to load image') - return null - }); + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() + }) + .catch((e) => { + console.warn('Unable to load image') + return null + }) } //Because some layers load faster than others, check to see if diff --git a/src/essence/Tools/Info/InfoTool.css b/src/essence/Tools/Info/InfoTool.css index c45547aa3..00c1fb24b 100644 --- a/src/essence/Tools/Info/InfoTool.css +++ b/src/essence/Tools/Info/InfoTool.css @@ -115,6 +115,19 @@ border-top: 1px solid var(--color-a-5); } +#infoToolSelectedDatasetDropdown { + width: 100%; + height: 40px; + background: var(--color-a1-5); + border-bottom: 1px solid var(--color-a-5); +} + +#infoToolSelectedDatasetDropdown .dropy__title span { + color: var(--color-h); + font-size: 14px; + padding-top: 12px; +} + #infoToolFilter { display: flex; height: 36px; @@ -189,8 +202,8 @@ } #infoToolData > li > div:first-child { - max-width: 86px; - min-width: 86px; + max-width: 33%; + min-width: 33%; color: white; text-overflow: ellipsis; overflow: hidden; diff --git a/src/essence/Tools/Info/InfoTool.js b/src/essence/Tools/Info/InfoTool.js index 63a659ff5..c90369cd9 100644 --- a/src/essence/Tools/Info/InfoTool.js +++ b/src/essence/Tools/Info/InfoTool.js @@ -6,9 +6,12 @@ import Map_ from '../../Basics/Map_/Map_' import { Kinds } from '../../../pre/tools' import Dropy from '../../../external/Dropy/dropy' +import Datasets from '../../Ancillary/Datasets' import Help from '../../Ancillary/Help' import ConfirmationModal from '../../Ancillary/ConfirmationModal' +import tippy from 'tippy.js' + import './InfoTool.css' const helpKey = 'InfoTool' @@ -48,6 +51,9 @@ var markup = [ "", "", "", + "