diff --git a/examples/assets/data/adresses.carte b/examples/assets/data/adresses.carte new file mode 100644 index 0000000..d6887f9 --- /dev/null +++ b/examples/assets/data/adresses.carte @@ -0,0 +1,597 @@ +{ + "param": { + "lon": 2.5588762304695947, + "lat": 48.269411616712944, + "zoom": 6.702255765261408, + "rot": 0, + "controlParams": { + "zoomBtn": true, + "scaleLine": true, + "pSearchBar": false, + "legend": true, + "coords": false, + "selectLayer": true, + "geoloc": false, + "profil": false, + "printDlg": false + }, + "proj": { + "valeur": "EPSG:4326" + } + }, + "legende": { + "legendtitle": "Ma légende", + "lineHeight": 45, + "legendWidth": 300, + "legendParam": { + "width": 300, + "lineHeight": 45 + }, + "items": [], + "legendVisible": true + }, + "layers": [ + { + "id": 1, + "type": "Geoportail", + "name": "Plan IGN", + "title": "Plan IGN", + "visibility": true, + "opacity": 1, + "description": "Cartographie multi-échelles sur le territoire national, issue des bases de données vecteur de l’IGN, mis à jour régulièrement et réalisée selon un processus entièrement automatisé. Version actuellement en beta test", + "copyright": "", + "popupContent": {}, + "grayscale": false, + "blendMode": "", + "minZoom": null, + "maxZoom": null, + "crop": {}, + "layer": "GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2" + }, + { + "dessin": true, + "attributes": [], + "id": 3, + "type": "Vector", + "name": "", + "title": "adresses", + "visibility": true, + "opacity": 1, + "copyright": "", + "selectable": true, + "popupContent": {}, + "grayscale": false, + "blendMode": "", + "minZoom": null, + "maxZoom": null, + "declutter": false, + "conditionStyle": [], + "mode": "cluster", + "cluster": true, + "radiusCluster": 40, + "clusterDistance": 40, + "crop": {}, + "data": { + "type": "FeatureCollection", + "decimals": 3, + "hashProperties": [ + "#", + "Score", + "Qualité", + "Adresse géocodée", + "nom", + "rue", + "cp", + "commune", + "Téléphone", + "Horaires d'ouverture", + "Code INSEE", + "Longitude", + "Latitude" + ], + "features": [ + [ + "Ik.Yn,CAT}4g", + [ + 0, + 6, + 1, + 0.84, + 2, + "Numéro", + 3, + "8 Avenue Blaise Pascal 77420 Champs-sur-Marne", + 4, + "École nationale des sciences géographiques", + 5, + "6/8 avenue Blaise Pascal", + 6, + "77420", + 7, + "CHAMPS-SUR-MARNE", + 8, + "01 64 15 30 01", + 9, + "", + 10, + "77083", + 11, + 2.588727, + 12, + 48.840563 + ] + ], + [ + "H>d[R,CAWbw@", + [ + 0, + 7, + 1, + 0.97, + 2, + "Numéro", + 3, + "73 Avenue de Paris 94160 Saint-Mandé", + 4, + "Centre de production Île-de-France", + 5, + "73 avenue de Paris", + 6, + "94160", + 7, + "SAINT-MANDE", + 8, + "", + 9, + "", + 10, + "94067", + 11, + 2.424573, + 12, + 48.845726 + ] + ], + [ + "H>d[R,CAWbw@", + [ + 0, + 1, + 1, + 0.97, + 2, + "Numéro", + 3, + "73 Avenue de Paris 94160 Saint-Mandé", + 4, + "IGN - Direction générale", + 5, + "73 avenue de Paris", + 6, + "94160", + 7, + "SAINT-MANDE", + 8, + "01 43 98 80 00", + 9, + "du lundi au vendredi, de 9h à 17h", + 10, + "94067", + 11, + 2.424573, + 12, + 48.845726 + ] + ], + [ + "H>HV ,CAWR3u", + [ + 0, + 5, + 1, + 0.96, + 2, + "Numéro", + 3, + "8 Avenue Pasteur 94160 Saint-Mandé", + 4, + "Le Géoroom (Photothèque nationale et cartothèque)", + 5, + "8 avenue Pasteur", + 6, + "94160", + 7, + "SAINT-MANDE", + 8, + "01 64 15 30 01", + 9, + "Du mardi au vendredi de 10h à 19h, Le samedi de 10h à 18h", + 10, + "94067", + 11, + 2.42373, + 12, + 48.845483 + ] + ], + [ + "BK'cG,CCF9[i", + [ + 0, + 11, + 1, + 0.96, + 2, + "Numéro", + 3, + "73 Rue Marie Curie 14200 Hérouville-Saint-Clair", + 4, + "Direction inter régionale nord-ouest", + 5, + "73 rue Marie Curie", + 6, + "14200", + 7, + "HEROUVILLE-SAINT-CLAIR", + 8, + "", + 9, + "", + 10, + "14327", + 11, + -0.344472, + 12, + 49.212742 + ] + ], + [ + "G:Rrs,CDYO$U", + [ + 0, + 4, + 1, + 0.94, + 2, + "Numéro", + 3, + "3 Rue Cassini 60000 Tillé", + 4, + "IGN - Service de l'imagerie et de l'aéronautique", + 5, + "3 rue Cassini", + 6, + "60000", + 7, + "TILLE", + 8, + "", + 9, + "", + 10, + "60639", + 11, + 2.105947, + 12, + 49.454222 + ] + ], + [ + "J`K,B#FdMT", + [ + 0, + 12, + 1, + 0.66, + 2, + "Numéro", + 3, + "1330 Avenue Jean-René Guillibert Gautier de la Lauzière 13290 Aix-en-Provence", + 4, + "Direction inter régionale sud-est", + 5, + "1330 av J R G Gautier de la Lauziere", + 6, + "13290", + 7, + "AIX-EN-PROVENCE", + 8, + "", + 9, + "", + 10, + "13001", + 11, + 5.371065, + 12, + 43.478817 + ] + ], + [ + "F0VHd,B] W-'", + [ + 0, + 3, + 1, + 0.94, + 2, + "Point d'intérêt", + 3, + "Le Camp des Landes 41200 Villefranche-sur-Cher", + 4, + "IGN Sologne", + 5, + "Le Camp des Landes", + 6, + "41200", + 7, + "VILLEFRANCHE-SUR-CHER", + 8, + "", + 9, + "", + 10, + "41280", + 11, + 1.716838, + 12, + 47.294215 + ] + ], + [ + "E{K^w,B]cZsl", + [ + 0, + 10, + 1, + 0.97, + 2, + "Numéro", + 3, + "2 Boulevard de la Loire 44200 Nantes", + 4, + "Direction inter régionale nord-ouest", + 5, + "2 boulevard de la Loire", + 6, + "44200", + 7, + "NANTES", + 8, + "", + 9, + "", + 10, + "44109", + 11, + -1.526916, + 12, + 47.211575 + ] + ], + [ + "P%> u,B:oRm6", + [ + 0, + 9, + 1, + 0.98, + 2, + "Numéro", + 3, + "239 Rue Garibaldi 69003 Lyon", + 4, + "Direction inter régionale centre-est", + 5, + "239 rue Garibaldi", + 6, + "69003", + 7, + "LYON", + 8, + "", + 9, + "", + 10, + "69383", + 11, + 4.853271, + 12, + 45.754865 + ] + ], + [ + "E.m_E,B#i]_&", + [ + 0, + 2, + 1, + 0.96, + 2, + "Numéro", + 3, + "6 Avenue de l’Europe 31520 Ramonville-Saint-Agne", + 4, + "IGN Espace", + 5, + "6 avenue de l'Europe", + 6, + "31520", + 7, + "RAMONVILLE-SAINT-AGNE", + 8, + "", + 9, + "", + 10, + "31446", + 11, + 1.487341, + 12, + 43.551478 + ] + ], + [ + "ClvfY,B)o$QI", + [ + 0, + 15, + 1, + 0.97, + 2, + "Rue", + 3, + "Rue Pierre Ramond 33160 Saint-Médard-en-Jalles", + 4, + "Direction inter régionale sud-ouest", + 5, + "Rue Pierre Ramond ", + 6, + "33160", + 7, + "SAINT-MEDARD-EN-JALLES", + 8, + "", + 9, + "", + 10, + "33449", + 11, + -0.743015, + 12, + 44.889924 + ] + ], + [ + "I|Cy2,B`cn#-", + [ + 0, + 8, + 1, + 0.95, + 2, + "Point d'intérêt", + 3, + "45290 Château des BarresNogent-sur-Vernisson", + 4, + "Service de l'inventaire forestier", + 5, + "Chateau des Barres", + 6, + "45290", + 7, + "NOGENT-SUR-VERNISSON", + 8, + "", + 9, + "", + 10, + "45229", + 11, + 2.762028, + 12, + 47.835905 + ] + ], + [ + "T}:]j,B~!^&L", + [ + 0, + 13, + 1, + 0.96, + 2, + "Numéro", + 3, + "1 Rue des Blanches Terres 54250 Champigneulles", + 4, + "Direction inter régionale nord-est", + 5, + "1 rue des Blanches Terres", + 6, + "54250", + 7, + "Champigneulles", + 8, + "", + 9, + "", + 10, + "54115", + 11, + 6.156285, + 12, + 48.740098 + ] + ] + ], + "style": [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ], + "popupContent": [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ] + } + } + ], + "symbolLib": [] +} \ No newline at end of file diff --git a/examples/assets/data/cluster.carte b/examples/assets/data/cluster.carte new file mode 100644 index 0000000..4d9af6b --- /dev/null +++ b/examples/assets/data/cluster.carte @@ -0,0 +1,264 @@ +{ + "param": { + "lon": -0.2632504808230034, + "lat": 45.89028989496441, + "zoom": 7.810533891551703, + "rot": 0, + "controlParams": { + "zoomBtn": true, + "scaleLine": true, + "pSearchBar": false, + "legend": false, + "coords": false, + "selectLayer": true, + "geoloc": false, + "profil": false, + "printDlg": false + }, + "proj": { + "valeur": "EPSG:4326" + } + }, + "legende": { + "legendtitle": "Ma légende", + "lineHeight": 45, + "legendWidth": 300, + "legendParam": { + "width": 300, + "lineHeight": 45 + }, + "items": [], + "legendVisible": false + }, + "layers": [ + { + "id": 1, + "type": "Geoportail", + "name": "Plan IGN", + "title": "Plan IGN", + "visibility": true, + "opacity": 1, + "description": "Cartographie multi-échelles sur le territoire national, issue des bases de données vecteur de l’IGN, mis à jour régulièrement et réalisée selon un processus entièrement automatisé. Version actuellement en beta test", + "copyright": "", + "popupContent": {}, + "grayscale": false, + "blendMode": "", + "minZoom": null, + "maxZoom": null, + "crop": {}, + "layer": "GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2" + }, + { + "dessin": true, + "attributes": [], + "id": 2, + "type": "Vector", + "name": "Dessin", + "title": "Dessin", + "visibility": true, + "opacity": 1, + "copyright": "", + "selectable": true, + "popupContent": {}, + "grayscale": false, + "blendMode": "", + "minZoom": null, + "maxZoom": null, + "declutter": false, + "style": [], + "conditionStyle": [], + "mode": "cluster", + "cluster": true, + "radiusCluster": 40, + "clusterDistance": 40, + "crop": {}, + "data": { + "type": "FeatureCollection", + "decimals": 3, + "hashProperties": [], + "features": [ + [ + "Ds@$.,B@.8%/" + ], + [ + "DxK/V,B@{HPB" + ], + [ + "BiIs<,B[Dn9%" + ], + [ + "B%?gt,B[`RQ6" + ], + [ + "BO*WW,B]UmRJ" + ], + [ + "Em/S1,B^7d#D" + ], + [ + "EC6qI,B=R*3o" + ], + [ + "}D%g,B=5p4P" + ], + [ + "BT9R=,B>@)6V" + ], + [ + "C+F_2,B??[7=" + ], + [ + "C9< ,B?_V0r" + ], + [ + "BZL2&,B>=N9[" + ], + [ + "+ihM,B?pX7O" + ], + [ + "qA)t,B[gLP9" + ], + [ + "L[d5,B??[7=" + ], + [ + "J#K>,B=ZB30" + ], + [ + "mk!%,B]4qR&" + ], + [ + "FvTl3,B=rD3[" + ], + [ + "CRiQ4,B^0K![" + ], + [ + "B~f*?,B?XV6." + ], + [ + "B`$$K,B>fe5e" + ], + [ + "DOO:s,B@:YO:" + ], + [ + "ChFgr,B@vnOc" + ], + [ + "Bs$!~,B@:YO:" + ], + [ + "B*1c~,B[%PQe" + ] + ], + "style": [ + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + }, + { + "zi": 0 + } + ], + "popupContent": [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ] + } + } + ], + "symbolLib": [] +} \ No newline at end of file diff --git a/examples/cluster.html b/examples/cluster.html new file mode 100644 index 0000000..175d353 --- /dev/null +++ b/examples/cluster.html @@ -0,0 +1,35 @@ + + + + Macarte-utils + + + + + + + + + + + + + + + + + + +
+

Cluster attributes

+ +
+

Attribut à afficher

+ +
+
+ + + + + \ No newline at end of file diff --git a/examples/cluster/index.css b/examples/cluster/index.css new file mode 100644 index 0000000..8419f14 --- /dev/null +++ b/examples/cluster/index.css @@ -0,0 +1,31 @@ +#form { + display: inline-block; + vertical-align: top; + padding-right: 1em; + width: 22em; + box-sizing: border-box; +} +#map { + display: inline-block; + width: calc(100% - 22em); + min-height: 70em; +} + +.ol-popup-content tr td:first-child { + max-width: 10em; + overflow: hidden; + white-space: nowrap; +} + +#form > * { + display: block; + margin: .5em 0 0; + padding: 1em 0; + border-bottom: 5px solid #999; + width: 100%; +} + +#form input[type="number"] { + max-width: 7em; + margin: 0 .25em; +} \ No newline at end of file diff --git a/examples/cluster/index.js b/examples/cluster/index.js new file mode 100644 index 0000000..eb0ddda --- /dev/null +++ b/examples/cluster/index.js @@ -0,0 +1,98 @@ +import Carte from '../../Carte'; +import charte from '../../charte/macarte' +import ColorBrewer from '../../control/ColorBrewer'; +import PopupFeature from 'ol-ext/overlay/PopupFeature' +import Statistic from '../../layer/Statistic'; +import ol_ext_element from 'ol-ext/util/element'; +import notification from '../../dialog/notification'; +import dialog from '../../dialog/dialog'; +import element from 'ol-ext/util/element'; + +import './index.css' +import RepartitionGraph from '../../control/RepartitionGraph'; +import BaseLayer from 'ol/layer/Base'; +import Base from '../../format/Base'; + +charte.setApp('macarte', 'Cluster'); + +form = document.getElementById("form") + +// colonne(s) à représenter +const selectAttr = form.querySelector('[data-stat="cols"]'); +selectAttr.addEventListener('change', () => { + const cols = []; + selectAttr.querySelectorAll('option').forEach(o => { + if (o.selected) { + cols.push(o.value); + } + }) +}); + + +// Load carte statistic +const carte = new Carte({ + target: 'map', + // id: '32abd17790a2c94c703a9e7a2b8269ab', + // id: '81dd3298118b6cc4704c11726e065092', + url: './data/adresses.carte' +}) +carte.showControl('legend'); + +// Get statistical layer on read +let layer = null; +carte.on('read', () => { + carte.getMap().getLayers().forEach(l => { + if (l.values_.type === "Vector") { + layer = l + } + }) + // Fill attributes + features = layer.getSource().getFeatures(); + if (features.length) { + Object.keys(features[0].getProperties()).forEach(property => { + if (property !== features[0].getGeometryName()) { + element.create('OPTION', { + value: property, + text: property, + parent: selectAttr + }); + } + }); + } +}) + +// Add a popupfeature +// carte.getMap().addOverlay(new PopupFeature({ +// canFix: true, +// closeBox: true, +// minibar: true, +// template: (f) => { +// const prop = f.getProperties(); +// delete prop.geometry; +// return { +// attributes: Object.keys(prop) +// } +// }, +// select: carte.getSelect() +// })) +carte.setSelectStyle({points: true}); + + +// Update on select +carte.getSelect().on('select', updateSelection) + +function updateSelection() { + // Display nb object selected + carte.getSelect().getFeatures().forEach(f => { + f.getProperties().features.forEach(l => { + const prop = l.getProperties() + delete prop.geometry + }) + }); + +} + +/* DEBUG */ +window.carte = carte +window.layer = layer +/**/ \ No newline at end of file diff --git a/examples/index.html b/examples/index.html index 911d005..ec284e4 100644 --- a/examples/index.html +++ b/examples/index.html @@ -79,6 +79,10 @@

🗺️ Ma carte

COG Exemple d'affichage COG. +
  • + Clusters + Test for clusters +
  • diff --git a/format/Carte.js b/format/Carte.js index d6719c8..2244e4f 100644 --- a/format/Carte.js +++ b/format/Carte.js @@ -161,7 +161,6 @@ Carte.prototype.writeLayers = function(layers, uncompressed) { data.push(layer); } else { console.warn('Layer [' + l.get('type') + ':' + l.get('title') + '] has no writer...') - console.log(l) } }) return data; diff --git a/format/layer/Layer.js b/format/layer/Layer.js index 28e37ea..e87a41a 100644 --- a/format/layer/Layer.js +++ b/format/layer/Layer.js @@ -103,7 +103,8 @@ Layer.prototype.readOptions = function(layer, options) { if (options.cluster || options.mode === 'cluster') { layer.setMode('cluster', { distance: options.clusterDistance || options.radiusCluster || 40, - maxZoomCluster: parseInt(options.maxZoomCluster) + maxZoomCluster: parseInt(options.maxZoomCluster), + clusterStat: options.clusterStat }); } else if (options.mode) { layer.setMode(options.mode || 'vector'); @@ -198,6 +199,7 @@ Layer.prototype.writeOptions = function(layer, options) { options.cluster = true; options.clusterDistance = options.radiusCluster = layer.get('clusterDistance'); options.maxZoomCluster = layer.get('maxZoomCluster'); + options.clusterStat = layer.get("clusterStat"); } } // Crop diff --git a/layer/VectorStyle.js b/layer/VectorStyle.js index 05c7ec4..95fe54d 100644 --- a/layer/VectorStyle.js +++ b/layer/VectorStyle.js @@ -299,6 +299,13 @@ VectorStyle.prototype.setMode = function(mode, options) { this.set('clusterDistance', parseFloat(options.clusterDistance || options.distance) || 40); this.set('clusterDash', !!options.clusterDash); this.set('clusterColor', !!options.clusterColor); + + // Attributes and colors + if (options.clusterStat) { + this.set('clusterStat', options.clusterStat) + } else { + this.set('clusterStat', false) + } switch (mode) { case 'cluster':{ // Create cluster layer when needed diff --git a/package.json b/package.json index 5d54d8e..aa2d81a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "start": "parcel ./examples/index.html --dist-dir ./.dist", "build": "parcel build ./examples/index.html --no-source-maps --public-url . --dist-dir ./docs/mcutils", "doc": "jsdoc ./ -c ./doc/jsdoc.json", - "lint": "eslint --fix **/*.js --ignore-path .lintignore || exit 0", + "lint": "eslint --fix \"**/*.js\" --ignore-path .lintignore || exit 0", "lintex": "eslint --fix **/*.js || exit 0" }, "repository": { diff --git a/style/ignStyleFn.js b/style/ignStyleFn.js index 9ca5c3a..744deb3 100644 --- a/style/ignStyleFn.js +++ b/style/ignStyleFn.js @@ -2,8 +2,10 @@ import ol_style_Style from 'ol/style/Style' import ol_style_Text from 'ol/style/Text' import ol_style_Image from 'ol/style/Image' import ol_style_Circle from 'ol/style/Circle' +import ol_style_Chart from 'ol-ext/style/Chart' import ol_style_Stroke from 'ol/style/Stroke' import ol_style_Fill from 'ol/style/Fill' +import chroma from 'chroma-js' import {asArray, asArray as ol_color_asArray} from 'ol/color' import {DEVICE_PIXEL_RATIO as ol_has_DEVICE_PIXEL_RATIO} from 'ol/has' @@ -193,6 +195,11 @@ function ordering(f1, f2) { function getClusterRadius(size) { return Math.max(8, Math.min(size*0.75, 20)); } + +function getChartRadius(size) { + return Math.min(size+15, 40);; +} + /** * Create a ol.style.Image for a cluster * @param {number} options.size the cluster size @@ -231,9 +238,10 @@ function clusterImage(options) { * @param {*} clusterColor * @param {*} clusterDash * @param {*} clusterTextColor + * @param {*} options * @private */ -function getClusterStyle(cluster, optId, clusterColor, clusterDash, clusterTextColor) { +function getClusterStyle(cluster, optId, clusterColor, clusterDash, clusterTextColor, options) { var size = cluster.length; var styleid = 'cluster:'+size+'-'+optId; var style = _cacheStyle[styleid]; @@ -247,6 +255,115 @@ function getClusterStyle(cluster, optId, clusterColor, clusterDash, clusterTextC }), }), }); + style.setZIndex(options.zIndex||0); + } + return [style]; +} + + +/** + * Create a ol.style.Chart for a Statistic cluster + * @param {number} options.size the cluster size + * @param {Object} options.dataColor the cluster colors and data for the chart + * @return {ol.style.Image} the cluster image + * @private + */ +function statisticImage(options) { + options = options || {}; + const colors = options.colors; + const data = options.data; + const radius = getChartRadius(options.size); + return new ol_style_Chart({ + type: 'donut', + radius: radius, + data: data, + colors: colors, + rotateWithView: true, + stroke: new ol_style_Stroke({ + color: "#FFF", + width: 2 + }), + // text: new ol_style_Text({ + // color: 'rgba('+color.join(',')+',1)', + // }), + }); +} + + +/** Get Style for cluster + * @param {Array} cluster + * @param {string} optId cluster Id + * @param {*} clusterColor + * @param {*} clusterDash + * @param {*} clusterTextColor + * @param {*} options + * @private + */ +function getStatisticClusterStyle(f, cluster, optId, clusterColor, clusterDash, clusterTextColor, options) { + const size = cluster.length; + + let dataColor = f.get('dataColor'); + // Get number of element per color + if (!dataColor) { + dataColor = {}; + cluster.forEach(f => { + // Get feature style + st = f.getLayer().getStyle()(f) + switch(f.getGeometry().getType()) { + case 'MultiPoint': + case 'Point': + var color = st[0].getImage()._color + break; + default: + var color = st[0].getStroke().getColor() + break; + } + if (color in dataColor) dataColor[color] ++; + else dataColor[color] = 1; + }) + // Add it to the cluster to prevent recalculation + f.dataColor = dataColor; + } + + // Rename all colors to hex format + for (const color in dataColor) { + // rgba value + if (!color.includes("#")) { + hexColor = chroma(color).hex(); + dataColor[hexColor] = dataColor[color]; + delete dataColor[color] + } + } + + const hexKeys = Object.keys(dataColor).sort() + // Construct the new data and colors + cache key + let cacheKey = ""; + let colors = []; + let data = [] + hexKeys.forEach(key => { + colors.push(key); + data.push(dataColor[key]) + cacheKey += key.toString() + dataColor[key].toString() + }) + + // Construct style id and style + const styleid = 'clusterStat:'+cacheKey+'-'+optId; + let style = _cacheStyle[styleid]; + if (!style) { + style = _cacheStyle[styleid] = new ol_style_Style({ + image: statisticImage({ size: size, colors: colors, data: data, dash: clusterDash }), + text: new ol_style_Text({ + text: size.toString(), + scale: 1.3, + fill: new ol_style_Fill({ + color: "black", + }), + backgroundFill: new ol_style_Fill({ + color: "rgba(255, 255, 255, 0.5)", + }), + }), + }); + style.setZIndex(options.zIndex||0); } return [style]; } @@ -552,7 +669,7 @@ function getStyleFn(options) { clusterTextColor = isDarkColor(clusterColor) ? '#fff' : '#000'; } const clusterDash = options.clusterDash; - const optId = (options.clusterColor||'')+'-'+(options.clusterDash === false ? 0 : 1); + const optId = (options.clusterColor||'')+'-'+(options.clusterDash === false ? 0 : 1)+'-'+(options.clusterStat === false ? 0 : 1); // Clusters style return function(f, res, clustered) { @@ -567,7 +684,18 @@ function getStyleFn(options) { if (cluster.length == 1) { f = cluster[0]; } else { - return getClusterStyle(cluster, optId, clusterColor, clusterDash, clusterTextColor); + // Check if the feature is already a cluster + let clusterStatFeature = false; + if (!options.clusterStat) { + clusterStatFeature = cluster[0].getLayer().get('clusterStat'); + } + // Get style for statistic clusters + if (options.clusterStat || clusterStatFeature) { + return getStatisticClusterStyle(f, cluster, optId, clusterColor, clusterDash, clusterTextColor, options) + } + else { + return getClusterStyle(cluster, optId, clusterColor, clusterDash, clusterTextColor, options); + } } } @@ -630,7 +758,6 @@ function getFeatureStyle(f, clustered, options, ignStyle, clusterColor) { stroke: stroke, geometry: getClusterGeom }); - // console.log("style main") } st.setZIndex(options.zIndex||0); style = [st]; @@ -645,7 +772,6 @@ function getFeatureStyle(f, clustered, options, ignStyle, clusterColor) { // Stroke Arrow if (!(st = _cacheStyle[id.arrow])) { st = _cacheStyle[id.arrow] = getStyleArrow(s); - // console.log("style arrow") } setArrowRotation(st, f) st.setZIndex(options.zIndex||0); @@ -655,7 +781,6 @@ function getFeatureStyle(f, clustered, options, ignStyle, clusterColor) { if (label) { if (!(st = _cacheStyle[id.text])) { st = _cacheStyle[id.text] = getStyleLabel(s /*, typeGeom*/); - // console.log("style text",s) } // Label avec affichage conditionnel si le champ est vide st.getText().setText(label.replace ? f.getLabelContent("(("+label.replace(/\\n/g, '\n')+"))") : f.getLabelContent("(("+label+"))")); @@ -779,25 +904,33 @@ function getSelectStyleFn(options) { if (points) s.push(ptsStyle); return s; } - case 'overlay': { + case 'overlay': { // Feature style s = showObject ? style(f, res) : []; s.push(overlay); if (points) s.push(ptsStyle); return s; } - default: { + default: { // Feature style s = showObject ? style(f, res) : []; // Add var g = f.getGeometry(); if (/Point/.test(g.getType())) { var cluster = f.get('features'); - if (cluster && cluster.length==1) { - f = cluster[0]; - cluster = false; + // Get function radius according to the cluster type + let functionRadius = getClusterRadius; + if (cluster) { + if (cluster.length==1) { + f = cluster[0]; + cluster = false; + } else if (cluster[0].getLayer().get('clusterStat')) { + functionRadius = getChartRadius; + } } - const r = (cluster ? getClusterRadius(cluster.length) : (f.getIgnStyle('pointRadius') || 5)) + radius; + + // Get radius value + const r = (cluster ? functionRadius(cluster.length) : (f.getIgnStyle('pointRadius') || 5)) + radius; s.unshift(new ol_style_Style({ image: new ol_style_Circle({ stroke: strokePoint,