diff --git a/lib/layer/featureFormats.mjs b/lib/layer/featureFormats.mjs index 70cc96165f..68c5f0f276 100644 --- a/lib/layer/featureFormats.mjs +++ b/lib/layer/featureFormats.mjs @@ -71,7 +71,7 @@ The layer.params.fields[] property values will be added to the layer.featureFiel export function wkt(layer, features) { if (!Array.isArray(features)) return; - + const formatWKT = new ol.format.WKT mapp.layer.featureFields.reset(layer); @@ -147,3 +147,49 @@ export function wkt_properties(layer, features) { mapp.layer.featureFields.process(layer); } + +/** +@function cluster + +@description +The cluster function processes an array of feature data and returns an array of OpenLayers features to be added to the layer source. + +It creates Point geometries from coordinate pairs, extracts count and ID information, and assigns additional properties based on the layer's field definitions. + +The function also tracks the maximum count across all features in the layer.max_size property. + +@param {layer} layer A layer object, likely an OpenLayers layer. +@param {Array} features An array of feature data, where each feature is represented as an array. +@property {number} layer.max_size The maximum count across all features. +@property {Object} layer.params +@property {Array} layer.params.fields Array of field names for additional feature properties. +@returns {Array} Array of OpenLayers Feature objects. +*/ +export function cluster(layer, features) { + + layer.max_size = 1 + + return features.map((vals, i) => { + + const geometry = new ol.geom.Point(vals.shift()) + + const count = vals.shift() + + layer.max_size = layer.max_size > count ? layer.max_size : count; + + const id = vals.shift() + + const properties = { count } + + layer.params.fields.forEach((field, i) => { + properties[field] = vals[i] + }) + + return new ol.Feature({ + id, + geometry, + ...properties + }) + + }) +} diff --git a/lib/layer/format/vector.mjs b/lib/layer/format/vector.mjs index b313fb26a4..2869984df3 100644 --- a/lib/layer/format/vector.mjs +++ b/lib/layer/format/vector.mjs @@ -124,7 +124,7 @@ export default function vector(layer) { clearTimeout(layer.timeout) - layer.timeout = setTimeout(()=>layer.reload(), 100) + layer.timeout = setTimeout(() => layer.reload(), 100) }) } @@ -267,13 +267,13 @@ function clusterConfig(layer) { return; }; - // Check if distance is defined as <1 and layer is a format wkt - if (layer.cluster.distance < 1) { + // Check if distance is defined as <1 and layer is a format wkt + if (layer.cluster.distance < 1) { console.warn(`Layer: ${layer.key}, cluster.distance is less than 1 [pixel]. The cluster config will be removed.`) - + // Remove the cluster object - delete layer.cluster; + delete layer.cluster; return; } @@ -284,7 +284,7 @@ function clusterConfig(layer) { if (typeof layer.cluster.resolution === 'number') { // Assign resolution as float. layer.params.resolution = parseFloat(layer.cluster.resolution); - } + } // Otherwise, warn and return. else { console.warn(`Layer: ${layer.key}, cluster.resolution must be a number.`) diff --git a/tests/browser/local.test.mjs b/tests/browser/local.test.mjs index f840b37c69..a36fb2a0d6 100644 --- a/tests/browser/local.test.mjs +++ b/tests/browser/local.test.mjs @@ -12,8 +12,8 @@ import { ui_elementsTest } from '../lib/ui/elements/_elements.test.mjs'; import { ui_layers } from '../lib/ui/layers/_layers.test.mjs'; import { entriesTest } from '../lib/ui/locations/entries/_entries.test.mjs'; -import {uiTest} from '../lib/ui/_ui.test.mjs'; - +import { uiTest } from '../lib/ui/_ui.test.mjs'; +import { formatTest } from '../lib/layer/format/_format.test.mjs'; //API Tests await workspaceTest(); await queryTest(); @@ -52,4 +52,6 @@ await entriesTest.geometryTest(mapview); await ui_layers.filtersTest(mapview); -await uiTest.Tabview(); \ No newline at end of file +await uiTest.Tabview(); + +await formatTest.vectorTest(mapview); \ No newline at end of file diff --git a/tests/lib/layer/format/_format.test.mjs b/tests/lib/layer/format/_format.test.mjs new file mode 100644 index 0000000000..74ee052f37 --- /dev/null +++ b/tests/lib/layer/format/_format.test.mjs @@ -0,0 +1,5 @@ +import { vectorTest } from './vector.test.mjs'; + +export const formatTest = { + vectorTest +} \ No newline at end of file diff --git a/tests/lib/layer/format/vector.test.mjs b/tests/lib/layer/format/vector.test.mjs new file mode 100644 index 0000000000..5eda373300 --- /dev/null +++ b/tests/lib/layer/format/vector.test.mjs @@ -0,0 +1,107 @@ +/** + * This is the vector module used to test /lib/layer/format/vector + * @module layer/format/vector + */ + +/** + * This is the entry point function for the vector test module. + * @function vectorTest + * @param {object} mapview + */ +export async function vectorTest(mapview) { + codi.describe('Layer Format: Vector', async () => { + + /** + * ### Should be able to create a cluster layer + * 1. It takes layer params. + * 2. Decorates the layer. + * 3. We then give the vector function the layer. + * 4. We expect the format of the layer to change to 'cluster' + * @function it + */ + codi.it('Should create a cluster layer', async () => { + const layer_params = { + mapview: mapview, + 'key': 'cluster_test', + 'display': true, + 'group': 'layer', + 'format': 'wkt', //This should change to cluster when used in the vector function + 'dbs': 'NEON', + 'table': 'test.scratch', + 'srid': '3857', + 'geom': 'geom_3857', + 'qID': 'id', + 'cluster': { + 'resolution': 0.005, + 'hexgrid': true + }, + 'infoj': [ + { + 'type': 'pin', + 'label': 'ST_PointOnSurface', + 'field': 'pin', + 'fieldfx': 'ARRAY[ST_X(ST_PointOnSurface(geom_3857)),ST_Y(ST_PointOnSurface(geom_3857))]' + } + ], + 'style': { + 'default': { + 'icon': { + 'type': 'dot', + 'fillColor': '#13336B' + } + }, + 'cluster': { + 'icon': { + 'type': 'target', + 'fillColor': '#E6FFFF', + 'layers': { + '1': '#13336B', + '0.85': '#E6FFFF' + } + } + }, + 'highlight': { + 'scale': 1.3 + }, + 'theme': { + 'title': 'theme_1', + 'type': 'graduated', + 'field': 'test_template_style', + 'graduated_breaks': 'greater_than', + 'template': { + 'key': 'test_template_style', + 'template': '100-99', + 'value_only': true + }, + 'cat_arr': [ + { + 'value': 0, + 'label': '0 to 5%', + 'style': { + 'icon': { + 'fillColor': '#ffffcc', + 'fillOpacity': 0.8 + } + } + } + ] + } + } + } + + //Decorating layer + const layer = await mapp.layer.decorate(layer_params); + + //Passing the layer to the format method + mapp.layer.formats.vector(layer); + + //Showing the layer + layer.show(); + codi.assertTrue(typeof layer.show === 'function', 'The layer should have a show function'); + codi.assertTrue(typeof layer.reload === 'function', 'The layer should have a reload function'); + codi.assertTrue(typeof layer.setSource === 'function', 'The layer should have a setSource function'); + codi.assertTrue(layer.format === 'cluster', 'The layer should have the format cluster'); + layer.hide(); + }); + }); +} \ No newline at end of file diff --git a/tests/workspace.json b/tests/workspace.json index 0a153f82eb..c5692615a5 100644 --- a/tests/workspace.json +++ b/tests/workspace.json @@ -310,7 +310,8 @@ "src": "file:/tests/assets/layers/location_mock/layer.json" } ] - } + }, + "cluster_test": {} } } } \ No newline at end of file