From 203d72e87d4a67ad516bb31f7786dd90f0658559 Mon Sep 17 00:00:00 2001 From: Tariq Soliman Date: Mon, 5 Feb 2024 14:20:09 -0800 Subject: [PATCH] #485 Rover Image Overlay Improvements (#486) * #485 Rover Image Overlay Improvments * #485 Prevent reload/updates to layers through the mmgisAPI if one is already taking place * #485 Support initialOpacity for imageOverlays w/ docs * Remove dev consolelog * #485 Rover Improvement fixes * #485 Image overlay chache works (but is offset badly now) * #485 Fix image_overlay updateVectorLayer flickering * #485 Fix layerObj ref --- config/js/config.js | 1 + docs/pages/APIs/JavaScript/Main/Main.md | 4 + docs/pages/Configure/Kinds/Kinds.md | 1 + docs/pages/Configure/Layers/Vector/Vector.md | 2 + src/essence/Ancillary/TimeControl.js | 4 +- .../Basics/Layers_/LayerConstructors.js | 81 +- src/essence/Basics/Layers_/Layers_.js | 110 +- src/essence/Basics/Map_/Map_.js | 1067 +++++++++-------- src/essence/Tools/Layers/LayersTool.js | 15 +- src/essence/mmgisAPI/mmgisAPI.js | 60 +- .../Leaflet/leaflet-imagetransform.js | 142 ++- 11 files changed, 837 insertions(+), 650 deletions(-) diff --git a/config/js/config.js b/config/js/config.js index 6b00aea7..985f9dfb 100644 --- a/config/js/config.js +++ b/config/js/config.js @@ -2935,6 +2935,7 @@ function layerPopulateVariable(modalId, layerType) { }, image: { initialVisibility: true, + initialOpacity: 1, path: "url to top-down ortho image. ex. public/images/rovers/PerseveranceTopDown.png", pathProp: "path to image. take priority over path", widthMeters: 2.6924, diff --git a/docs/pages/APIs/JavaScript/Main/Main.md b/docs/pages/APIs/JavaScript/Main/Main.md index a1610a7c..a5421541 100644 --- a/docs/pages/APIs/JavaScript/Main/Main.md +++ b/docs/pages/APIs/JavaScript/Main/Main.md @@ -197,6 +197,8 @@ This function updates an existing vector layer with a specified name and valid G - `inputData` - valid GeoJSON data - `keepN` - number of features to keep. A value less than or equal to 0 keeps all previous features +Returns `false` if the layer could not be updated (either some parameters are wrong or that layer is already is the midst of being loaded). + The following is an example of how to call the `updateVectorLayer` function: ```javascript @@ -377,6 +379,8 @@ This function will reload the given layer by re-fetching the data and re-drawing - `evenIfOff` - _boolean_ | If true, reloads the layer even if the layer is not active - `evenIfControlled` - _boolean_ | If true, reloads the layer even if it's a "Controlled" layer +Returns `false` if the layer could not be updated (either some parameters are wrong or that layer is already is the midst of being loaded). + The following is an example of how to call the `reloadLayer` function: ```javascript diff --git a/docs/pages/Configure/Kinds/Kinds.md b/docs/pages/Configure/Kinds/Kinds.md index 0438b26c..fac2ff35 100644 --- a/docs/pages/Configure/Kinds/Kinds.md +++ b/docs/pages/Configure/Kinds/Kinds.md @@ -29,6 +29,7 @@ The Waypoint Kind only applies to point features and uses the `markerAttachments markerAttachments: { image: { initialVisibility: true, + initialOpacity: 1, path: "url to top-down ortho image. ex. public/images/rovers/PerseveranceTopDown.png", pathProp: "path to image. take priority over path", widthMeters: 2.6924, diff --git a/docs/pages/Configure/Layers/Vector/Vector.md b/docs/pages/Configure/Layers/Vector/Vector.md index 4db87eec..73b84990 100644 --- a/docs/pages/Configure/Layers/Vector/Vector.md +++ b/docs/pages/Configure/Layers/Vector/Vector.md @@ -203,6 +203,7 @@ Example: }, "image": { "initialVisibility": true, + "initialOpacity": 1, "path": "url to top-down ortho image. ex. public/images/rovers/PerseveranceTopDown.png", "pathProp": "path to image. take priority over path", "widthMeters": 2.6924, @@ -311,6 +312,7 @@ Example: - `opacity3d`: 3d curtain ellipse opacity - `image`: Places a scaled and orientated image under each marker. A sublayer. - `initialVisibility`: Whether the image sublayer is initially on. Users can toggle sublayers on and off in the layer settings in the LayersTool. + - `initialOpacity`: The inital image opacity. Users canchange sublayer opacity n the layer settings in the LayersTool. From 0 to 1. Default 1 - `path`: A url to a (preferably) top-down north-facing orthographic image. - `pathProp`: A prop path to an image url. Take priority over path. Useful if the path is feature specific. - `widthMeters`: Width of image in meters in order to calculate scale. diff --git a/src/essence/Ancillary/TimeControl.js b/src/essence/Ancillary/TimeControl.js index 8cd51896..3c14b7e2 100644 --- a/src/essence/Ancillary/TimeControl.js +++ b/src/essence/Ancillary/TimeControl.js @@ -174,7 +174,7 @@ var TimeControl = { layer = L_.layers.data[layer] } - if (L_.layers.layer[layer.name] === null) return + if (L_.layers.layer[layer.name] === null) return false let layerTimeFormat = layer.time?.format == null @@ -233,7 +233,7 @@ var TimeControl = { // refresh map if (evenIfControlled === true || layer.controlled !== true) if (L_.layers.on[layer.name] || evenIfOff) { - await Map_.refreshLayer(layer) + return await Map_.refreshLayer(layer) } } } diff --git a/src/essence/Basics/Layers_/LayerConstructors.js b/src/essence/Basics/Layers_/LayerConstructors.js index dd9756ed..c135b055 100644 --- a/src/essence/Basics/Layers_/LayerConstructors.js +++ b/src/essence/Basics/Layers_/LayerConstructors.js @@ -967,6 +967,25 @@ const uncertaintyEllipses = (geojson, layerObj, leafletLayerObject) => { let leafletLayerObjectUncertaintyEllipse if (uncertaintyVar) { + let existingOn = null + let existingOpacity = + uncertaintyVar.initialOpacity != null + ? uncertaintyVar.initialOpacity + : 1 + if (L_.layers.attachments[L_.asLayerUUID(layerObj.name)]) { + existingOn = + L_.layers.attachments[L_.asLayerUUID(layerObj.name)] + .uncertainty_ellipses.on + existingOpacity = L_.layers.opacity[layerObj.name] + } + + const isOn = + existingOn != null + ? existingOn + : uncertaintyVar.initialVisibility != null + ? uncertaintyVar.initialVisibility + : true + uncertaintyStyle = { fillOpacity: uncertaintyVar.fillOpacity || 0.25, fillColor: uncertaintyVar.color || 'white', @@ -1018,7 +1037,7 @@ const uncertaintyEllipses = (geojson, layerObj, leafletLayerObject) => { curtainUncertaintyOptions = { name: `markerAttachmentUncertainty_${layerObj.name}Curtain`, - on: true, + on: isOn, opacity: uncertaintyVar.opacity3d || 0.5, imageColor: uncertaintyVar.color3d || uncertaintyVar.color || '#FFFF00', @@ -1030,9 +1049,9 @@ const uncertaintyEllipses = (geojson, layerObj, leafletLayerObject) => { } clampedUncertaintyOptions = { name: `markerAttachmentUncertainty_${layerObj.name}Clamped`, - on: true, + on: isOn, order: -9999, - opacity: 1, + opacity: existingOpacity, minZoom: 0, maxZoom: 100, geojson: { @@ -1087,22 +1106,23 @@ const uncertaintyEllipses = (geojson, layerObj, leafletLayerObject) => { }, } + const layer = L.geoJson(geojson, leafletLayerObjectUncertaintyEllipse) + layer.customClearLayers = function (layerName, subName) { + const sublayer = L_.layers.attachments[layerName][subName] + L_.Globe_.litho.removeLayer(sublayer.curtainLayerId) + L_.Globe_.litho.removeLayer(sublayer.clampedLayerId) + } + return curtainUncertaintyOptions ? { - on: - uncertaintyVar.initialVisibility != null - ? uncertaintyVar.initialVisibility - : true, + on: isOn, type: 'uncertainty_ellipses', curtainLayerId: curtainUncertaintyOptions.name, curtainOptions: curtainUncertaintyOptions, clampedLayerId: clampedUncertaintyOptions.name, clampedOptions: clampedUncertaintyOptions, geojson: geojson, - layer: L.geoJson( - geojson, - leafletLayerObjectUncertaintyEllipse - ), + layer: layer, title: 'Renders elliptical buffers about point features based on X and Y uncertainty properties.', } : false @@ -1121,6 +1141,25 @@ const imageOverlays = (geojson, layerObj, leafletLayerObject) => { ) let leafletLayerObjectImageOverlay + let existingOn = null + let existingOpacity = + imageVar.initialOpacity != null ? imageVar.initialOpacity : 1 + if (L_.layers.attachments[L_.asLayerUUID(layerObj.name)]) { + existingOn = + L_.layers.attachments[L_.asLayerUUID(layerObj.name)] + .image_overlays.on + existingOpacity = + L_.layers.attachments[L_.asLayerUUID(layerObj.name)] + .image_overlays.opacity + } + + const isOn = + existingOn != null + ? existingOn + : imageVar.initialVisibility != null + ? imageVar.initialVisibility + : true + if (imageVar && imageShow === 'always') leafletLayerObjectImageOverlay = { pointToLayer: (feature, latlong) => { @@ -1239,30 +1278,20 @@ const imageOverlays = (geojson, layerObj, leafletLayerObject) => { return L.layerGroup([ L.imageTransform(imageSettings.image, anchors, { - opacity: 1, + opacity: existingOpacity, clip: anchors, - id: `${layerObj.name}${ - imageSettings.image - }${angle}${JSON.stringify(center)}`, + id: `${layerObj.name}_${imageSettings.image}`, + layerName: layerObj.name, }), ]) }, } - let existingOn = null - if (L_.layers.attachments[L_.asLayerUUID(layerObj.name)]) - existingOn = - L_.layers.attachments[L_.asLayerUUID(layerObj.name)] - .image_overlays.on - const isOn = - existingOn != null - ? existingOn - : imageVar.initialVisibility != null - ? imageVar.initialVisibility - : true return imageShow === 'always' ? { on: isOn, + type: 'image_overlays', + opacity: existingOpacity, layer: L.geoJson(geojson, leafletLayerObjectImageOverlay), title: 'Map rendered image overlays.', } diff --git a/src/essence/Basics/Layers_/Layers_.js b/src/essence/Basics/Layers_/Layers_.js index 2d58c4f6..273ad270 100644 --- a/src/essence/Basics/Layers_/Layers_.js +++ b/src/essence/Basics/Layers_/Layers_.js @@ -70,6 +70,7 @@ const L_ = { toggledOffFeatures: [], mapAndGlobeLinked: false, addLayerQueue: [], + _layersBeingMade: {}, _onLoadCallbacks: [], _loaded: false, init: function (configData, missionsList, urlOnLayers) { @@ -561,6 +562,10 @@ const L_ = { }) } } + // Refresh opacity + if (s.type === 'vector') { + L_.setLayerOpacity(s.name, L_.layers.opacity[s.name]) + } }, _refreshAnnotationEvents() { // Add annotation click events since onEachFeatureDefault doesn't apply to popups @@ -574,6 +579,28 @@ const L_ = { }) }) }, + // If opacity is null, reinforces opacities + setSublayerOpacity(layerName, sublayerName, opacity) { + layerName = L_.asLayerUUID(layerName) + + const sublayers = L_.layers.attachments[layerName] || {} + const sublayer = sublayers[sublayerName] + + if (opacity == null) opacity = sublayer?.opacity + + if (sublayer && sublayer.opacity != null) { + sublayer.opacity = opacity + switch (sublayer.type) { + case 'image_overlays': + $(`.${sublayer.type}_${layerName}`).css({ + opacity: opacity, + }) + break + default: + break + } + } + }, toggleSublayer: function (layerName, sublayerName) { layerName = L_.asLayerUUID(layerName) @@ -631,6 +658,7 @@ const L_ = { 1 - L_._layersOrdered.indexOf(layerName) ) + L_.setSublayerOpacity(layerName, sublayerName) break } sublayer.on = true @@ -737,16 +765,16 @@ const L_ = { map.addLayer( L_.layers.layer[L_.layers.dataFlat[i].name] ) - // Set markerDiv based opacities if any - $( - `.leafletMarkerShape_${F_.getSafeName( - L_.layers.dataFlat[i].name - )}` - ).css({ - opacity: - L_.layers.opacity[L_.layers.dataFlat[i].name] || - 0, - }) + // Refresh opacity + if (L_.layers.dataFlat[i].type === 'vector') { + const lname = L_.layers.dataFlat[i].name + setTimeout(() => { + L_.setLayerOpacity( + lname, + L_.layers.opacity[lname] + ) + }, 300) + } } catch (e) { console.log(e) console.warn( @@ -869,7 +897,7 @@ const L_ = { L_._refreshAnnotationEvents() }, - addGeoJSONData: function (layer, geojson) { + addGeoJSONData: function (layer, geojson, keepLastN) { if (layer._sourceGeoJSON) { if (layer._sourceGeoJSON.features) if (geojson.features) @@ -884,6 +912,10 @@ const L_ = { ? geojson : null ) + if (keepLastN && keepLastN > 0) { + layer._sourceGeoJSON.features = + layer._sourceGeoJSON.features.slice(-1 * keepLastN) + } } // Don't add data if hidden @@ -912,7 +944,6 @@ const L_ = { L_.layers.on[layer._layerName] = true } //L_.syncSublayerData(layer._layerName) - if (initialOn) { // Reselect activeFeature if (L_.activeFeature) { @@ -1484,10 +1515,10 @@ const L_ = { opacity: newOpacity, fillOpacity: newOpacity * l.options.initialFillOpacity, }) - $(`.leafletMarkerShape_${F_.getSafeName(name)}`).css({ - opacity: newOpacity, - }) } + $(`.leafletMarkerShape_${F_.getSafeName(name)}`).css({ + opacity: newOpacity, + }) const sublayers = L_.layers.attachments[name] if (sublayers) { @@ -1501,11 +1532,16 @@ const L_ = { sublayers[sub].layer.setOpacity(newOpacity) } catch (error) { try { + let opacity = newOpacity + let fillOpacity = + newOpacity * l.options.initialFillOpacity + if (sub === 'uncertainty_ellipses') { + opacity = opacity * 0.8 + fillOpacity = fillOpacity * 0.25 + } sublayers[sub].layer.setStyle({ - opacity: newOpacity, - fillOpacity: - newOpacity * - l.options.initialFillOpacity, + opacity, + fillOpacity, }) } catch (error2) { /* @@ -2607,30 +2643,41 @@ const L_ = { ) } }, - updateVectorLayer: function (layerName, inputData) { + updateVectorLayer: function (layerName, inputData, keepLastN) { layerName = L_.asLayerUUID(layerName) if (layerName in L_.layers.layer) { + const layerObj = L_.layers.data[layerName] + if (L_._layersBeingMade[layerName] === true) { + console.error( + `ERROR - updateVectorLayer: Cannot make layer ${layerObj.display_name}/${layerObj.name} as it's already being made!` + ) + return false + } + const updateLayer = L_.layers.layer[layerName] try { - L_.addGeoJSONData(updateLayer, inputData) + L_.addGeoJSONData(updateLayer, inputData, keepLastN) } catch (e) { console.log(e) console.warn( - 'Warning: Unable to update vector layer as the input data is invalid: ' + + 'Warning: Unable to update vector layer as the layer or input data is invalid: ' + layerName ) - return + return false } L_.syncSublayerData(layerName) L_.globeLithoLayerHelper(L_.layers.layer[layerName]) + L_.setLayerOpacity(layerName, L_.layers.opacity[layerName]) } else { console.warn( 'Warning: Unable to update vector layer as it does not exist: ' + layerName ) + return false } + return true }, // Make a layer's sublayer match the layers data again syncSublayerData: async function (layerName, onlyClear) { @@ -2653,6 +2700,15 @@ const L_ = { subUpdateLayers[sub].layer != null ) { subUpdateLayers[sub].layer.clearLayers() + if ( + typeof subUpdateLayers[sub].layer + .customClearLayers === 'function' + ) { + subUpdateLayers[sub].layer.customClearLayers( + layerName, + sub + ) + } if (!onlyClear) { if ( @@ -2671,6 +2727,14 @@ const L_ = { ) { subUpdateLayers[sub].layer.addData(geojson) } + + if (sub === 'image_overlays') { + subUpdateLayers[sub].layer.setZIndex( + L_._layersOrdered.length + + 1 - + L_._layersOrdered.indexOf(layerName) + ) + } } } } diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index 3dfc79cb..637e966d 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -401,19 +401,26 @@ let Map_ = { L_.layers.data[L_._layersOrdered[i]].type == 'vector' && L_.layers.data[L_._layersOrdered[i]].name == layerObj.name ) { - const wasOn = L_.layers.on[layerObj.name] - if (wasOn) L_.toggleLayer(L_.layers.data[layerObj.name]) // turn off if on - // fake on - L_.layers.on[layerObj.name] = true - await makeLayer(layerObj, true) - L_.addVisible(Map_, [layerObj.name]) - - // turn off if was off - if (wasOn) L_.layers.on[layerObj.name] = false - L_.toggleLayer(L_.layers.data[layerObj.name]) // turn back on/off - - L_.enforceVisibilityCutoffs() - return + if (L_._layersBeingMade[layerObj.name] !== true) { + const wasOn = L_.layers.on[layerObj.name] + if (wasOn) L_.toggleLayer(L_.layers.data[layerObj.name]) // turn off if on + // fake on + L_.layers.on[layerObj.name] = true + await makeLayer(layerObj, true, null) + L_.addVisible(Map_, [layerObj.name]) + + // turn off if was off + if (wasOn) L_.layers.on[layerObj.name] = false + L_.toggleLayer(L_.layers.data[layerObj.name]) // turn back on/off + + L_.enforceVisibilityCutoffs() + } else { + console.error( + `ERROR - refreshLayer: Cannot make layer ${layerObj.display_name}/${layerObj.name} as it's already being made!` + ) + return false + } + return true } } }, @@ -523,596 +530,606 @@ function makeLayers(layersObj) { } } //Takes the layer object and makes it a map layer -async function makeLayer(layerObj, evenIfOff, forceGeoJSON) { - //Decide what kind of layer it is - //Headers do not need to be made - if (layerObj.type != 'header') { - //Simply call the appropriate function for each layer type - switch (layerObj.type) { - case 'vector': - await makeVectorLayer(evenIfOff, null, forceGeoJSON) - break - case 'tile': - makeTileLayer() - break - case 'vectortile': - makeVectorTileLayer() - break - case 'query': - await makeVectorLayer(false, true, forceGeoJSON) - break - case 'data': - makeDataLayer() - break - case 'model': - //Globe only - makeModelLayer() - break - default: - console.warn('Unknown layer type: ' + layerObj.type) +async function makeLayer(layerObj, evenIfOff, forceGeoJSON, id) { + return new Promise(async (resolve, reject) => { + const layerName = L_.asLayerUUID(layerObj.name) + if (L_._layersBeingMade[layerName] === true) { + console.error( + `ERROR - makeLayer: Cannot make layer ${layerObj.display_name}/${layerObj.name} as it's already being made!` + ) + resolve(false) + return + } else { + L_._layersBeingMade[layerName] = true + } + //Decide what kind of layer it is + //Headers do not need to be made + if (layerObj.type != 'header') { + //Simply call the appropriate function for each layer type + switch (layerObj.type) { + case 'vector': + await makeVectorLayer( + layerObj, + evenIfOff, + null, + forceGeoJSON + ) + break + case 'tile': + makeTileLayer(layerObj) + break + case 'vectortile': + makeVectorTileLayer(layerObj) + break + case 'query': + await makeVectorLayer(layerObj, false, true, forceGeoJSON) + break + case 'data': + makeDataLayer(layerObj) + break + case 'model': + //Globe only + makeModelLayer(layerObj) + break + default: + console.warn('Unknown layer type: ' + layerObj.type) + } } - } - //Default is onclick show full properties and onhover show 1st property - Map_.onEachFeatureDefault = onEachFeatureDefault - function onEachFeatureDefault(feature, layer) { - const pv = L_.getLayersChosenNamePropVal(feature, layer) + // release hold on layer + L_._layersBeingMade[layerName] = false - layer['useKeyAsName'] = Object.keys(pv)[0] - if ( - layer.hasOwnProperty('options') && - layer.options.hasOwnProperty('layerName') - ) { - L_.layers.data[layer.options.layerName].useKeyAsName = - layer['useKeyAsName'] - } + resolve(true) + }) +} - if (typeof layer['useKeyAsName'] === 'string') { - //Add a mouseover event to the layer - layer.on('mouseover', function () { - //Make it turn on CursorInfo and show name and value - CursorInfo.update(pv, null, false) - }) - //Add a mouseout event - layer.on('mouseout', function () { - //Make it turn off CursorInfo - CursorInfo.hide() - }) - } +//Default is onclick show full properties and onhover show 1st property +Map_.onEachFeatureDefault = onEachFeatureDefault +function onEachFeatureDefault(feature, layer) { + const pv = L_.getLayersChosenNamePropVal(feature, layer) + + layer['useKeyAsName'] = Object.keys(pv)[0] + if ( + layer.hasOwnProperty('options') && + layer.options.hasOwnProperty('layerName') + ) { + L_.layers.data[layer.options.layerName].useKeyAsName = + layer['useKeyAsName'] + } - if ( - !( - feature.style && - feature.style.hasOwnProperty('noclick') && - feature.style.noclick - ) - ) { - //Add a click event to send the data to the info tab - layer.on('click', (e) => { - featureDefaultClick(feature, layer, e) - }) - } + if (typeof layer['useKeyAsName'] === 'string') { + //Add a mouseover event to the layer + layer.on('mouseover', function () { + //Make it turn on CursorInfo and show name and value + CursorInfo.update(pv, null, false) + }) + //Add a mouseout event + layer.on('mouseout', function () { + //Make it turn off CursorInfo + CursorInfo.hide() + }) } - Map_.featureDefaultClick = featureDefaultClick - function featureDefaultClick(feature, layer, e) { - if ( - ToolController_.activeTool && - ToolController_.activeTool.disableLayerInteractions === true + if ( + !( + feature.style && + feature.style.hasOwnProperty('noclick') && + feature.style.noclick ) - return + ) { + //Add a click event to send the data to the info tab + layer.on('click', (e) => { + featureDefaultClick(feature, layer, e) + }) + } +} - //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 && +Map_.featureDefaultClick = featureDefaultClick +function featureDefaultClick(feature, layer, e) { + if ( + ToolController_.activeTool && + 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 - ) { - 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('.') - ) - } + 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 + 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() - } + keepGoing() + }, + function (data) { + keepGoing() + } + ) + } else { + keepGoing() + } - function keepGoing() { - //View images - var propImages = propertiesToImages( - feature.properties, - layer.options.metadata - ? layer.options.metadata.base_url || '' - : '' - ) + function keepGoing() { + //View images + var propImages = propertiesToImages( + feature.properties, + layer.options.metadata ? layer.options.metadata.base_url || '' : '' + ) - Kinds.use( - L_.layers.data[layerObj.name].kind, - Map_, - feature, - layer, - layer.options.layerName, - propImages, - e - ) + Kinds.use( + L_.layers.data[layer.options.layerName].kind, + Map_, + feature, + layer, + layer.options.layerName, + propImages, + e + ) - //update url - if (layer != null && layer.hasOwnProperty('options')) { - var keyAsName - if (layer.hasOwnProperty('useKeyAsName')) { - keyAsName = layer.feature.properties[layer.useKeyAsName] - } else { - keyAsName = layer.feature.properties[0] - } + //update url + if (layer != null && layer.hasOwnProperty('options')) { + var keyAsName + if (layer.hasOwnProperty('useKeyAsName')) { + keyAsName = layer.feature.properties[layer.useKeyAsName] + } else { + keyAsName = layer.feature.properties[0] } + } - Viewer_.changeImages(propImages, feature, layer) - for (var i in propImages) { - if (propImages[i].type == 'radargram') { - //Globe_.radargram( layer.options.layerName, feature.geometry, propImages[i].url, propImages[i].length, propImages[i].depth ); - break - } + Viewer_.changeImages(propImages, feature, layer) + for (var i in propImages) { + if (propImages[i].type == 'radargram') { + //Globe_.radargram( layer.options.layerName, feature.geometry, propImages[i].url, propImages[i].length, propImages[i].depth ); + break } + } - //figure out how to construct searchStr in URL. For example: a ChemCam target can sometime - //be searched by "target sol", or it can be searched by "sol target" depending on config file. - var searchToolVars = L_.getToolVars('search') - var searchfields = {} - if (searchToolVars.hasOwnProperty('searchfields')) { - for (var layerfield in searchToolVars.searchfields) { - var fieldString = searchToolVars.searchfields[layerfield] - fieldString = fieldString.split(')') - for (var i = 0; i < fieldString.length; i++) { - fieldString[i] = fieldString[i].split('(') - var li = fieldString[i][0].lastIndexOf(' ') - if (li != -1) { - fieldString[i][0] = fieldString[i][0].substring( - li + 1 - ) - } + //figure out how to construct searchStr in URL. For example: a ChemCam target can sometime + //be searched by "target sol", or it can be searched by "sol target" depending on config file. + var searchToolVars = L_.getToolVars('search') + var searchfields = {} + if (searchToolVars.hasOwnProperty('searchfields')) { + for (var layerfield in searchToolVars.searchfields) { + var fieldString = searchToolVars.searchfields[layerfield] + fieldString = fieldString.split(')') + for (var i = 0; i < fieldString.length; i++) { + fieldString[i] = fieldString[i].split('(') + var li = fieldString[i][0].lastIndexOf(' ') + if (li != -1) { + fieldString[i][0] = fieldString[i][0].substring(li + 1) } - fieldString.pop() - //0 is function, 1 is parameter - searchfields[layerfield] = fieldString } + fieldString.pop() + //0 is function, 1 is parameter + searchfields[layerfield] = fieldString } + } - var str = '' - if (searchfields.hasOwnProperty(layer.options.layerName)) { - var sf = searchfields[layer.options.layerName] //sf for search field - for (var i = 0; i < sf.length; i++) { - str += sf[i][1] - str += ' ' - } + var str = '' + if (searchfields.hasOwnProperty(layer.options.layerName)) { + var sf = searchfields[layer.options.layerName] //sf for search field + for (var i = 0; i < sf.length; i++) { + str += sf[i][1] + str += ' ' } - str = str.substring(0, str.length - 1) + } + str = str.substring(0, str.length - 1) - var searchFieldTokens = str.split(' ') - var searchStr + var searchFieldTokens = str.split(' ') + var searchStr - if (searchFieldTokens.length == 2) { - if ( - searchFieldTokens[0].toLowerCase() == - layer.useKeyAsName.toLowerCase() - ) { - searchStr = keyAsName + ' ' + layer.feature.properties.Sol - } else { - searchStr = layer.feature.properties.Sol + ' ' + keyAsName - } + if (searchFieldTokens.length == 2) { + if ( + searchFieldTokens[0].toLowerCase() == + layer.useKeyAsName.toLowerCase() + ) { + searchStr = keyAsName + ' ' + layer.feature.properties.Sol + } else { + searchStr = layer.feature.properties.Sol + ' ' + keyAsName } - - QueryURL.writeSearchURL([searchStr], layer.options.layerName) } - } - //Pretty much like makePointLayer but without the pointToLayer stuff - async function makeVectorLayer(evenIfOff, useEmptyGeoJSON, forceGeoJSON) { - return new Promise((resolve, reject) => { - if (forceGeoJSON) add(forceGeoJSON) - else - captureVector( - layerObj, - { evenIfOff: evenIfOff, useEmptyGeoJSON: useEmptyGeoJSON }, - add - ) + QueryURL.writeSearchURL([searchStr], layer.options.layerName) + } +} - function add(data) { - // [] - if (Array.isArray(data) && data.length === 0) { - data = { type: 'FeatureCollection', features: [] } - } - // [] - else if ( - Array.isArray(data) && - data[0] && - data[0].type === 'FeatureCollection' - ) { - const nextData = { type: 'FeatureCollection', features: [] } - data.forEach((fc) => { - if (fc.type === 'FeatureCollection') - nextData.features = nextData.features.concat( - fc.features - ) - }) - data = nextData - } +//Pretty much like makePointLayer but without the pointToLayer stuff +async function makeVectorLayer( + layerObj, + evenIfOff, + useEmptyGeoJSON, + forceGeoJSON +) { + return new Promise((resolve, reject) => { + if (forceGeoJSON) add(forceGeoJSON) + else + captureVector( + layerObj, + { evenIfOff: evenIfOff, useEmptyGeoJSON: useEmptyGeoJSON }, + add + ) - let invalidGeoJSONTrace = gjv.valid(data, true) - const allowableErrors = [`position must only contain numbers`] - invalidGeoJSONTrace = invalidGeoJSONTrace.filter((t) => { - if (typeof t !== 'string') return false - for (let i = 0; i < allowableErrors.length; i++) { - if (t.toLowerCase().indexOf(allowableErrors[i]) != -1) - return false - } - return true - }) - if ( - data == null || - data === 'off' || - invalidGeoJSONTrace.length > 0 - ) { - if (data != null && data != 'off') { - data = null - console.warn( - `ERROR: ${layerObj.display_name} has invalid GeoJSON!` + function add(data) { + // [] + if (Array.isArray(data) && data.length === 0) { + data = { type: 'FeatureCollection', features: [] } + } + // [] + else if ( + Array.isArray(data) && + data[0] && + data[0].type === 'FeatureCollection' + ) { + const nextData = { type: 'FeatureCollection', features: [] } + data.forEach((fc) => { + if (fc.type === 'FeatureCollection') + nextData.features = nextData.features.concat( + fc.features ) - } - L_._layersLoaded[ - L_._layersOrdered.indexOf(layerObj.name) - ] = true - L_.layers.layer[layerObj.name] = data == null ? null : false - allLayersLoaded() - resolve() - return - } - - layerObj.style = layerObj.style || {} - layerObj.style.layerName = layerObj.name - - layerObj.style.opacity = L_.layers.opacity[layerObj.name] - //layerObj.style.fillOpacity = L_.layers.opacity[layerObj.name] - - const vl = constructVectorLayer( - data, - layerObj, - onEachFeatureDefault, - Map_ - ) - L_.layers.attachments[layerObj.name] = vl.sublayers - L_.layers.layer[layerObj.name] = vl.layer + }) + data = nextData + } - d3.selectAll('.' + F_.getSafeName(layerObj.name)).data( - data.features - ) + let invalidGeoJSONTrace = gjv.valid(data, true) + const allowableErrors = [`position must only contain numbers`] + invalidGeoJSONTrace = invalidGeoJSONTrace.filter((t) => { + if (typeof t !== 'string') return false + for (let i = 0; i < allowableErrors.length; i++) { + if (t.toLowerCase().indexOf(allowableErrors[i]) != -1) + return false + } + return true + }) + if ( + data == null || + data === 'off' || + invalidGeoJSONTrace.length > 0 + ) { + if (data != null && data != 'off') { + data = null + console.warn( + `ERROR: ${layerObj.display_name} has invalid GeoJSON!` + ) + } L_._layersLoaded[ L_._layersOrdered.indexOf(layerObj.name) ] = true + L_.layers.layer[layerObj.name] = data == null ? null : false allLayersLoaded() - resolve() + return } - }) - } - async function makeTileLayer() { - let layerUrl = layerObj.url - if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl - let bb = null - if (layerObj.hasOwnProperty('boundingBox')) { - bb = L.latLngBounds( - L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), - L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) + layerObj.style = layerObj.style || {} + layerObj.style.layerName = layerObj.name + + layerObj.style.opacity = L_.layers.opacity[layerObj.name] + //layerObj.style.fillOpacity = L_.layers.opacity[layerObj.name] + + const vl = constructVectorLayer( + data, + layerObj, + onEachFeatureDefault, + Map_ + ) + L_.layers.attachments[layerObj.name] = vl.sublayers + L_.layers.layer[layerObj.name] = vl.layer + + d3.selectAll('.' + F_.getSafeName(layerObj.name)).data( + data.features ) + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() + resolve() } - layerUrl = await TimeControl.performTimeUrlReplacements( - layerUrl, - layerObj + }) +} + +async function makeTileLayer(layerObj) { + let layerUrl = layerObj.url + if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl + let bb = null + if (layerObj.hasOwnProperty('boundingBox')) { + bb = L.latLngBounds( + L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), + L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) ) + } + layerUrl = await TimeControl.performTimeUrlReplacements(layerUrl, layerObj) + + let tileFormat = 'tms' + // For backward compatibility with the .tms option + if (typeof layerObj.tileformat === 'undefined') { + tileFormat = typeof layerObj.tms === 'undefined' ? true : layerObj.tms + tileFormat = tileFormat ? 'tms' : 'wmts' + } else tileFormat = layerObj.tileformat + + L_.layers.layer[layerObj.name] = L.tileLayer.colorFilter(layerUrl, { + minZoom: layerObj.minZoom, + maxZoom: layerObj.maxZoom, + maxNativeZoom: layerObj.maxNativeZoom, + tileFormat: tileFormat, + tms: tileFormat === 'tms', + //noWrap: true, + continuousWorld: true, + reuseTiles: true, + bounds: bb, + timeEnabled: layerObj.time != null && layerObj.time.enabled === true, + time: typeof layerObj.time === 'undefined' ? '' : layerObj.time.end, + compositeTile: + typeof layerObj.time === 'undefined' + ? false + : layerObj.time.compositeTile || false, + starttime: + typeof layerObj.time === 'undefined' ? '' : layerObj.time.start, + endtime: typeof layerObj.time === 'undefined' ? '' : layerObj.time.end, + variables: layerObj.variables || {}, + }) + + L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) + + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() +} - let tileFormat = 'tms' - // For backward compatibility with the .tms option - if (typeof layerObj.tileformat === 'undefined') { - tileFormat = - typeof layerObj.tms === 'undefined' ? true : layerObj.tms - tileFormat = tileFormat ? 'tms' : 'wmts' - } else tileFormat = layerObj.tileformat - - L_.layers.layer[layerObj.name] = L.tileLayer.colorFilter(layerUrl, { - minZoom: layerObj.minZoom, - maxZoom: layerObj.maxZoom, - maxNativeZoom: layerObj.maxNativeZoom, - tileFormat: tileFormat, - tms: tileFormat === 'tms', - //noWrap: true, - continuousWorld: true, - reuseTiles: true, - bounds: bb, - timeEnabled: - layerObj.time != null && layerObj.time.enabled === true, - time: typeof layerObj.time === 'undefined' ? '' : layerObj.time.end, - compositeTile: - typeof layerObj.time === 'undefined' - ? false - : layerObj.time.compositeTile || false, - starttime: - typeof layerObj.time === 'undefined' ? '' : layerObj.time.start, - endtime: - typeof layerObj.time === 'undefined' ? '' : layerObj.time.end, - variables: layerObj.variables || {}, - }) +function makeVectorTileLayer(layerObj) { + var layerUrl = layerObj.url + if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl - L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) + let urlSplit = layerObj.url.split(':') - L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true - allLayersLoaded() + if (urlSplit[0].toLowerCase() === 'geodatasets' && urlSplit[1] != null) { + layerUrl = + `${window.mmgisglobal.ROOT_PATH || ''}/api/geodatasets/get?layer=${ + urlSplit[1] + }` + '&type=mvt&x={x}&y={y}&z={z}' } - function makeVectorTileLayer() { - var layerUrl = layerObj.url - if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl - - let urlSplit = layerObj.url.split(':') + var bb = null + if (layerObj.hasOwnProperty('boundingBox')) { + bb = L.latLngBounds( + L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), + L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) + ) + } - if ( - urlSplit[0].toLowerCase() === 'geodatasets' && - urlSplit[1] != null - ) { - layerUrl = - `${ - window.mmgisglobal.ROOT_PATH || '' - }/api/geodatasets/get?layer=${urlSplit[1]}` + - '&type=mvt&x={x}&y={y}&z={z}' + var clearHighlight = function () { + for (let l of Object.keys(L_.layers.data)) { + if (L_.layers.layer[l]) { + var highlight = L_.layers.layer[l].highlight + if (highlight) { + L_.layers.layer[l].resetFeatureStyle(highlight) + } + L_.layers.layer[l].highlight = null + } } + } + var timedSelectTimeout = null + var timedSelect = function (layer, layerName, e) { + clearTimeout(timedSelectTimeout) + timedSelectTimeout = setTimeout( + (function (layer, layerName, e) { + return function () { + let ell = { latlng: null } + if (e.latlng != null) + ell.latlng = JSON.parse(JSON.stringify(e.latlng)) + + Kinds.use( + L_.layers.data[layerName].kind, + Map_, + L_.layers.layer[layerName].activeFeatures[0], + layer, + layerName, + null, + ell + ) - var bb = null - if (layerObj.hasOwnProperty('boundingBox')) { - bb = L.latLngBounds( - L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), - L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) - ) - } + ToolController_.getTool('InfoTool').use( + layer, + layerName, + L_.layers.layer[layerName].activeFeatures, + null, + null, + null, + ell + ) + L_.layers.layer[layerName].activeFeatures = [] + } + })(layer, layerName, e), + 100 + ) + } - var clearHighlight = function () { - for (let l of Object.keys(L_.layers.data)) { - if (L_.layers.layer[l]) { - var highlight = L_.layers.layer[l].highlight - if (highlight) { - L_.layers.layer[l].resetFeatureStyle(highlight) - } - L_.layers.layer[l].highlight = null + var vectorTileOptions = { + layerName: layerObj.name, + rendererFactory: L.svg.tile, + vectorTileLayerStyles: layerObj.style.vtLayer || {}, + interactive: true, + minZoom: layerObj.minZoom, + maxZoom: layerObj.maxZoom, + maxNativeZoom: layerObj.maxNativeZoom, + getFeatureId: (function (vtId) { + return function (f) { + if ( + f.properties.properties && + typeof f.properties.properties === 'string' + ) { + f.properties = JSON.parse(f.properties.properties) } + return f.properties[vtId] } - } - var timedSelectTimeout = null - var timedSelect = function (layer, layerName, e) { - clearTimeout(timedSelectTimeout) - timedSelectTimeout = setTimeout( - (function (layer, layerName, e) { - return function () { - let ell = { latlng: null } - if (e.latlng != null) - ell.latlng = JSON.parse(JSON.stringify(e.latlng)) - - Kinds.use( - L_.layers.data[layerName].kind, - Map_, - L_.layers.layer[layerName].activeFeatures[0], - layer, - layerName, - null, - ell - ) + })(layerObj.style.vtId), + } - ToolController_.getTool('InfoTool').use( - layer, - layerName, - L_.layers.layer[layerName].activeFeatures, - null, - null, - null, - ell - ) - L_.layers.layer[layerName].activeFeatures = [] - } - })(layer, layerName, e), - 100 + L_.layers.layer[layerObj.name] = L.vectorGrid + .protobuf(layerUrl, vectorTileOptions) + .on('click', function (e, b, x) { + let layerName = e.target.options.layerName + let vtId = L_.layers.layer[layerName].vtId + clearHighlight() + L_.layers.layer[layerName].highlight = e.layer.properties[vtId] + + L_.layers.layer[layerName].setFeatureStyle( + L_.layers.layer[layerName].highlight, + { + weight: 2, + color: 'red', + opacity: 1, + fillColor: 'red', + fill: true, + radius: 4, + fillOpacity: 1, + } ) - } + L_.layers.layer[layerName].activeFeatures = + L_.layers.layer[layerName].activeFeatures || [] + L_.layers.layer[layerName].activeFeatures.push({ + type: 'Feature', + properties: e.layer.properties, + geometry: {}, + }) - var vectorTileOptions = { - layerName: layerObj.name, - rendererFactory: L.svg.tile, - vectorTileLayerStyles: layerObj.style.vtLayer || {}, - interactive: true, - minZoom: layerObj.minZoom, - maxZoom: layerObj.maxZoom, - maxNativeZoom: layerObj.maxNativeZoom, - getFeatureId: (function (vtId) { - return function (f) { + Map_.activeLayer = e.layer + if (Map_.activeLayer) L_.Map_._justSetActiveLayer = true + + let p = e.sourceTarget._point + + if (p) { + for (var i in e.layer._renderer._features) { if ( - f.properties.properties && - typeof f.properties.properties === 'string' + e.layer._renderer._features[i].feature._pxBounds.min + .x <= p.x && + e.layer._renderer._features[i].feature._pxBounds.max + .x >= p.x && + e.layer._renderer._features[i].feature._pxBounds.min + .y <= p.y && + e.layer._renderer._features[i].feature._pxBounds.max + .y >= p.y && + e.layer._renderer._features[i].feature.properties[ + vtId + ] != e.layer.properties[vtId] ) { - f.properties = JSON.parse(f.properties.properties) + L_.layers.layer[layerName].activeFeatures.push({ + type: 'Feature', + properties: + e.layer._renderer._features[i].feature + .properties, + geometry: {}, + }) } - return f.properties[vtId] } - })(layerObj.style.vtId), - } - - L_.layers.layer[layerObj.name] = L.vectorGrid - .protobuf(layerUrl, vectorTileOptions) - .on('click', function (e, b, x) { - let layerName = e.target.options.layerName - let vtId = L_.layers.layer[layerName].vtId - clearHighlight() - L_.layers.layer[layerName].highlight = e.layer.properties[vtId] - - L_.layers.layer[layerName].setFeatureStyle( - L_.layers.layer[layerName].highlight, - { - weight: 2, - color: 'red', - opacity: 1, - fillColor: 'red', - fill: true, - radius: 4, - fillOpacity: 1, - } - ) - L_.layers.layer[layerName].activeFeatures = - L_.layers.layer[layerName].activeFeatures || [] - L_.layers.layer[layerName].activeFeatures.push({ - type: 'Feature', - properties: e.layer.properties, - geometry: {}, - }) - - Map_.activeLayer = e.layer - if (Map_.activeLayer) L_.Map_._justSetActiveLayer = true + } - let p = e.sourceTarget._point + timedSelect(e.layer, layerName, e) - if (p) { - for (var i in e.layer._renderer._features) { - if ( - e.layer._renderer._features[i].feature._pxBounds.min - .x <= p.x && - e.layer._renderer._features[i].feature._pxBounds.max - .x >= p.x && - e.layer._renderer._features[i].feature._pxBounds.min - .y <= p.y && - e.layer._renderer._features[i].feature._pxBounds.max - .y >= p.y && - e.layer._renderer._features[i].feature.properties[ - vtId - ] != e.layer.properties[vtId] - ) { - L_.layers.layer[layerName].activeFeatures.push({ - type: 'Feature', - properties: - e.layer._renderer._features[i].feature - .properties, - geometry: {}, - }) - } - } + L.DomEvent.stop(e) + }) + .on( + 'mouseover', + (function (vtKey) { + return function (e, a, b, c) { + if (vtKey != null) + CursorInfo.update( + vtKey + ': ' + e.layer.properties[vtKey], + null, + false + ) } + })(layerObj.style.vtKey) + ) + .on('mouseout', function () { + CursorInfo.hide() + }) - timedSelect(e.layer, layerName, e) + L_.layers.layer[layerObj.name].vtId = layerObj.style.vtId + L_.layers.layer[layerObj.name].vtKey = layerObj.style.vtKey - L.DomEvent.stop(e) - }) - .on( - 'mouseover', - (function (vtKey) { - return function (e, a, b, c) { - if (vtKey != null) - CursorInfo.update( - vtKey + ': ' + e.layer.properties[vtKey], - null, - false - ) - } - })(layerObj.style.vtKey) - ) - .on('mouseout', function () { - CursorInfo.hide() - }) + L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) - L_.layers.layer[layerObj.name].vtId = layerObj.style.vtId - L_.layers.layer[layerObj.name].vtKey = layerObj.style.vtKey + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() +} - L_.setLayerOpacity(layerObj.name, L_.layers.opacity[layerObj.name]) +function makeModelLayer(layerObj) { + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() +} - L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true - allLayersLoaded() - } +function makeDataLayer(layerObj) { + let layerUrl = layerObj.demtileurl + if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl - function makeModelLayer() { - L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true - allLayersLoaded() + let bb = null + if (layerObj.hasOwnProperty('boundingBox')) { + bb = L.latLngBounds( + L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), + L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) + ) } - function makeDataLayer() { - let layerUrl = layerObj.demtileurl - if (!F_.isUrlAbsolute(layerUrl)) layerUrl = L_.missionPath + layerUrl + const shader = F_.getIn(layerObj, 'variables.shader') || {} + const shaderType = shader.type || 'image' - let bb = null - if (layerObj.hasOwnProperty('boundingBox')) { - bb = L.latLngBounds( - L.latLng(layerObj.boundingBox[3], layerObj.boundingBox[2]), - L.latLng(layerObj.boundingBox[1], layerObj.boundingBox[0]) - ) - } - - const shader = F_.getIn(layerObj, 'variables.shader') || {} - const shaderType = shader.type || 'image' - - var uniforms = {} - for (let i = 0; i < DataShaders[shaderType].settings.length; i++) { - uniforms[DataShaders[shaderType].settings[i].parameter] = - DataShaders[shaderType].settings[i].value - } - - L_.layers.layer[layerObj.name] = L.tileLayer.gl({ - options: { - tms: true, - bounds: bb, - }, - fragmentShader: DataShaders[shaderType].frag, - tileUrls: [layerUrl], - pixelPerfect: true, - uniforms: uniforms, - }) + var uniforms = {} + for (let i = 0; i < DataShaders[shaderType].settings.length; i++) { + uniforms[DataShaders[shaderType].settings[i].parameter] = + DataShaders[shaderType].settings[i].value + } - if (DataShaders[shaderType].attachImmediateEvents) { - DataShaders[shaderType].attachImmediateEvents(layerObj.name, shader) - } + L_.layers.layer[layerObj.name] = L.tileLayer.gl({ + options: { + tms: true, + bounds: bb, + }, + fragmentShader: DataShaders[shaderType].frag, + tileUrls: [layerUrl], + pixelPerfect: true, + uniforms: uniforms, + }) + + if (DataShaders[shaderType].attachImmediateEvents) { + DataShaders[shaderType].attachImmediateEvents(layerObj.name, shader) + } - 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() - } + L_._layersLoaded[L_._layersOrdered.indexOf(layerObj.name)] = true + allLayersLoaded() } //Because some layers load faster than others, check to see if diff --git a/src/essence/Tools/Layers/LayersTool.js b/src/essence/Tools/Layers/LayersTool.js index 56047ed3..4e2292ae 100644 --- a/src/essence/Tools/Layers/LayersTool.js +++ b/src/essence/Tools/Layers/LayersTool.js @@ -1330,6 +1330,7 @@ function interfaceWithMMGIS(fromInit) { ).join('\n'), '' ].join('\n') : null, + L_.layers.attachments[layerName][s].opacity != null ? `` : null, '
', `
`, '
', @@ -1347,7 +1348,7 @@ function interfaceWithMMGIS(fromInit) { //Applies slider values to map layers $('.transparencyslider').off('input') $('.transparencyslider').on('input', function () { - var texttransp = $(this).val() + const texttransp = $(this).val() L_.setLayerOpacity($(this).attr('layername'), texttransp) $(this) .parent() @@ -1377,6 +1378,18 @@ function interfaceWithMMGIS(fromInit) { } } ) + + $( + '#layersToolList > li > .settings .sublayer .sublayeropacityslider' + ).off('input') + $( + '#layersToolList > li > .settings .sublayer .sublayeropacityslider' + ).on('input', function () { + const opacity = parseFloat($(this).val()) + const layerName = $(this).attr('layername') + const sublayerName = $(this).attr('sublayername') + L_.setSublayerOpacity(layerName, sublayerName, opacity) + }) //Makes sublayers clickable on and off $('#layersToolList > li > .settings .sublayer .checkbox').off('click') $('#layersToolList > li > .settings .sublayer .checkbox').on( diff --git a/src/essence/mmgisAPI/mmgisAPI.js b/src/essence/mmgisAPI/mmgisAPI.js index 1bfb031c..ff45d4cb 100644 --- a/src/essence/mmgisAPI/mmgisAPI.js +++ b/src/essence/mmgisAPI/mmgisAPI.js @@ -162,36 +162,38 @@ var mmgisAPI_ = { // If layer is a DrawTool array of layers for (let layer in L_.layers.layer[key]) { let foundFeatures - if ('getLayers' in L_.layers.layer[key][layer]) { - if ( - L_.layers.layer[key][layer]?.feature?.properties - ?.arrow - ) { - // If the DrawTool sublayer is an arrow - foundFeatures = findFeaturesInLayer( - extent, - L_.layers.layer[key][layer] - ) - - // As long as one of the layers of the arrow layer is in the current Map bounds, - // return the parent arrow layer's feature - if (foundFeatures && foundFeatures.length > 0) { - foundFeatures = - L_.layers.layer[key][layer].feature + if (layer && L_.layers.layer[key][layer]) { + if ('getLayers' in L_.layers.layer[key][layer]) { + if ( + L_.layers.layer[key][layer]?.feature?.properties + ?.arrow + ) { + // If the DrawTool sublayer is an arrow + foundFeatures = findFeaturesInLayer( + extent, + L_.layers.layer[key][layer] + ) + + // As long as one of the layers of the arrow layer is in the current Map bounds, + // return the parent arrow layer's feature + if (foundFeatures && foundFeatures.length > 0) { + foundFeatures = + L_.layers.layer[key][layer].feature + } + } else { + // If the DrawTool sublayer is Polygon or Line + foundFeatures = findFeaturesInLayer( + extent, + L_.layers.layer[key][layer] + ) + } + } else if ('getLatLng' in L_.layers.layer[key][layer]) { + // If the DrawTool sublayer is a Point + if (isLayerInBounds(L_.layers.layer[key][layer])) { + foundFeatures = [ + L_.layers.layer[key][layer].feature, + ] } - } else { - // If the DrawTool sublayer is Polygon or Line - foundFeatures = findFeaturesInLayer( - extent, - L_.layers.layer[key][layer] - ) - } - } else if ('getLatLng' in L_.layers.layer[key][layer]) { - // If the DrawTool sublayer is a Point - if (isLayerInBounds(L_.layers.layer[key][layer])) { - foundFeatures = [ - L_.layers.layer[key][layer].feature, - ] } } diff --git a/src/external/Leaflet/leaflet-imagetransform.js b/src/external/Leaflet/leaflet-imagetransform.js index afba8d9c..0eee728e 100644 --- a/src/external/Leaflet/leaflet-imagetransform.js +++ b/src/external/Leaflet/leaflet-imagetransform.js @@ -72,58 +72,64 @@ _imgLoaded: false, _initImage: function () { const cache = LImageTransformCache[this.options.id] - if (cache) { - this._image = cache._image - this._imgNode = cache._imgNode - this._canvas = cache._canvas - this._onImageLoad() + + this._image = L.DomUtil.create('div', 'leaflet-image-layer') + + L.DomUtil.addClass( + this._image, + `image_overlays_${this.options.layerName}` + ) + + if (this._map.options.zoomAnimation && L.Browser.any3d) { + L.DomUtil.addClass(this._image, 'leaflet-zoom-animated') } else { - this._image = L.DomUtil.create('div', 'leaflet-image-layer') + L.DomUtil.addClass(this._image, 'leaflet-zoom-hide') + } - if (this._map.options.zoomAnimation && L.Browser.any3d) { - L.DomUtil.addClass(this._image, 'leaflet-zoom-animated') - } else { - L.DomUtil.addClass(this._image, 'leaflet-zoom-hide') - } + this._imgNode = L.DomUtil.create('img') + if (this.options.clip) { + if (cache?.canvas) { + this._cached = true + this._canvas = this._cloneCanvas(cache.canvas) + this._canvas.style['transform-origin'] = '0px 0px' + this._imgNode = cache.imgNode + this._imgNode.style[L.DomUtil.TRANSFORM_ORIGIN] = '0 0' + this._image.appendChild(this._canvas) - this._imgNode = L.DomUtil.create('img') - if (this.options.clip) { + this._clipDone = false + + this._onImageLoad(false) + return + } else { this._canvas = L.DomUtil.create( 'canvas', 'leaflet-canvas-transform' ) - this._image.appendChild(this._canvas) - this._canvas.style[L.DomUtil.TRANSFORM_ORIGIN] = '0 0' - this._clipDone = false - } else { - this._image.appendChild(this._imgNode) - this._imgNode.style[L.DomUtil.TRANSFORM_ORIGIN] = '0 0' - - // Hide imgNode until image has loaded - this._imgNode.style.display = 'none' - } - - if (this.options.id != null) { - LImageTransformCache[this.options.id] = { - _image: this._image, - _imgNode: this._imgNode, - _canvas: this._canvas, - } } + this._canvas.style[L.DomUtil.TRANSFORM_ORIGIN] = '0 0' + this._image.appendChild(this._canvas) + this._clipDone = false + } else { + this._image.appendChild(this._imgNode) + this._imgNode.style[L.DomUtil.TRANSFORM_ORIGIN] = '0 0' - this._updateOpacity() - - //TODO createImage util method to remove duplication - this._imgLoaded = false - L.extend(this._imgNode, { - galleryimg: 'no', - onselectstart: L.Util.falseFn, - onmousemove: L.Util.falseFn, - onload: L.bind(this._onImageLoad, this), - onerror: L.bind(this._onImageError, this), - src: this._url, - }) + // Hide imgNode until image has loaded + this._imgNode.style.display = 'none' } + + this._updateOpacity() + + //TODO createImage util method to remove duplication + this._imgLoaded = false + L.extend(this._imgNode, { + galleryimg: 'no', + onselectstart: L.Util.falseFn, + onmousemove: L.Util.falseFn, + onload: L.bind(this._onImageLoad, this), + onerror: L.bind(this._onImageError, this), + src: this._url, + }) + //} }, createImage: function () {}, @@ -132,8 +138,8 @@ this.fire('error') }, - _onImageLoad: function () { - if (this.options.clip) { + _onImageLoad: function (e) { + if (e !== false && this.options.clip) { this._canvas.width = this._imgNode.width this._canvas.height = this._imgNode.height } else { @@ -144,6 +150,19 @@ this._reset() this.fire('load') + + if (e !== false) { + // cache it + if ( + this.options.id != null && + LImageTransformCache[this.options.id] == null + ) { + LImageTransformCache[this.options.id] = { + canvas: this._cloneCanvas(this._drawCanvasCache()), + imgNode: this._imgNode.cloneNode(), + } + } + } }, _reset: function () { @@ -209,6 +228,7 @@ imgNode.style[L.DomUtil.TRANSFORM] = this._getMatrix3dCSS( this._matrix3d ) + if (this.options.clip) { if (this._pixelClipPoints) { this.options.clip = [] @@ -234,7 +254,33 @@ } } }, + _getBase64Image: function (img) { + const canvas = document.createElement('canvas') + canvas.width = img.width + canvas.height = img.height + + const ctx = canvas.getContext('2d') + ctx.drawImage(img, 0, 0) + return canvas.toDataURL('image/png') + }, + _cloneCanvas: function (oldCanvas) { + if (oldCanvas == null) return null + //create a new canvas + const newCanvas = document.createElement('canvas') + newCanvas.classList.add('leaflet-canvas-transform') + newCanvas.style = oldCanvas.style + const context = newCanvas.getContext('2d') + + //set dimensions + newCanvas.width = oldCanvas.width + newCanvas.height = oldCanvas.height + + //apply the old canvas to the new one + context.drawImage(oldCanvas, 0, 0) + //return the new canvas + return newCanvas + }, _getMatrix3dCSS: function (arr) { // get CSS atribute matrix3d var css = 'matrix3d(' @@ -290,6 +336,14 @@ this._clipDone = true } }, + _drawCanvasCache: function () { + const canvas = this._cloneCanvas(this._canvas) + const ctx = canvas.getContext('2d') + + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.drawImage(this._imgNode, 0, 0) + return canvas + }, }) L.imageTransform = function (url, bounds, options) {