Skip to content

Commit

Permalink
feat: Add a hack to style cluster layers using SLD
Browse files Browse the repository at this point in the history
HACK is needed to style cluster layers. It wraps existing OL style function in a function which searches for for Text styles and in them for serialized feature arrays and instead sets the len
gth of this array as the label. If the geostyler text symbolizer had {{features}} as the text label template (which returns the "features" attribute of the parent/cluster feature) and returned
 '[object Object], [object Object]' the result would become "2". See geostyler/geostyler-openlayers-parser#227
  • Loading branch information
raitisbe committed May 31, 2021
1 parent 6f43937 commit 4d46d97
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 5 deletions.
4 changes: 3 additions & 1 deletion projects/hslayers/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,9 @@
"addRule": "Add rule",
"toggleFilters": "Toggle filters",
"toggleScaleDenominators": "Toggle scale denominators",
"removeFilter": "Remove filter"
"removeFilter": "Remove filter",
"simpleRule": "Simple rule",
"clusterRule": "Rule for clusters"
},
"TOOLBAR": {
"measureLinesAndPolygon": "Measure lines and polygon"
Expand Down
5 changes: 4 additions & 1 deletion projects/hslayers/src/components/styles/styler.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
</button>
<div ngbDropdownMenu aria-labelledby="dropdownAddRule">
<button ngbDropdownItem (click)="HsStylerService.addRule('Simple')">
Simple rule
{{'STYLER.simpleRule' | translate}}
</button>
<button ngbDropdownItem (click)="HsStylerService.addRule('Cluster')">
{{'STYLER.clusterRule' | translate}}
</button>
</div>
</div>
Expand Down
65 changes: 62 additions & 3 deletions projects/hslayers/src/components/styles/styler.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import {Circle, Fill, Icon, Stroke, Style, Text} from 'ol/style';
import {Style as GeoStylerStyle} from 'geostyler-style';
import {StyleFunction} from 'ol/style';
import {createDefaultStyle} from 'ol/style/Style';

import {HsEventBusService} from '../core/event-bus.service';
Expand Down Expand Up @@ -369,12 +370,38 @@ export class HsStylerService {
return sld;
}

addRule(kind: 'Simple' | 'ByScale' | 'ByFilter' | 'ByFilterAndScale'): void {
addRule(
kind: 'Simple' | 'ByScale' | 'ByFilter' | 'ByFilterAndScale' | 'Cluster'
): void {
switch (kind) {
case 'Cluster':
this.styleObject.rules.push({
name: 'Untitled rule',
filter: [
'&&',
['!=', 'features', 'undefined'],
['!=', 'features', '[object Object]'],
],
symbolizers: [
{kind: 'Mark', color: '#000', wellKnownName: 'circle'},
{
kind: 'Text',
label: '{{features}}',
haloColor: '#fff',
color: '#000',
offset: [0, -10],
},
],
});
break;
case 'Simple':
default:
this.styleObject.rules.push({name: 'Untitled rule', symbolizers: []});
this.styleObject.rules.push({
name: 'Untitled rule',
symbolizers: [],
});
}
this.save();
}

encodeTob64(str: string): string {
Expand All @@ -397,7 +424,10 @@ export class HsStylerService {

async save(): Promise<void> {
try {
const style = await this.geoStylerStyleToOlStyle(this.styleObject);
let style = await this.geoStylerStyleToOlStyle(this.styleObject);
if (this.layer.getSource().getSource) {
style = this.wrapStyleForClusters(style);
}
this.layer.setStyle(style);
const sld = await this.jsonToSld(this.styleObject);
setSld(this.layer, sld);
Expand All @@ -406,6 +436,35 @@ export class HsStylerService {
}
}

/**
* HACK is needed to style cluster layers. It wraps existing OL style function
* in a function which searches for for Text styles and in them for serialized
* feature arrays and instead sets the length of this array as the label.
* If the geostyler text symbolizer had {{features}} as the text label template
* (which returns the "features" attribute of the parent/cluster feature) and returned
* '[object Object], [object Object]' the result would become "2".
* See https://github.com/geostyler/geostyler-openlayers-parser/issues/227
* @param style
* @returns
*/
wrapStyleForClusters(style: StyleFunction): StyleFunction {
return (feature, resolution) => {
const tmp: Style[] = style(feature, resolution);
for (const evaluatedStyle of tmp) {
if (
evaluatedStyle.getText &&
evaluatedStyle.getText()?.getText()?.includes('[object Object]')
) {
const featureListSerialized = evaluatedStyle.getText().getText();
evaluatedStyle
.getText()
.setText(featureListSerialized.split(',').length.toString());
}
}
return tmp;
};
}

/**
* Force repainting of clusters by reapplying cluster style which
* was created in cluster method
Expand Down

0 comments on commit 4d46d97

Please sign in to comment.