diff --git a/API/Backend/Geodatasets/routes/geodatasets.js b/API/Backend/Geodatasets/routes/geodatasets.js index a86cc7e1..86f62fc9 100644 --- a/API/Backend/Geodatasets/routes/geodatasets.js +++ b/API/Backend/Geodatasets/routes/geodatasets.js @@ -55,9 +55,9 @@ function get(reqtype, req, res, next) { if (result) { let table = result.dataValues.table; if (type == "geojson") { - let q = - "SELECT properties, ST_AsGeoJSON(geom)" + " " + "FROM " + table; + let q = `SELECT properties, ST_AsGeoJSON(geom) FROM ${table}`; + let hasBounds = false; if (req.body?.bounds) { const bounds = req.body.bounds; if ( @@ -69,7 +69,12 @@ function get(reqtype, req, res, next) { bounds.northEast.lat != null ) { // ST_MakeEnvelope is (xmin, ymin, xmax, ymax, srid) - q += ` WHERE ST_Intersects(ST_MakeEnvelope(${bounds.southWest.lng}, ${bounds.southWest.lat}, ${bounds.northEast.lng}, ${bounds.northEast.lat}, 4326), geom);`; + q += ` WHERE ST_Intersects(ST_MakeEnvelope(${parseFloat( + bounds.southWest.lng + )}, ${parseFloat(bounds.southWest.lat)}, ${parseFloat( + bounds.northEast.lng + )}, ${parseFloat(bounds.northEast.lat)}, 4326), geom)`; + hasBounds = true; } else { res.send({ status: "failure", @@ -79,6 +84,48 @@ function get(reqtype, req, res, next) { return; } } + if (req.body?.time) { + const time = req.body.time; + const format = req.body.time.format || "YYYY-MM-DDTHH:MI:SSZ"; + let t = ` `; + if (!hasBounds) t += `WHERE `; + else t += `AND `; + + if ( + time.startProp == null || + time.startProp.indexOf(`'`) != -1 || + time.endProp == null || + time.endProp.indexOf(`'`) != -1 || + time.start == null || + time.start.indexOf(`'`) != -1 || + time.end == null || + time.end.indexOf(`'`) != -1 || + format.indexOf(`'`) != -1 + ) { + res.send({ + status: "failure", + message: "Missing inner or malformed 'time' parameters.", + }); + return; + } + + // prettier-ignore + t += [ + `(`, + `properties->>'${time.startProp}' IS NOT NULL AND properties->>'${time.endProp}' IS NOT NULL AND`, + ` date_part('epoch', to_timestamp(properties->>'${time.startProp}', '${format}'::text)) >= date_part('epoch', to_timestamp('${time.start}'::text, '${format}'::text))`, + ` AND date_part('epoch', to_timestamp(properties->>'${time.endProp}', '${format}'::text)) <= date_part('epoch', to_timestamp('${time.end}'::text, '${format}'::text))`, + `)`, + ` OR `, + `(`, + `properties->>'${time.startProp}' IS NULL AND properties->>'${time.endProp}' IS NOT NULL AND`, + ` date_part('epoch', to_timestamp(properties->>'${time.endProp}', '${format}'::text)) >= date_part('epoch', to_timestamp('${time.start}'::text, '${format}'::text))`, + ` AND date_part('epoch', to_timestamp(properties->>'${time.endProp}', '${format}'::text)) >= date_part('epoch', to_timestamp('${time.end}'::text, '${format}'::text))`, + `)` + ].join('') + q += t; + } + q += `;`; sequelize .query(q) diff --git a/src/essence/Ancillary/TimeControl.js b/src/essence/Ancillary/TimeControl.js index 3c14b7e2..0c4d7389 100644 --- a/src/essence/Ancillary/TimeControl.js +++ b/src/essence/Ancillary/TimeControl.js @@ -292,7 +292,11 @@ var TimeControl = { var reloadedLayers = [] for (let layerName in L_.layers.data) { const layer = L_.layers.data[layerName] - if (layer.time && layer.time.enabled === true) { + if ( + layer.time && + layer.time.enabled === true && + layer.variables?.dynamicExtent != true + ) { TimeControl.reloadLayer(layer) reloadedLayers.push(layer.name) } diff --git a/src/essence/Basics/Layers_/LayerCapturer.js b/src/essence/Basics/Layers_/LayerCapturer.js index e9e73e79..6e39d3d7 100644 --- a/src/essence/Basics/Layers_/LayerCapturer.js +++ b/src/essence/Basics/Layers_/LayerCapturer.js @@ -5,6 +5,10 @@ import L_ from '../Layers_/Layers_' import calls from '../../../pre/calls' import TimeControl from '../../Ancillary/TimeControl' +// This is so that an eariler and slower dynamic geodataset request +// does not override an earlier shorter one +// Object of layerName: timestamp +const _geodatasetRequestLastTimestamp = {} export const captureVector = (layerObj, options, cb, dynamicCb) => { options = options || {} let layerUrl = layerObj.url @@ -63,36 +67,71 @@ export const captureVector = (layerObj, options, cb, dynamicCb) => { // Return .on('moveend zoomend') event dynamicCb((e) => { const zoom = L_.Map_.map.getZoom() + if ( zoom >= (layerData.minZoom || 0) && (zoom <= layerData.maxZoom || 100) ) { // Then query, delete existing and remake const bounds = L_.Map_.map.getBounds() + const body = { + layer: urlSplitRaw[1], + type: 'geojson', + bounds: { + northEast: { + lat: bounds._northEast.lat, + lng: bounds._northEast.lng, + }, + southWest: { + lat: bounds._southWest.lat, + lng: bounds._southWest.lng, + }, + }, + crsCode: mmgisglobal.customCRS.code.replace( + 'EPSG:', + '' + ), + } + + if (layerData.time?.enabled === true) { + body.time = { + start: layerData.time.start, + startProp: layerData.time.startProp, + end: layerData.time.end, + endProp: layerData.time.endProp, + } + if (e.hasOwnProperty('endTime')) { + // Then this function was being called from timeChange + body.time.start = e.startTime + body.time.end = e.endTime + } + } + + const dateNow = new Date().getTime() + + _geodatasetRequestLastTimestamp[layerObj.name] = + Math.max( + _geodatasetRequestLastTimestamp[ + layerObj.name + ] || 0, + dateNow + ) calls.api( 'geodatasets_get', - { - layer: urlSplitRaw[1], - type: 'geojson', - bounds: { - northEast: { - lat: bounds._northEast.lat, - lng: bounds._northEast.lng, - }, - southWest: { - lat: bounds._southWest.lat, - lng: bounds._southWest.lng, - }, - }, - crsCode: mmgisglobal.customCRS.code.replace( - 'EPSG:', - '' - ), - }, + body, (data) => { - L_.clearVectorLayer(layerObj.name) - L_.updateVectorLayer(layerObj.name, data.body) + if ( + _geodatasetRequestLastTimestamp[ + layerObj.name + ] == dateNow + ) { + L_.clearVectorLayer(layerObj.name) + L_.updateVectorLayer( + layerObj.name, + data.body + ) + } }, (data) => { console.warn( diff --git a/src/essence/Basics/Layers_/Layers_.js b/src/essence/Basics/Layers_/Layers_.js index 6d7d4fa2..56beaa35 100644 --- a/src/essence/Basics/Layers_/Layers_.js +++ b/src/essence/Basics/Layers_/Layers_.js @@ -1034,11 +1034,27 @@ const L_ = { ) } else { const savedOptions = JSON.parse(JSON.stringify(layer.options)) + layer.setStyle({ color: color, stroke: color, }) layer.options = savedOptions + + // For some odd reason sometimes the first style does not work + // This makes sure it does + setTimeout(() => { + if ( + layer.options.color != color && + layer.options.stroke != color + ) { + layer.setStyle({ + color: color, + stroke: color, + }) + layer.options = savedOptions + } + }, 1) } } catch (err) { if (layer._icon) diff --git a/src/essence/Basics/Map_/Map_.js b/src/essence/Basics/Map_/Map_.js index ae6f7de0..dee8e85a 100644 --- a/src/essence/Basics/Map_/Map_.js +++ b/src/essence/Basics/Map_/Map_.js @@ -783,7 +783,11 @@ async function makeVectorLayer( { evenIfOff: evenIfOff, useEmptyGeoJSON: useEmptyGeoJSON }, add, (f) => { - Map_.map.on('moveend zoomend', f) + Map_.map.on('moveend', f) + L_.subscribeTimeChange( + `dynamicgeodataset_${layerObj.name}`, + f + ) } ) diff --git a/src/essence/Tools/Layers/LayersTool.js b/src/essence/Tools/Layers/LayersTool.js index 4e2292ae..3ae49e56 100644 --- a/src/essence/Tools/Layers/LayersTool.js +++ b/src/essence/Tools/Layers/LayersTool.js @@ -188,7 +188,6 @@ function interfaceWithMMGIS(fromInit) { let currentContrast let currentSaturation let currentBlend - //Build layerExport var layerExport switch (node[i].type) { @@ -231,6 +230,13 @@ function interfaceWithMMGIS(fromInit) { '', '', '', + node[i]?.variables?.dynamicExtent === true ? ['