Skip to content

Commit 3936d85

Browse files
committed
Add CropLegendControl, update counts after tile loading and map panning
1 parent c14470d commit 3936d85

5 files changed

Lines changed: 115 additions & 8 deletions

File tree

map/crop/CropLegendControl.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Control from 'ol/control/Control';
2+
3+
/**
4+
* A custom Legend Control for OpenLayers
5+
* Displays a legend on the map with custom items
6+
*/
7+
export class CropLegendControl extends Control {
8+
constructor(options = {}) {
9+
const element = document.createElement('div');
10+
element.className = 'ol-crop-legend ol-unselectable ol-control';
11+
12+
super({
13+
element: element,
14+
target: options.target,
15+
});
16+
17+
this.legendItems = [];
18+
this.render();
19+
}
20+
21+
setMap(map) {
22+
super.setMap(map);
23+
24+
}
25+
render() {
26+
const element = this.element;
27+
element.innerHTML = ''; // Clear previous content
28+
29+
const legendTitle = document.createElement('div');
30+
legendTitle.className = 'legend-title';
31+
legendTitle.innerText = 'Legend';
32+
element.appendChild(legendTitle);
33+
34+
const list = document.createElement('ul');
35+
list.className = 'legend-list';
36+
37+
this.legendItems.forEach((item) => {
38+
const listItem = document.createElement('li');
39+
const colorBox = document.createElement('span');
40+
const text = document.createElement('span');
41+
42+
colorBox.className = 'legend-color';
43+
colorBox.style.backgroundColor = item.color;
44+
45+
text.className = 'legend-text';
46+
text.innerText = item.label;
47+
48+
listItem.appendChild(colorBox);
49+
listItem.appendChild(text);
50+
51+
list.appendChild(listItem);
52+
});
53+
54+
element.appendChild(list);
55+
}
56+
57+
}

map/crop/crop.js

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,67 @@ import {hcat} from "./codes";
44
import VectorTile from "ol/layer/VectorTile";
55
import {PMTilesVectorSource} from "ol-pmtiles";
66
const cropExtension = "https://fiboa.github.io/hcat-extension/v0.1.0/schema.yaml";
7+
import debounce from "lodash.debounce";
8+
import TileState from 'ol/TileState';
9+
import {intersects} from "ol/extent";
10+
import {toGeometry} from "ol/render/Feature";
11+
import {CropLegendControl} from "./CropLegendControl";
712

13+
const CROP_ATTRIBUTE = "ec:hcat_code";
814
const fieldStyle = {
915
"stroke-color": 'rgb(88, 88, 88, 88)',
1016
"stroke-width": 0.5,
1117
"fill-color": ['get', 'color']
1218
}
1319
const mapping = Object.fromEntries(hcat.map(c => [c.code, c.color]));
1420

21+
function forFeaturesInExtent(vt, extent, callback) {
22+
const renderer = vt.getRenderer();
23+
const tileCache = renderer.getTileCache();
24+
if (tileCache.getCount() === 0) return;
25+
26+
const tileGrid = renderer.getLayer().getSource().tileGrid;
27+
const z = tileGrid.getZForResolution(renderer.renderedResolution);
28+
tileCache.forEach((tile) => {
29+
if (tile.tileCoord[0] !== z || tile.getState() !== TileState.LOADED) return;
30+
for (const sourceTile of tile.getSourceTiles()) {
31+
const tileCoord = sourceTile.tileCoord;
32+
if (intersects(extent, tileGrid.getTileCoordExtent(tileCoord))) {
33+
for (const candidate of sourceTile.getFeatures()) {
34+
if (intersects(extent, candidate.getExtent())) callback(candidate);
35+
}
36+
}
37+
}
38+
});
39+
}
40+
1541
class CropMap extends FiboaMap {
1642
constructor() {
1743
super();
1844
this.fieldStyle = fieldStyle;
45+
this.vectortiles = [];
46+
this.updateFeatureCount = debounce(this.updateFeatureCount, 1000).bind(this);
47+
this.map.on("moveend", this.updateFeatureCount)
48+
this.map.addControl(new CropLegendControl());
49+
}
50+
updateFeatureCount(e) {
51+
// const extent = this.map.getView().calculateExtent();
52+
const extent = this.map.getView().calculateExtentInternal();
53+
const result = {
54+
area: 0,
55+
count: 0,
56+
perCrop: {}
57+
}
58+
for (const vt of this.vectortiles) {
59+
forFeaturesInExtent(vt, extent, (feature) => {
60+
const area = feature.properties_["area"] || toGeometry(feature).getArea();
61+
const crop = feature.properties_[CROP_ATTRIBUTE];
62+
result.count++;
63+
result.area += area;
64+
result[crop] = (result[crop] || 0) + area;
65+
});
66+
}
67+
console.log(extent, result);
1968
}
2069
addPMTilesLayer(c, options) {
2170
const source = new PMTilesVectorSource(options);
@@ -29,18 +78,12 @@ class CropMap extends FiboaMap {
2978
});
3079
// TODO, Count the features in view and display a legend explaining the top 5 crops
3180
// TODO, Add a filter based on crops. This will not be perfect on high zoom levels (lossy vector tiles)
32-
// source.on('tileloadend', e => {
33-
// const features = e.tile.getFeatures();
34-
// console.log(e, features.length, features[0].getProperties());
35-
// for (const feature of features) {
36-
// feature.getProperties().color = "##ff0000";
37-
// }
38-
// })
81+
this.vectortiles.push(fields);
3982
source.on('tileloadend', e => {
4083
const features = e.tile.getFeatures();
4184
for (const feature of features) {
4285
const p = feature.getProperties();
43-
p.color = mapping[p["ec:hcat_code"]] || "#99bbccaa";
86+
p.color = mapping[p[CROP_ATTRIBUTE]] || "#99bbccaa";
4487
}
4588
this.updateFeatureCount(e)
4689
})

map/package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

map/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"vite": "^6.2.4"
1212
},
1313
"dependencies": {
14+
"lodash.debounce": "^4.0.8",
1415
"ol": "^10.0.0",
1516
"ol-ext": "^4.0.22",
1617
"ol-pmtiles": "^2.0.2"

map/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,9 @@ form {
9797
.ol-popup ul li {
9898
list-style: none;
9999
margin: 0 0 0.25rem;
100+
}
101+
102+
.ol-crop-legend {
103+
right: .5em;
104+
bottom: .5em;
100105
}

0 commit comments

Comments
 (0)