From a8be0904c933a4d0298f5ea72ed8bbfbffb39aba Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 5 Dec 2023 16:07:13 +0000 Subject: [PATCH 1/5] wkt_properties format --- lib/layer/Style.mjs | 13 ++- lib/layer/format/mvt.mjs | 108 ++++++++++++++++++++-- lib/utils/featureFormats.mjs | 137 ++++++++++++++++------------ mod/workspace/templates/_queries.js | 4 + mod/workspace/templates/mvt.js | 4 +- mod/workspace/templates/mvt_geom.js | 28 ++++++ mod/workspace/templates/wkt.js | 33 ++++--- 7 files changed, 245 insertions(+), 82 deletions(-) create mode 100644 mod/workspace/templates/mvt_geom.js diff --git a/lib/layer/Style.mjs b/lib/layer/Style.mjs index 842cb43799..0be9781eac 100644 --- a/lib/layer/Style.mjs +++ b/lib/layer/Style.mjs @@ -18,7 +18,18 @@ export default layer => { if (Styles) return Styles } - feature.properties = feature.getProperties() + if (layer.featuresObject) { + + let id = feature.get('id').toString() + + feature.properties = layer.featuresObject[id] + + if (!feature.properties) return; + + } else { + + feature.properties = feature.getProperties() + } // Geojson features have a properties property if (feature.properties.properties) { diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index ba8eb91489..07aa5002dd 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -8,7 +8,7 @@ export default layer => { console.warn(`Layer ${layer.key} must be set to use SRID 3857.`) return; } - + // Assign empty style object if nullish. layer.style ??= {} @@ -25,9 +25,13 @@ export default layer => { layer.reload = () => { + if (layer.wkt_properties) { + changeEndLoad() + return; + } + layer.source.clear() layer.source.refresh() - layer.tilesLoaded = [] layer.featureSource.refresh() } @@ -44,15 +48,24 @@ export default layer => { cacheSize: 0, tileUrlFunction }) - + // Define source for mvt layer. layer.source = new ol.source.VectorTile({ format: new ol.format.MVT(), - transition: 0, - cacheSize: 0, - tileUrlFunction + transition: layer.transition, + cacheSize: layer.cacheSize }) + // Assign wkt properties load method. + if (layer.wkt_properties) { + layer.source.setTileUrlFunction(wktTileUrlFunction) + layer.mapview.Map.getTargetElement().addEventListener('changeEnd', changeEndLoad); + + } else { + + layer.source.setTileUrlFunction(tileUrlFunction) + } + function tileUrlFunction(tileCoord) { const table = layer.tableCurrent() @@ -76,7 +89,6 @@ export default layer => { y: tileCoord[2], locale: layer.mapview.locale.key, layer: layer.key, - srid: layer.mapview.srid, table, geom, filter: layer.filter?.current, @@ -86,6 +98,29 @@ export default layer => { return url } + function wktTileUrlFunction(tileCoord) { + + const table = layer.tableCurrent() + + if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() + + const geom = layer.geomCurrent() + + const url = `${layer.mapview.host}/api/query?${mapp.utils.paramString({ + template: 'mvt', + z: tileCoord[0], + x: tileCoord[1], + y: tileCoord[2], + locale: layer.mapview.locale.key, + layer: layer.key, + srid: layer.mapview.srid, + table, + geom + })}` + + return url + } + layer.L = new ol.layer.VectorTile({ key: layer.key, source: layer.source, @@ -95,4 +130,63 @@ export default layer => { style: mapp.layer.Style(layer) }) + function changeEndLoad() { + + layer.xhr?.abort() + + const table = layer.tableCurrent() + + if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() + + const geom = layer.geomCurrent() + + // Create a set of feature properties for styling. + layer.params.fields = [...new Set([ + Array.isArray(layer.style.theme?.fields) ? + layer.style.theme.fields : layer.style.theme?.field, + layer.style.theme?.field, + layer.style.label?.field + ].flat().filter(field => !!field))] + + const bounds = layer.mapview.getBounds() + + // Assign current viewport if queryparam is truthy. + let viewport = [bounds.west, bounds.south, bounds.east, bounds.north, layer.mapview.srid]; + + // Assign current viewport if queryparam is truthy. + let z = layer.mapview.Map.getView().getZoom(); + + layer.xhr = new XMLHttpRequest() + + layer.xhr.responseType = 'json' + + layer.xhr.onload = e => { + + layer.featuresObject = {} + + if (!e.target?.response) { + layer.L.changed() + return; + } + + mapp.utils.featureFormats.wkt_properties(layer, e.target.response) + + layer.L.changed() + } + + layer.xhr.open('GET', `${layer.mapview.host}/api/query?${mapp.utils.paramString({ + template: 'wkt', + locale: layer.mapview.locale.key, + layer: layer.key, + table, + geom, + no_geom: true, + viewport, + z, + filter: layer.filter?.current, + ...layer.params + })}`) + + layer.xhr.send() + } } \ No newline at end of file diff --git a/lib/utils/featureFormats.mjs b/lib/utils/featureFormats.mjs index 68a571822b..d9d6940c92 100644 --- a/lib/utils/featureFormats.mjs +++ b/lib/utils/featureFormats.mjs @@ -1,100 +1,121 @@ function initializeFieldStats(layer) { - if (layer.style?.theme?.field_stats) { - layer.style.theme.updated = true; - layer.style.theme.field_stats[layer.style.theme.field] = { - values: [] - }; - } + if (layer.style?.theme?.field_stats) { + layer.style.theme.updated = true; + layer.style.theme.field_stats[layer.style.theme.field] = { + values: [] + }; + } } export function geojson(layer, features) { - const formatGeojson = new ol.format.GeoJSON - - initializeFieldStats(layer); + const formatGeojson = new ol.format.GeoJSON - return features.map((feature) => { + initializeFieldStats(layer); - const properties = feature.properties + return features.map((feature) => { - if (layer.style?.theme?.field_stats) { + const properties = feature.properties - layer.style.theme.field_stats[layer.style.theme.field].values.push(parseFloat(properties[layer.style.theme.field])); - } + if (layer.style?.theme?.field_stats) { - return new ol.Feature({ - id: feature.id, - geometry: formatGeojson.readGeometry(feature.geometry, { - dataProjection: 'EPSG:' + layer.srid, - featureProjection: 'EPSG:' + layer.mapview.srid, - }), - properties - }) + layer.style.theme.field_stats[layer.style.theme.field].values.push(parseFloat(properties[layer.style.theme.field])); + } + return new ol.Feature({ + id: feature.id, + geometry: formatGeojson.readGeometry(feature.geometry, { + dataProjection: 'EPSG:' + layer.srid, + featureProjection: 'EPSG:' + layer.mapview.srid, + }), + properties }) + + }) } export function wkt(layer, features) { - const formatWKT = new ol.format.WKT + const formatWKT = new ol.format.WKT + + initializeFieldStats(layer); + + return features.map((feature) => { + + const properties = {} - initializeFieldStats(layer); + // Assign field key and value to properties object + layer.params.fields?.forEach((field, i) => { - return features.map((feature) => { + if (layer.style?.theme?.field_stats?.[field]) { - const properties = {} + layer.style?.theme?.field_stats?.[field].values.push(parseFloat(feature[i + 2])); + } - // Assign field key and value to properties object - layer.params.fields?.forEach((field, i) => { + properties[field] = feature[i + 2] + }) + + // Return feature from geometry with properties. + return new ol.Feature({ + id: feature.shift(), + geometry: formatWKT.readGeometry(feature.shift(), { + dataProjection: 'EPSG:' + layer.srid, + featureProjection: 'EPSG:' + layer.mapview.srid, + }), + properties + }) + + }) +} - if (layer.style?.theme?.field_stats?.[field]) { +export function wkt_properties(layer, features) { - layer.style?.theme?.field_stats?.[field].values.push(parseFloat(feature[i + 2])); - } + initializeFieldStats(layer); - properties[field] = feature[i + 2] - }) + for (const feature of features) { - // Return feature from geometry with properties. - return new ol.Feature({ - id: feature.shift(), - geometry: formatWKT.readGeometry(feature.shift(), { - dataProjection: 'EPSG:' + layer.srid, - featureProjection: 'EPSG:' + layer.mapview.srid, - }), - properties - }) + const properties = {} + // Assign field key and value to properties object + layer.params.fields?.forEach((field, i) => { + + if (layer.style?.theme?.field_stats?.[field]) { + + layer.style?.theme?.field_stats?.[field].values.push(parseFloat(feature[i + 1])); + } + + properties[field] = feature[i + 1] }) + layer.featuresObject[feature.shift()] = properties + } } export function cluster(layer, features) { - layer.max_size = 1 + layer.max_size = 1 - return features.map((vals, i) => { + return features.map((vals, i) => { - const geometry = new ol.geom.Point(vals.shift()) + const geometry = new ol.geom.Point(vals.shift()) - const count = vals.shift() + const count = vals.shift() - layer.max_size = layer.max_size > count ? layer.max_size: count; + layer.max_size = layer.max_size > count ? layer.max_size : count; - const id = vals.shift() + const id = vals.shift() - const properties = {count} - - layer.params.fields.forEach((field, i) => { - properties[field] = vals[i] - }) + const properties = { count } - return new ol.Feature({ - id, - geometry, - ...properties - }) + layer.params.fields.forEach((field, i) => { + properties[field] = vals[i] + }) + return new ol.Feature({ + id, + geometry, + ...properties }) + }) } diff --git a/mod/workspace/templates/_queries.js b/mod/workspace/templates/_queries.js index 7cd683e623..9c33d1ce1c 100644 --- a/mod/workspace/templates/_queries.js +++ b/mod/workspace/templates/_queries.js @@ -57,5 +57,9 @@ module.exports = { mvt: { render: require('./mvt'), value_only: true + }, + mvt_geom: { + render: require('./mvt_geom'), + value_only: true } } \ No newline at end of file diff --git a/mod/workspace/templates/mvt.js b/mod/workspace/templates/mvt.js index 808898fefb..a9aaf21a27 100644 --- a/mod/workspace/templates/mvt.js +++ b/mod/workspace/templates/mvt.js @@ -21,7 +21,7 @@ module.exports = _ => { ${_.layer.qID || null} as id, ${Array.isArray(fields) ? fields.toString() + ',' : ''} ST_AsMVTGeom( - ${_.geom || _.layer.geom}, + ${_.geom}, ST_TileEnvelope(${z},${x},${y}), 4096, 1024, @@ -32,7 +32,7 @@ module.exports = _ => { ${_.layer.z_field && `${_.layer.z_field} < ${z} AND` || ''} ST_Intersects( ST_TileEnvelope(${z},${x},${y}), - ${_.geom || _.layer.geom} + ${_.geom} ) \${filter} ) tile` diff --git a/mod/workspace/templates/mvt_geom.js b/mod/workspace/templates/mvt_geom.js new file mode 100644 index 0000000000..b32f7839b5 --- /dev/null +++ b/mod/workspace/templates/mvt_geom.js @@ -0,0 +1,28 @@ +module.exports = _ => { + + let + x = parseInt(_.x), + y = parseInt(_.y), + z = parseInt(_.z) + + return ` + SELECT + ST_AsMVT(tile, '${_.layer.key}', 4096, 'geom') mvt + FROM ( + SELECT + ${_.layer.qID || null} as id, + ST_AsMVTGeom( + ${_.geom}, + ST_TileEnvelope(${z},${x},${y}), + 4096, + 1024, + false + ) geom + FROM ${_.table} + WHERE + ST_Intersects( + ST_TileEnvelope(${z},${x},${y}), + ${_.geom} + ) + ) tile` +} \ No newline at end of file diff --git a/mod/workspace/templates/wkt.js b/mod/workspace/templates/wkt.js index 6dfc7414e7..dc253ae52f 100644 --- a/mod/workspace/templates/wkt.js +++ b/mod/workspace/templates/wkt.js @@ -1,20 +1,25 @@ module.exports = _ => { - // Get fields array from query params. - const fields = _.fields?.split(',') - .map(field => `${_.workspace.templates[field]?.template || field} AS ${field}`) - .filter(field => !!field) + // Get fields array from query params. + const fields = _.fields?.split(',') + .map(field => `${_.workspace.templates[field]?.template || field} AS ${field}`) + .filter(field => !!field) - // Push label (cluster) into fields - _.label && fields.push(`${_.workspace.templates[_.label]?.template || _.label} AS ${_.label}`) + // Unshift the geom field into the array. + if (_.geom && !_.no_geom) { - const where = _.viewport || `AND ${_.geom || _.layer.geom} IS NOT NULL` + fields.unshift(`ST_AsText(${_.geom}) AS geometry`) + } - return ` - SELECT - \${qID} AS id, - ST_AsText(${_.geom || _.layer.geom}) AS geometry - ${fields && `, ${fields.join(', ')}` || ''} - FROM \${table} - WHERE TRUE ${where} \${filter};` + // Push label (cluster) into fields + _.label && fields.push(`${_.workspace.templates[_.label]?.template || _.label} AS ${_.label}`) + + const where = _.viewport || `AND ${_.geom} IS NOT NULL` + + return ` + SELECT + \${qID} AS id + ${fields && `, ${fields.join(', ')}` || ''} + FROM \${table} + WHERE TRUE ${where} \${filter};` } \ No newline at end of file From 612e7b7b8d887f3ec1f9fdf50b0d2fcc92a3be84 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 5 Dec 2023 16:24:02 +0000 Subject: [PATCH 2/5] reduce cognitive load --- lib/layer/format/mvt.mjs | 141 ++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 67 deletions(-) diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index 07aa5002dd..18ae639588 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -58,15 +58,27 @@ export default layer => { // Assign wkt properties load method. if (layer.wkt_properties) { - layer.source.setTileUrlFunction(wktTileUrlFunction) - layer.mapview.Map.getTargetElement().addEventListener('changeEnd', changeEndLoad); + layer.source.setTileUrlFunction(wktTileUrlFunction(layer)) + layer.mapview.Map.getTargetElement().addEventListener('changeEnd', () => changeEndLoad(layer)); } else { - layer.source.setTileUrlFunction(tileUrlFunction) + layer.source.setTileUrlFunction(tileUrlFunction(layer)) } - function tileUrlFunction(tileCoord) { + layer.L = new ol.layer.VectorTile({ + key: layer.key, + source: layer.source, + renderBuffer: 200, + //renderMode: 'vector', + zIndex: layer.style?.zIndex || 1, + style: mapp.layer.Style(layer) + }) +} + +function wktTileUrlFunction(layer) { + + return tileCoord => { const table = layer.tableCurrent() @@ -74,14 +86,6 @@ export default layer => { const geom = layer.geomCurrent() - // Create a set of feature properties for styling. - layer.params.fields = [...new Set([ - Array.isArray(layer.style.theme?.fields) ? - layer.style.theme.fields : layer.style.theme?.field, - layer.style.theme?.field, - layer.style.label?.field - ].flat().filter(field => !!field))] - const url = `${layer.mapview.host}/api/query?${mapp.utils.paramString({ template: 'mvt', z: tileCoord[0], @@ -89,16 +93,18 @@ export default layer => { y: tileCoord[2], locale: layer.mapview.locale.key, layer: layer.key, + srid: layer.mapview.srid, table, - geom, - filter: layer.filter?.current, - ...layer.params + geom })}` return url } +} - function wktTileUrlFunction(tileCoord) { +function tileUrlFunction(layer) { + + return tileCoord => { const table = layer.tableCurrent() @@ -106,6 +112,14 @@ export default layer => { const geom = layer.geomCurrent() + // Create a set of feature properties for styling. + layer.params.fields = [...new Set([ + Array.isArray(layer.style.theme?.fields) ? + layer.style.theme.fields : layer.style.theme?.field, + layer.style.theme?.field, + layer.style.label?.field + ].flat().filter(field => !!field))] + const url = `${layer.mapview.host}/api/query?${mapp.utils.paramString({ template: 'mvt', z: tileCoord[0], @@ -113,80 +127,73 @@ export default layer => { y: tileCoord[2], locale: layer.mapview.locale.key, layer: layer.key, - srid: layer.mapview.srid, table, - geom + geom, + filter: layer.filter?.current, + ...layer.params })}` return url } - layer.L = new ol.layer.VectorTile({ - key: layer.key, - source: layer.source, - renderBuffer: 200, - //renderMode: 'vector', - zIndex: layer.style?.zIndex || 1, - style: mapp.layer.Style(layer) - }) - - function changeEndLoad() { - - layer.xhr?.abort() +} - const table = layer.tableCurrent() +function changeEndLoad(layer) { - if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() + layer.xhr?.abort() - const geom = layer.geomCurrent() + const table = layer.tableCurrent() - // Create a set of feature properties for styling. - layer.params.fields = [...new Set([ - Array.isArray(layer.style.theme?.fields) ? - layer.style.theme.fields : layer.style.theme?.field, - layer.style.theme?.field, - layer.style.label?.field - ].flat().filter(field => !!field))] + if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() - const bounds = layer.mapview.getBounds() + const geom = layer.geomCurrent() - // Assign current viewport if queryparam is truthy. - let viewport = [bounds.west, bounds.south, bounds.east, bounds.north, layer.mapview.srid]; + // Create a set of feature properties for styling. + layer.params.fields = [...new Set([ + Array.isArray(layer.style.theme?.fields) ? + layer.style.theme.fields : layer.style.theme?.field, + layer.style.theme?.field, + layer.style.label?.field + ].flat().filter(field => !!field))] - // Assign current viewport if queryparam is truthy. - let z = layer.mapview.Map.getView().getZoom(); + const bounds = layer.mapview.getBounds() - layer.xhr = new XMLHttpRequest() + // Assign current viewport if queryparam is truthy. + let viewport = [bounds.west, bounds.south, bounds.east, bounds.north, layer.mapview.srid]; - layer.xhr.responseType = 'json' + // Assign current viewport if queryparam is truthy. + let z = layer.mapview.Map.getView().getZoom(); - layer.xhr.onload = e => { + layer.xhr = new XMLHttpRequest() - layer.featuresObject = {} + layer.xhr.responseType = 'json' - if (!e.target?.response) { - layer.L.changed() - return; - } + layer.xhr.onload = e => { - mapp.utils.featureFormats.wkt_properties(layer, e.target.response) + layer.featuresObject = {} + if (!e.target?.response) { layer.L.changed() + return; } - layer.xhr.open('GET', `${layer.mapview.host}/api/query?${mapp.utils.paramString({ - template: 'wkt', - locale: layer.mapview.locale.key, - layer: layer.key, - table, - geom, - no_geom: true, - viewport, - z, - filter: layer.filter?.current, - ...layer.params - })}`) + mapp.utils.featureFormats.wkt_properties(layer, e.target.response) - layer.xhr.send() + layer.L.changed() } + + layer.xhr.open('GET', `${layer.mapview.host}/api/query?${mapp.utils.paramString({ + template: 'wkt', + locale: layer.mapview.locale.key, + layer: layer.key, + table, + geom, + no_geom: true, + viewport, + z, + filter: layer.filter?.current, + ...layer.params + })}`) + + layer.xhr.send() } \ No newline at end of file From f861eb54e893a613820f6133dd3755a544f5a700 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Tue, 12 Dec 2023 16:57:35 +0000 Subject: [PATCH 3/5] update table check in mvt url --- lib/layer/format/mvt.mjs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index 18ae639588..50d98fe7ac 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -82,7 +82,17 @@ function wktTileUrlFunction(layer) { const table = layer.tableCurrent() - if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() + // The layer has no data for this zoom level. + if (!table) { + layer.source.clear(); + return; + } + + // The layer does not display and doesn't have clones. + if (!layer.display && !layer.clones?.size) { + layer.source.clear(); + return; + } const geom = layer.geomCurrent() @@ -108,7 +118,17 @@ function tileUrlFunction(layer) { const table = layer.tableCurrent() - if ((!table || !layer.display) && !layer.clones?.size) return layer.source.clear() + // The layer has no data for this zoom level. + if (!table) { + layer.source.clear(); + return; + } + + // The layer does not display and doesn't have clones. + if (!layer.display && !layer.clones?.size) { + layer.source.clear(); + return; + } const geom = layer.geomCurrent() From 2f86a7c0b071776e07bba5c2826e92ddb135b6e4 Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 14 Dec 2023 10:57:29 +0000 Subject: [PATCH 4/5] properties changeloadevent --- lib/layer/format/mvt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index 50d98fe7ac..a3c5deff82 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -26,7 +26,7 @@ export default layer => { layer.reload = () => { if (layer.wkt_properties) { - changeEndLoad() + () => changeEndLoad(layer) return; } From 89a1a33b11027aff7296b152f889a74031fc277e Mon Sep 17 00:00:00 2001 From: dbauszus-glx Date: Thu, 14 Dec 2023 10:58:35 +0000 Subject: [PATCH 5/5] changeEndLoad(layer) argument --- lib/layer/format/mvt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/layer/format/mvt.mjs b/lib/layer/format/mvt.mjs index a3c5deff82..434b5664ae 100644 --- a/lib/layer/format/mvt.mjs +++ b/lib/layer/format/mvt.mjs @@ -26,7 +26,7 @@ export default layer => { layer.reload = () => { if (layer.wkt_properties) { - () => changeEndLoad(layer) + changeEndLoad(layer) return; }