Skip to content

Commit 76c7bb7

Browse files
committed
Finalize proof-of-concept for HCAT crops
1 parent 4ebe12f commit 76c7bb7

File tree

3 files changed

+58
-43
lines changed

3 files changed

+58
-43
lines changed

map/crop/CropLegendControl.js

Lines changed: 42 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -40,41 +40,45 @@ export class CropLegendControl extends Control {
4040
});
4141

4242
this.attribute = options.attribute;
43-
this.mapping = options.mapping || {};
43+
this.mapping = options.mapping || [];
4444
this.legendItems = [];
45+
this.level = this.mapping.length - 1;
4546
this.render();
4647
this.tileLoadEnd = this.tileLoadEnd.bind(this);
4748
this.changeSource = this.changeSource.bind(this);
4849
this.updateFeatureCount = debounce(this.updateFeatureCount, 1000).bind(this);
4950
}
5051
updateFeatureCount(e) {
5152
const map = this.getMap();
52-
const vectortiles = map.getLayers().getArray().filter(f => f instanceof VectorTile);
53+
const mapping = this.mapping[this.level];
54+
const vectorTiles = map.getLayers().getArray().filter(f => f instanceof VectorTile);
5355
const extent = map.getView().calculateExtentInternal();
54-
let totalArea = 0, count = 0, cropArea = {}, cropCount = {};
55-
for (const vt of vectortiles) {
56+
let totalArea = 0, count = 0, cropArea = {}, cropCount = {}, byName = {};
57+
for (const vt of vectorTiles) {
5658
forFeaturesInExtent(vt, extent, (feature) => {
5759
const area = feature.properties_["area"] || toGeometry(feature).getArea();
5860
const crop = feature.properties_[this.attribute];
61+
const name = mapping[crop]?.name;
62+
if (!byName[name]) byName[name] = mapping[crop];
5963
count++;
6064
totalArea += area;
61-
cropCount[crop] = (cropCount[crop] || 0) + 1;
62-
cropArea[crop] = (cropArea[crop] || 0) + area;
65+
cropCount[name] = (cropCount[name] || 0) + 1;
66+
cropArea[name] = (cropArea[name] || 0) + area;
6367
});
6468
}
6569
const topCrops = Object.entries(cropArea)
66-
.map(([crop, area]) => ({ crop, area }))
70+
.map(([name, area]) => ({ name, area }))
6771
.sort((a, b) => b.area - a.area) // Descending order
6872
.slice(0, 5);
6973

70-
this.legendItems = topCrops.map(({crop, area}) =>
74+
this.legendItems = topCrops.map(({name, area}) =>
7175
{
72-
const c = this.mapping[crop];
76+
const c = byName[name];
7377
const percent = (area / totalArea) * 100;
7478
return {
7579
label: c.name.replaceAll("_", " "),
7680
color: c.color || "#99bbccaa",
77-
percent: percent.toFixed(2) + "%",
81+
percent: percent >= 1 ? percent.toFixed(0) + "%" : "<1%",
7882
area: area.toFixed(2),
7983
}
8084
}
@@ -84,9 +88,10 @@ export class CropLegendControl extends Control {
8488

8589
tileLoadEnd(e) {
8690
const features = e.tile.getFeatures();
91+
const m = this.mapping.at(-1);
8792
for (const feature of features) {
8893
const p = feature.getProperties();
89-
p.color = this.mapping[p[this.attribute]]?.color || "#99bbccaa";
94+
p.color = m[p[this.attribute]]?.color || "#99bbccaa";
9095
}
9196
this.updateFeatureCount(e)
9297
}
@@ -113,34 +118,32 @@ export class CropLegendControl extends Control {
113118
}
114119
render() {
115120
const element = this.element;
116-
element.innerHTML = ''; // Clear previous content
117-
118-
const legendTitle = document.createElement('div');
119-
legendTitle.className = 'legend-title';
120-
legendTitle.innerText = 'Legend';
121-
element.appendChild(legendTitle);
122-
123-
const list = document.createElement('ul');
124-
list.className = 'legend-list';
125-
126-
this.legendItems.forEach((item) => {
127-
const listItem = document.createElement('li');
128-
const colorBox = document.createElement('span');
129-
const text = document.createElement('span');
130-
131-
colorBox.className = 'legend-color';
132-
colorBox.style.backgroundColor = item.color;
133-
134-
text.className = 'legend-text';
135-
text.innerText = item.label;
136-
137-
listItem.appendChild(colorBox);
138-
listItem.appendChild(text);
139-
140-
list.appendChild(listItem);
141-
});
142-
143-
element.appendChild(list);
121+
if (!this.legendItems?.length) {
122+
element.innerHTML = "";
123+
return;
124+
}
125+
let levels = "";
126+
if (this.mapping.length) {
127+
levels = `<span style="font-weight: normal">Levels: </span>`;
128+
for (let i = 0; i < this.mapping.length; i++) {
129+
levels += `<button class="legend-level${i===this.level?" active":""}">${i}</button>`;
130+
}
131+
}
132+
element.innerHTML = `
133+
<div class="legend-title">Legend ${levels}</div>
134+
${this.legendItems.map(({color, label, percent}) => `
135+
<ul class="legend-list">
136+
<li>
137+
<span class="legend-color" style="background-color: ${color};"></span>
138+
<span class="legend-text">${label} ${percent}</span>
139+
</li>
140+
</ul>
141+
`).join("")}
142+
`;
143+
this.element.querySelectorAll(".legend-level").forEach(e => e.addEventListener("click", (e) => {
144+
this.level = parseInt(e.target.innerText);
145+
this.updateFeatureCount();
146+
}))
144147
}
145148

146149
}

map/crop/crop.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ const fieldStyle = {
1212
"stroke-width": 0.5,
1313
"fill-color": ['get', 'color']
1414
}
15-
const mapping = Object.fromEntries(hcat.map(c => [c.code, c]));
15+
const hcats = hcat.map(c => [c.code, c])
16+
const mp = Object.fromEntries(hcats);
17+
const mapping = [4,6,8,10].map(l => Object.fromEntries(hcat.map(c => [c.code, mp[c.code.slice(0,l).padEnd(c.code.length, "0")]])));
1618

1719
class CropMap extends FiboaMap {
1820
constructor() {
@@ -31,9 +33,6 @@ class CropMap extends FiboaMap {
3133
source: source,
3234
style: this.fieldStyle,
3335
});
34-
// TODO, Count the features in view and display a legend explaining the top 5 crops
35-
// TODO, Add a filter based on crops. This will not be perfect on high zoom levels (lossy vector tiles)
36-
3736
this.map.addLayer(fields);
3837
}
3938
}

map/style.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ form {
116116
margin-bottom: 5px;
117117
}
118118

119+
.ol-crop-legend .legend-level {
120+
display: inline-block;
121+
font-weight: normal;
122+
margin-bottom: 5px;
123+
padding-left: 5px;
124+
padding-right: 5px;
125+
border: 1px black;
126+
}
127+
128+
.ol-crop-legend .legend-level.active {
129+
background-color: lightblue;
130+
}
131+
119132
.ol-crop-legend .legend-list {
120133
list-style: none;
121134
margin: 0;

0 commit comments

Comments
 (0)