diff --git a/lib/layer/decorate.mjs b/lib/layer/decorate.mjs index 5896f90ce..a2530a8ab 100644 --- a/lib/layer/decorate.mjs +++ b/lib/layer/decorate.mjs @@ -33,6 +33,7 @@ export default async layer => { const location = { layer, + table: layer.tableCurrent(), new: true } @@ -44,7 +45,7 @@ export default async layer => { mapp.utils.paramString({ locale: layer.mapview.locale.key, layer: layer.key, - table: layer.tableCurrent() + table: location.table }), body: JSON.stringify(Object.assign({ [layer.geom]: feature.geometry @@ -55,7 +56,7 @@ export default async layer => { layer.draw?.defaults || {})) }) - layer.reload() + //layer.reload() // Get the newly created location. mapp.location.get(location) diff --git a/lib/location/decorate.mjs b/lib/location/decorate.mjs index 4ae3f96cc..ce0bb7da9 100644 --- a/lib/location/decorate.mjs +++ b/lib/location/decorate.mjs @@ -7,6 +7,7 @@ export default location => { removeCallbacks: [], trash, update, + mvt_cache, updateCallbacks: [], }) @@ -47,6 +48,8 @@ async function update() { if (!Object.keys(newValues).length) return; + await this.mvt_cache() + await mapp.utils.xhr({ method: 'POST', url: @@ -59,6 +62,8 @@ async function update() { }), body: JSON.stringify(newValues), }); + + await this.mvt_cache() let dependents = this.infoj .filter(entry => typeof entry.newValue !== 'undefined') @@ -119,10 +124,30 @@ function flyTo (maxZoom) { }); } +async function mvt_cache() { + + if (!this.layer?.mvt_cache) return; + + await mapp.utils.xhr(`${this.layer.mapview.host}/api/query?` + + mapp.utils.paramString({ + template: 'mvt_cache_delete_intersects', + locale: this.layer.mapview.locale.key, + layer: this.layer.key, + mvt_cache: this.layer.mvt_cache, + table: this.table, + qID: this.layer.qID, + id: this.id, + geom: this.layer.geom + })) +} + async function trash() { if(!confirm(mapp.dictionary.confirm_delete)) return; + // Must clear cache before removing location from source. + await this.mvt_cache() + await mapp.utils.xhr(`${this.layer.mapview.host}/api/location/delete?` + mapp.utils.paramString({ locale: this.layer.mapview.locale.key, diff --git a/lib/location/get.mjs b/lib/location/get.mjs index c043d490a..48570ff7e 100644 --- a/lib/location/get.mjs +++ b/lib/location/get.mjs @@ -65,6 +65,8 @@ export default async function (location, list = location.layer.mapview.locations mapp.location.decorate(Object.assign(location, { infoj })) + location.new && await location.mvt_cache() + // Assign location to mapview. list[location.hook] = location diff --git a/lib/mapview/geoJSON.mjs b/lib/mapview/geoJSON.mjs index 71a930d7b..b4fb4781f 100644 --- a/lib/mapview/geoJSON.mjs +++ b/lib/mapview/geoJSON.mjs @@ -22,24 +22,6 @@ export default function (params){ if (!feature) return; - if (feature.getGeometry().getType() !== 'Point') { - - const styles = [params.Style].flat().map(style => style.getStroke()) - - // All other geometry types must have a Stroke style. - if (!styles.some(style => !!style)) { - - params.Style = new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: '#3399CC' - }) - }) - - console.warn('Missing Stroke style for geojson vector geometry') - } - - } - const layerVector = new ol.layer.Vector({ source: new ol.source.Vector({ features: [feature] diff --git a/lib/ui/elements/drawing.mjs b/lib/ui/elements/drawing.mjs index 95459989f..eff941a50 100644 --- a/lib/ui/elements/drawing.mjs +++ b/lib/ui/elements/drawing.mjs @@ -460,8 +460,7 @@ function circle(layer) { layer.mapview.interactions.draw(layer.draw.circle) - }}> - ${label}` + }}>${label}` // Return the config element in a drawer with the interaction toggle button as sibling. return mapp.utils.html.node`
@@ -469,8 +468,7 @@ function circle(layer) { header: mapp.utils.html`

${mapp.dictionary.circle_config}

`, - content: layer.draw.circle.panel - })} + content: layer.draw.circle.panel})} ${layer.draw.circle.btn}` } @@ -490,6 +488,7 @@ function locator(layer) { const location = { layer: layer, + table: layer.tableCurrent(), new: true } @@ -499,28 +498,24 @@ function locator(layer) { location.id = await mapp.utils.xhr({ method: 'POST', - url: `${location.layer.mapview.host}/api/location/new?` + + url: `${layer.mapview.host}/api/location/new?` + mapp.utils.paramString({ - locale: location.layer.mapview.locale.key, - layer: location.layer.key, - table: location.layer.tableCurrent() + locale: layer.mapview.locale.key, + layer: layer.key, + table: location.table }), body: JSON.stringify({ - [location.layer.geom]: { + [layer.geom]: { type: 'Point', coordinates: coords } }) }) - - location.layer.reload() - + mapp.location.get(location) }) - - }}> - ${mapp.dictionary.draw_position}` + }}>${mapp.dictionary.draw_position}` return layer.draw.locator.btn } \ No newline at end of file diff --git a/lib/ui/locations/entries/geometry.mjs b/lib/ui/locations/entries/geometry.mjs index 2eed9c105..ac9e91744 100644 --- a/lib/ui/locations/entries/geometry.mjs +++ b/lib/ui/locations/entries/geometry.mjs @@ -12,15 +12,11 @@ export default entry => { // Assigning the mapview to the entry makes the entry behave like a layer object for draw and modify interactions. entry.mapview = entry.location.layer.mapview - // Assign Style if not already assigned. - entry.Style = entry.Style + // Assign entry.style to location.style + entry.style = {...entry.location?.style, ...entry.style} - // Create OL style object from style object. - || typeof entry.style === 'object' && mapp.utils - .style(Object.assign({}, entry.location?.style || {}, entry.style)) - - // Assign style from location. - || entry.location.Style + // Create ol style from entry.style if not yet defined. + entry.Style ??= mapp.utils.style(entry.style) // Assign method to show geometry in mapview. entry.show = show @@ -56,10 +52,11 @@ export default entry => { draw(entry, list); // Push modify button into list. - if (entry.edit?.geometry) { + if (entry.edit) { // Only if the entry has a value, should the modify button be shown (as you can't modify a null geometry) if (entry.value) { + // If label is provided for the Modify Button, use it. Otherwise use default. let modifyBtnLabel = entry.edit?.modifyBtnOnly?.label || 'Modify Geometry'; @@ -95,22 +92,14 @@ export default entry => { // Allow for geometries to be shown before confirming the deletion. setTimeout(remove, 500) - function remove() { + async function remove() { // Return if user does not confirm deletion. if (!confirm('Delete Geometry?')) return; - // Set newValue to null in order update location field in database. - entry.newValue = null + entry.value = null - // Must be removed prior to database update / re-render. - if (entry.L) { - entry.location.layer.mapview.Map.removeLayer(entry.L) - delete entry.L - } - - // Re-renders location view after database update. - entry.location.update() + postUpdate(entry) } }}>Delete Geometry`) @@ -125,7 +114,7 @@ export default entry => { // Return drawer with list elements to entry node. return mapp.ui.elements.drawer({ data_id: `draw-drawer`, - class: `group ${entry.draw?.groupClassList && 'expanded' || ''}`, + class: entry.draw?.classList, header: mapp.utils.html` ${chkbox}
@@ -213,8 +202,10 @@ function modify(e, entry) { if (feature) { // Assign feature geometry as new value. - entry.newValue = feature.geometry - entry.location.update() + entry.value = feature.geometry + + postUpdate(entry) + return; } @@ -226,25 +217,73 @@ function modify(e, entry) { // Method for button element to call draw interaction. function draw(entry, list) { - if (typeof entry.draw === 'object') { - entry.draw.callback = feature => { - if (!feature) return; + // Drawing is only available within an edit context. + if (entry.edit?.draw) { + + entry.draw = entry.edit.draw + } + + // Editing with drawing is toggled off. + if (entry._edit?.draw) delete entry.draw + + // Short circuit without an entry.draw config. + if (!entry.draw) return; + + entry.draw.callback = feature => { + + if (!feature) return; + + // Assign feature geometry as new value. + entry.value = feature.geometry + + postUpdate(entry) + } - // Remove existing entry geometry layer. - entry.location.layer.mapview.Map.removeLayer(entry.L) + Object.keys(entry.draw).forEach(key => { - // Assign feature geometry as new value. - entry.newValue = feature.geometry - entry.location.update() + if (mapp.ui.elements.drawing[key]) { + list.push(mapp.ui.elements.drawing[key](entry)) } + }) - Object.keys(entry.draw).forEach(key => { +} - if (mapp.ui.elements.drawing[key]) { - list.push(mapp.ui.elements.drawing[key](entry)) - } - }) +async function postUpdate(entry) { + + if (entry.L) { + + // Remove existing entry geometry layer. + entry.location.layer.mapview.Map.removeLayer(entry.L) + + delete entry.L + } + + entry.location.view?.classList.add('disabled') + + // Update the geometry field value. + await mapp.utils.xhr({ + method: 'POST', + url: + `${entry.location.layer.mapview.host}/api/location/update?` + + mapp.utils.paramString({ + locale: entry.location.layer.mapview.locale.key, + layer: entry.location.layer.key, + table: entry.location.table, + id: entry.location.id, + }), + body: JSON.stringify({ [entry.field]: entry.value }), + }) + + if (entry.location.layer.geom === entry.field) { + // Reload the layer if the layers geom field has been updated. + entry.location.layer.reload() } + + // Render geometry entry with updated entry.value + mapp.utils.render(entry.node, mapp.ui.locations.entries.geometry(entry)) + + entry.location.view?.classList.remove('disabled') + } \ No newline at end of file diff --git a/lib/utils/queryParams.mjs b/lib/utils/queryParams.mjs index 1f2a609c7..a4b4c06e1 100644 --- a/lib/utils/queryParams.mjs +++ b/lib/utils/queryParams.mjs @@ -1,5 +1,5 @@ export default _this => { - + // Assign empty object if not defined. _this.queryparams = _this.queryparams || {} @@ -7,9 +7,7 @@ export default _this => { _this.queryparams.layer = _this.queryparams.layer || _this.viewport // Assign table name from layer. - if (_this.queryparams.table === true) { - _this.queryparams.table = _this.layer?.tableCurrent() - } + _this.queryparams.table &&= _this.location?.layer?.tableCurrent() // Assign fieldValues from the location to queryparams. if (Array.isArray(_this.queryparams.fieldValues) && _this.location) { diff --git a/lib/utils/style.mjs b/lib/utils/style.mjs index 9c9ecd766..6f2def83a 100644 --- a/lib/utils/style.mjs +++ b/lib/utils/style.mjs @@ -12,7 +12,7 @@ export default (style, feature) => { style.forEach(style => { // Only process icon for features if they are point geometries. - if (style.icon && (!feature || feature?.geometryType === 'Point')) { + if (style.icon) { // icon styles must always be processed as an array. style.icon = Array.isArray(style.icon) ? style.icon : [style.icon] @@ -44,7 +44,9 @@ export default (style, feature) => { })) }) - } else { + } + + if (style.fillColor || style.strokeColor) { // Create OL fill. let fill = style.fillColor && new ol.style.Fill({ diff --git a/mod/location/delete.js b/mod/location/delete.js index cfe11186a..7734680be 100644 --- a/mod/location/delete.js +++ b/mod/location/delete.js @@ -7,18 +7,10 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; - if (layer.mvt_cache) await dbs[layer.dbs || req.params.workspace.dbs](` - DELETE FROM ${layer.mvt_cache} - WHERE ST_Intersects(tile, ( - SELECT ${layer.geom} - FROM ${req.params.table} - WHERE ${layer.qID} = $1));`, [req.params.id]) - var rows = await dbs[layer.dbs || req.params.workspace.dbs](` DELETE FROM ${req.params.table} WHERE ${layer.qID} = $1;`, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') res.send('Location delete successful') - } \ No newline at end of file diff --git a/mod/location/new.js b/mod/location/new.js index 4e3ba0ea4..ef78fc866 100644 --- a/mod/location/new.js +++ b/mod/location/new.js @@ -45,17 +45,6 @@ module.exports = async (req, res) => { if (rows instanceof Error) return res.status(500).send('Failed to query PostGIS table.') - // Cached tiles which intersect the geometry must be retired. - if (layer.mvt_cache) await dbs[layer.dbs || req.params.workspace.dbs](` - DELETE - FROM ${layer.mvt_cache} - WHERE ST_Intersects(tile, ( - SELECT ${layer.geom} - FROM ${req.params.table} - WHERE ${layer.qID} = $1 - ));`, [rows[0].id]) - // Return id of newly created location. res.send(Array.isArray(rows) && rows[0].id?.toString() || null) - } \ No newline at end of file diff --git a/mod/location/update.js b/mod/location/update.js index f6790a9a3..5c6c83a08 100644 --- a/mod/location/update.js +++ b/mod/location/update.js @@ -47,28 +47,11 @@ module.exports = async (req, res) => { // Validate dynamic method call. if (!Object.hasOwn(dbs, layer.dbs || req.params.workspace.dbs) || typeof dbs[layer.dbs || req.params.workspace.dbs] !== 'function') return; - // Remove tiles from mvt_cache. - if (layer.mvt_cache) await dbs[layer.dbs || req.params.workspace.dbs](` - DELETE FROM ${layer.mvt_cache} - WHERE ST_Intersects(tile, ( - SELECT ${layer.geom} - FROM ${req.params.table} - WHERE ${layer.qID} = $1));`, [req.params.id]) - var q = `UPDATE ${req.params.table} SET ${fields.join()} WHERE ${layer.qID} = $1;` var rows = await dbs[layer.dbs || req.params.workspace.dbs](q, [req.params.id]) if (rows instanceof Error) return res.status(500).send('PostgreSQL query error - please check backend logs.') - // Remove tiles from mvt_cache. - if (layer.mvt_cache) await dbs[layer.dbs || req.params.workspace.dbs](` - DELETE FROM ${layer.mvt_cache} - WHERE ST_Intersects(tile, ( - SELECT ${layer.geom} - FROM ${req.params.table} - WHERE ${layer.qID} = $1));`, [req.params.id]) - res.send('This is fine.') - } \ No newline at end of file diff --git a/mod/query.js b/mod/query.js index 22c3a41b1..f2cc7f671 100644 --- a/mod/query.js +++ b/mod/query.js @@ -94,6 +94,11 @@ module.exports = async (req, res) => { try { template.template = template.render && template.render(req.params) || template.template + if (!template.template) { + + return res.status(400).send('Unable to parse template string.') + } + query = template.template // Replace parameter for identifiers, e.g. table, schema, columns diff --git a/mod/workspace/assignTemplates.js b/mod/workspace/assignTemplates.js index 6a9efd78c..49a5c9542 100644 --- a/mod/workspace/assignTemplates.js +++ b/mod/workspace/assignTemplates.js @@ -64,6 +64,9 @@ module.exports = async (workspace) => { admin: true, render: require('./templates/mvt_cache'), }, + mvt_cache_delete_intersects: { + template: require('./templates/mvt_cache_delete_intersects'), + }, // Default templates can be overridden by assigning a template with the same name. }, diff --git a/mod/workspace/templates/mvt_cache.js b/mod/workspace/templates/mvt_cache.js index 08ae8f96a..24a07a1ed 100644 --- a/mod/workspace/templates/mvt_cache.js +++ b/mod/workspace/templates/mvt_cache.js @@ -1,6 +1,8 @@ module.exports = _ => { const layer = _.workspace.locales[_.locale].layers[_.layer] + + if (!layer.mvt_cache) return; return ` diff --git a/mod/workspace/templates/mvt_cache_delete_intersects.js b/mod/workspace/templates/mvt_cache_delete_intersects.js new file mode 100644 index 000000000..ab4386154 --- /dev/null +++ b/mod/workspace/templates/mvt_cache_delete_intersects.js @@ -0,0 +1,7 @@ +module.exports = ` +DELETE + FROM \${mvt_cache} + WHERE ST_Intersects(tile, ( + SELECT \${geom} + FROM \${table} + WHERE \${qID} = %{id}));` \ No newline at end of file