11import Control from 'ol/control/Control' ;
2+ import VectorTile from 'ol/layer/VectorTile' ;
3+ import { toGeometry } from "ol/render/Feature" ;
4+ import TileState from "ol/TileState" ;
5+ import { intersects } from "ol/extent" ;
6+ import debounce from "lodash.debounce" ;
7+
8+ function forFeaturesInExtent ( vt , extent , callback ) {
9+ const renderer = vt . getRenderer ( ) ;
10+ const tileCache = renderer . getTileCache ( ) ;
11+ if ( tileCache . getCount ( ) === 0 ) return ;
12+
13+ const tileGrid = renderer . getLayer ( ) . getSource ( ) . tileGrid ;
14+ const z = tileGrid . getZForResolution ( renderer . renderedResolution ) ;
15+ tileCache . forEach ( ( tile ) => {
16+ if ( tile . tileCoord [ 0 ] !== z || tile . getState ( ) !== TileState . LOADED ) return ;
17+ for ( const sourceTile of tile . getSourceTiles ( ) ) {
18+ const tileCoord = sourceTile . tileCoord ;
19+ if ( intersects ( extent , tileGrid . getTileCoordExtent ( tileCoord ) ) ) {
20+ for ( const candidate of sourceTile . getFeatures ( ) ) {
21+ if ( intersects ( extent , candidate . getExtent ( ) ) ) callback ( candidate ) ;
22+ }
23+ }
24+ }
25+ } ) ;
26+ }
227
328/**
429 * A custom Legend Control for OpenLayers
@@ -14,13 +39,77 @@ export class CropLegendControl extends Control {
1439 target : options . target ,
1540 } ) ;
1641
42+ this . attribute = options . attribute ;
43+ this . mapping = options . mapping || { } ;
1744 this . legendItems = [ ] ;
1845 this . render ( ) ;
46+ this . tileLoadEnd = this . tileLoadEnd . bind ( this ) ;
47+ this . changeSource = this . changeSource . bind ( this ) ;
48+ this . updateFeatureCount = debounce ( this . updateFeatureCount , 1000 ) . bind ( this ) ;
49+ }
50+ updateFeatureCount ( e ) {
51+ const map = this . getMap ( ) ;
52+ const vectortiles = map . getLayers ( ) . getArray ( ) . filter ( f => f instanceof VectorTile ) ;
53+ const extent = map . getView ( ) . calculateExtentInternal ( ) ;
54+ let totalArea = 0 , count = 0 , cropArea = { } , cropCount = { } ;
55+ for ( const vt of vectortiles ) {
56+ forFeaturesInExtent ( vt , extent , ( feature ) => {
57+ const area = feature . properties_ [ "area" ] || toGeometry ( feature ) . getArea ( ) ;
58+ const crop = feature . properties_ [ this . attribute ] ;
59+ count ++ ;
60+ totalArea += area ;
61+ cropCount [ crop ] = ( cropCount [ crop ] || 0 ) + 1 ;
62+ cropArea [ crop ] = ( cropArea [ crop ] || 0 ) + area ;
63+ } ) ;
64+ }
65+ const topCrops = Object . entries ( cropArea )
66+ . map ( ( [ crop , area ] ) => ( { crop, area } ) )
67+ . sort ( ( a , b ) => b . area - a . area ) // Descending order
68+ . slice ( 0 , 5 ) ;
69+
70+ this . legendItems = topCrops . map ( ( { crop, area} ) =>
71+ {
72+ const c = this . mapping [ crop ] ;
73+ const percent = ( area / totalArea ) * 100 ;
74+ return {
75+ label : c . name . replaceAll ( "_" , " " ) ,
76+ color : c . color || "#99bbccaa" ,
77+ percent : percent . toFixed ( 2 ) + "%" ,
78+ area : area . toFixed ( 2 ) ,
79+ }
80+ }
81+ )
82+ this . render ( ) ;
83+ }
84+
85+ tileLoadEnd ( e ) {
86+ const features = e . tile . getFeatures ( ) ;
87+ for ( const feature of features ) {
88+ const p = feature . getProperties ( ) ;
89+ p . color = this . mapping [ p [ this . attribute ] ] ?. color || "#99bbccaa" ;
90+ }
91+ this . updateFeatureCount ( e )
92+ }
93+
94+ changeSource ( e ) {
95+ if ( ! ( e . element instanceof VectorTile ) ) return ;
96+ if ( e . type === "add" ) {
97+ e . element . getSource ( ) . on ( 'tileloadend' , this . tileLoadEnd ) ;
98+ } else {
99+ e . element . getSource ( ) . un ( 'tileloadend' , this . tileLoadEnd ) ;
100+ }
19101 }
20102
21103 setMap ( map ) {
22104 super . setMap ( map ) ;
23-
105+ const layers = map . getLayers ( ) ;
106+ if ( map ) {
107+ map . on ( "moveend" , this . updateFeatureCount ) ;
108+ layers . on ( [ "add" , "remove" ] , this . changeSource )
109+ } else {
110+ map . un ( "moveend" , this . updateFeatureCount ) ;
111+ layers . un ( [ "add" , "remove" ] , this . changeSource )
112+ }
24113 }
25114 render ( ) {
26115 const element = this . element ;
0 commit comments