diff --git a/package.json b/package.json
index b2995e06..aae95f12 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"homepage": "http://wiki.openstreetmap.org/wiki/UMap",
"dependencies": {
"@tmcw/togeojson": "^5.8.0",
+ "chroma-js": "^2.4.2",
"csv2geojson": "5.1.1",
"dompurify": "^3.0.3",
"georsstogeojson": "^0.1.0",
diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh
index 751132fc..b12bd27b 100755
--- a/scripts/vendorsjs.sh
+++ b/scripts/vendorsjs.sh
@@ -26,5 +26,6 @@ mkdir -p umap/static/umap/vendors/tokml && cp -r node_modules/tokml/tokml.js uma
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/dist/L.Control.Locate.css umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/locatecontrol/ && cp -r node_modules/leaflet.locatecontrol/src/L.Control.Locate.js umap/static/umap/vendors/locatecontrol/
mkdir -p umap/static/umap/vendors/dompurify/ && cp -r node_modules/dompurify/dist/purify.js umap/static/umap/vendors/dompurify/
+mkdir -p umap/static/umap/vendors/chroma/ && cp -r node_modules/chroma-js/chroma.min.js umap/static/umap/vendors/chroma/
echo 'Done!'
diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js
index 7de50be8..7e2428d9 100644
--- a/umap/static/umap/js/umap.features.js
+++ b/umap/static/umap/js/umap.features.js
@@ -283,7 +283,7 @@ L.U.FeatureMixin = {
} else if (L.Util.usableOption(this.properties._umap_options, option)) {
value = this.properties._umap_options[option]
} else if (this.datalayer) {
- value = this.datalayer.getOption(option)
+ value = this.datalayer.getOption(option, this)
} else {
value = this.map.getOption(option)
}
diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js
index 0d983e67..f3f022b2 100644
--- a/umap/static/umap/js/umap.forms.js
+++ b/umap/static/umap/js/umap.forms.js
@@ -379,6 +379,7 @@ L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
['Default', L._('Default')],
['Cluster', L._('Clustered')],
['Heat', L._('Heatmap')],
+ ['Choropleth', L._('Choropleth')],
],
})
diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js
index 21d41b52..16a3d75b 100644
--- a/umap/static/umap/js/umap.layer.js
+++ b/umap/static/umap/js/umap.layer.js
@@ -106,6 +106,94 @@ L.U.Layer.Cluster = L.MarkerClusterGroup.extend({
},
})
+L.U.Layer.Choropleth = L.FeatureGroup.extend({
+ _type: 'Choropleth',
+ includes: [L.U.Layer],
+ canBrowse: true,
+
+ initialize: function (datalayer) {
+ this.datalayer = datalayer
+ if (!L.Util.isObject(this.datalayer.options.choropleth)) {
+ this.datalayer.options.choropleth = {}
+ }
+ L.FeatureGroup.prototype.initialize.call(
+ this,
+ [],
+ this.datalayer.options.choropleth
+ )
+ },
+
+ computeLimits: function () {
+ const values = []
+ this.datalayer.eachLayer((layer) => values.push(layer.properties.density))
+ this.options.limits = chroma.limits(
+ values,
+ this.datalayer.options.choropleth.mode || 'q',
+ this.datalayer.options.choropleth.steps || 5
+ )
+ const color = this.datalayer.getOption('color')
+ this.options.colors = chroma
+ .scale(['white', color])
+ .colors(this.options.limits.length)
+ },
+
+ getColor: function (feature) {
+ if (!feature) return // FIXME shold not happen
+ const featureValue = feature.properties.density
+ // Find the bucket/step/limit that this value is less than and give it that color
+ for (let i = 0; i < this.options.limits.length; i++) {
+ if (featureValue <= this.options.limits[i]) {
+ return this.options.colors[i]
+ }
+ }
+ },
+
+ getOption: function (option, feature) {
+ if (option === 'fillColor' || option === 'color') return this.getColor(feature)
+ },
+
+ addLayer: function (layer) {
+ this.computeLimits()
+ L.FeatureGroup.prototype.addLayer.call(this, layer)
+ },
+
+ removeLayer: function (layer) {
+ this.computeLimits()
+ L.FeatureGroup.prototype.removeLayer.call(this, layer)
+ },
+
+ onAdd: function (map) {
+ this.computeLimits()
+ L.FeatureGroup.prototype.onAdd.call(this, map)
+ },
+
+ getEditableOptions: function () {
+ return [
+ [
+ 'options.choropleth.steps',
+ {
+ handler: 'IntInput',
+ placeholder: L._('Choropleth steps'),
+ helpText: L._('Choropleth steps (default 5)'),
+ },
+ ],
+ [
+ 'options.choropleth.mode',
+ {
+ handler: 'Select',
+ selectOptions: [
+ ['q', L._('quantile')],
+ ['e', L._('equidistant')],
+ ['l', L._('logarithmic')],
+ ['k', L._('k-mean')],
+ ],
+ helpText: L._('Choropleth mode'),
+ },
+ ],
+ ]
+ },
+})
+
L.U.Layer.Heat = L.HeatLayer.extend({
_type: 'Heat',
includes: [L.U.Layer],
@@ -897,8 +985,6 @@ L.U.DataLayer = L.Evented.extend({
'options.fillOpacity',
]
- shapeOptions = shapeOptions.concat(this.layer.getEditableOptions())
-
const redrawCallback = function (field) {
this.hide()
this.layer.postUpdate(field)
@@ -1050,7 +1136,11 @@ L.U.DataLayer = L.Evented.extend({
this.map.ui.openPanel({ data: { html: container }, className: 'dark' })
},
- getOption: function (option) {
+ getOption: function (option, feature) {
+ if (this.layer && this.layer.getOption) {
+ const value = this.layer.getOption(option, feature)
+ if (value) return value
+ }
if (L.Util.usableOption(this.options, option)) return this.options[option]
else return this.map.getOption(option)
},
diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html
index e2da8669..609eff2d 100644
--- a/umap/templates/umap/js.html
+++ b/umap/templates/umap/js.html
@@ -24,6 +24,7 @@
+
{% endcompress %}
{% if locale %}{% endif %}
{% compress js %}