Skip to content

Commit

Permalink
fix: merge icontext symbol from separate symbolizers when needed
Browse files Browse the repository at this point in the history
  • Loading branch information
jansule committed Sep 4, 2023
1 parent 45328ae commit a02a565
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 19 deletions.
25 changes: 25 additions & 0 deletions data/mapbox/icontext_symbolizer.ts
@@ -0,0 +1,25 @@
import { MbStyle } from '../../src/MapboxStyleParser';

const iconTextSymbolizer: Omit<MbStyle, 'sources'> = {
version: 8,
name: 'icontext symbolizer',
sprite: 'https://testurl.com',
layers: [
{
type: 'symbol',
paint: {
'text-color': 'rgba(45, 45, 45, 1)',
},
layout: {
'text-field': '{name}',
'text-size': 12,
'icon-image': 'poi',
visibility: 'visible',
},
id: 'label and icon',
}
]
};


export default iconTextSymbolizer;
38 changes: 38 additions & 0 deletions data/mapbox_metadata/icontext_symbolizer.ts
@@ -0,0 +1,38 @@
import { MbStyle } from '../../src/MapboxStyleParser';

const iconTextSymbolizer: Omit<MbStyle, 'sources'> = {
version: 8,
name: 'icontext symbolizer',
sprite: 'https://testurl.com',
layers: [
{
type: 'symbol',
paint: {
'text-color': 'rgba(45, 45, 45, 1)',
},
layout: {
'text-field': '{name}',
'text-size': 12,
'icon-image': 'poi',
visibility: 'visible',
},
id: 'r0_sy0_st0',
}
],
metadata: {
'geostyler:ref': {
rules: [
{
name: 'label and icon',
symbolizers: [
[
'r0_sy0_st0',
]
],
},
],
},
},
};

export default iconTextSymbolizer;
34 changes: 34 additions & 0 deletions data/styles_metadata/icontext_symbolizer.ts
@@ -0,0 +1,34 @@
import { Style } from 'geostyler-style';

const iconTextSymbolizer: Style = {
name: 'icontext symbolizer',
rules: [
{
name: 'label and icon',
symbolizers: [
{
kind: 'Text',
color: 'rgba(45, 45, 45, 1)',
label: '{{name}}',
size: 12,
visibility: true
},
{
kind: 'Icon',
visibility: true,
image: '/sprites/?name=poi&baseurl=' + encodeURIComponent('https://testurl.com')
}
]
}
],
metadata: {
'mapbox:ref': {
splitSymbolizers: [{
rule: 0,
symbolizers: [0, 1]
}]
}
}
};

export default iconTextSymbolizer;
15 changes: 15 additions & 0 deletions src/MapboxStyleParser.spec.ts
Expand Up @@ -48,6 +48,9 @@ import mb_line_patternline_metadata from '../data/mapbox_metadata/line_patternli
import point_placeholdertext_simple from '../data/styles/point_placeholderText_simple';
import mb_point_placeholdertext_simple from '../data/mapbox/point_placeholderText_simple';
import mb_point_placeholdertext_simple_metadata from '../data/mapbox_metadata/point_placeholderText_simple';
import icontext_symbolizer_metadata from '../data/styles_metadata/icontext_symbolizer';
import mb_icontext_symbolizer from '../data/mapbox/icontext_symbolizer';
import mb_icontext_symbolizer_metadata from '../data/mapbox_metadata/icontext_symbolizer';
import { CustomLayerInterface } from 'mapbox-gl';
import { AnyLayer } from 'mapbox-gl';

Expand Down Expand Up @@ -186,6 +189,12 @@ describe('MapboxStyleParser implements StyleParser', () => {
expect(geoStylerStyle).toBeDefined();
expect(geoStylerStyle).toEqual(icon_simpleicon_mapboxapi);
});

it('can read a mapbox style with icontext symbolizer', async () => {
const { output: geoStylerStyle } = await styleParser.readStyle(mb_icontext_symbolizer);
expect(geoStylerStyle).toBeDefined();
expect(geoStylerStyle).toEqual(icontext_symbolizer_metadata);
});
});

describe('#writeStyle', () => {
Expand Down Expand Up @@ -289,5 +298,11 @@ describe('MapboxStyleParser implements StyleParser', () => {
expect(mbStyle).toBeDefined();
expect(mbStyle).toEqual(mb_icon_simpleicon_mapboxapi_metadata);
});

it('can write a mapbox style with icontext symbolizer', async () => {
const { output: mbStyle } = await styleParser.writeStyle(icontext_symbolizer_metadata);
expect(mbStyle).toBeDefined();
expect(mbStyle).toEqual(mb_icontext_symbolizer_metadata);
});
});
});
88 changes: 70 additions & 18 deletions src/MapboxStyleParser.ts
Expand Up @@ -77,6 +77,13 @@ type GeoStylerRef = {
}[];
};

type MapboxRef = {
splitSymbolizers?: {
rule: number;
symbolizers: number[];
}[];
};

type SymbolType = {
textSymbolizer?: TextSymbolizer;
iconSymbolizer?: IconSymbolizer;
Expand Down Expand Up @@ -628,7 +635,7 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
merged.outlineJoin = lineSymbolizer.join;
merged.outlineWidth = lineSymbolizer.width;
} else {
throw new Error(`Trying to merge to symbolizers of differnt kinds: ${s1.kind}, ${s2.kind}`);
throw new Error(`Trying to merge two symbolizers of different kinds: ${s1.kind}, ${s2.kind}`);
}
}
return merged;
Expand All @@ -641,9 +648,10 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
* @param layer The mapbox Layer
* @return A GeoStyler-Style Rule Array
*/
mapboxLayersToGeoStylerRules(layers: NoneCustomLayer[]): Rule[] {
mapboxLayersToGeoStylerRules(layers: NoneCustomLayer[], mapboxRef: MapboxRef): Rule[] {
const geoStylerRef: GeoStylerRef = this.mbMetadata?.['geostyler:ref'];
const gsRules: Rule[] = [];
const splitSymbolizers: MapboxRef['splitSymbolizers'] = [];

if (geoStylerRef) {
geoStylerRef.rules.forEach((rule, ruleIndex) => {
Expand All @@ -654,13 +662,22 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
rule.symbolizers?.forEach((layerIds, symbolizerIndex) => {
const matchingLayers = layers.filter(layer => layerIds.includes(layer.id));
const flattenedSymbolizers = matchingLayers
.map(layer => this.getSymbolizersFromMapboxLayer(layer))
.map(layer => {
const symbs = this.getSymbolizersFromMapboxLayer(layer);
if (symbs.length > 1) {
splitSymbolizers.push({
rule: ruleIndex,
symbolizers: symbs.map((s, sIdx) => sIdx)
});
}
return symbs;
})
.flat();

symbolizers[symbolizerIndex] = this.mergeSymbolizers(flattenedSymbolizers);

// TODO: check if there are multiple layers with different filters
// and scaledenomintors and throw a warning that we only use the first
// and scaledenominators and throw a warning that we only use the first
// one
if (matchingLayers?.[0]) {
filter = this.getFilterFromMapboxFilter(matchingLayers[0].filter);
Expand All @@ -682,12 +699,11 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
});
} else {
// returns array of rules where one rule contains one symbolizer
layers.forEach(layer => {
layers.forEach((layer, layerIdx) => {
const symbolizers = this.getSymbolizersFromMapboxLayer(layer);
if (symbolizers.length < 1) {
return;
}

const filter = this.getFilterFromMapboxFilter(layer.filter);
const scaleDenominator = this.getScaleDenominatorFromMapboxZoom(
layer.minzoom,
Expand All @@ -699,11 +715,21 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
scaleDenominator,
symbolizers
};
gsRules?.push(rule);
gsRules.push(rule);
if (symbolizers.length > 1) {
splitSymbolizers.push({
rule: gsRules.length - 1,
symbolizers: symbolizers.map((s, sIdx) => sIdx)
});
}
});
}

return gsRules || [];
if (splitSymbolizers.length) {
mapboxRef.splitSymbolizers = splitSymbolizers;
}

return gsRules;
}

/**
Expand All @@ -716,6 +742,7 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
let style: Style = {} as Style;
style.name = mapboxStyle.name || '';
style.rules = [];
const mapboxRef = {};
this.mbMetadata = mapboxStyle.metadata;
if (mapboxStyle.sprite) {
this.spriteBaseUrl = MapboxStyleUtil.getUrlForMbPlaceholder(mapboxStyle.sprite);
Expand All @@ -725,9 +752,14 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
const layers = mapboxStyle.layers.filter(
layer => !(layer.type === 'custom')
) as NoneCustomLayer[];
const rules = this.mapboxLayersToGeoStylerRules(layers);
const rules = this.mapboxLayersToGeoStylerRules(layers, mapboxRef);
style.rules = style.rules.concat(rules);
}

if (Object.keys(mapboxRef).length) {
style.metadata = {
'mapbox:ref': mapboxRef
};
}
return style;
}
Expand Down Expand Up @@ -791,7 +823,8 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
// Mapbox Style version
const version = 8;
const name = geoStylerStyle.name;
const {layers, geoStylerRef} = this.getMapboxLayersFromRules(geoStylerStyle.rules);
const metadata = geoStylerStyle.metadata?.['mapbox:ref'];
const {layers, geoStylerRef} = this.getMapboxLayersFromRules(geoStylerStyle.rules, metadata);
const sprite = MapboxStyleUtil.getMbPlaceholderForUrl(this.spriteBaseUrl);

let mapboxObject = omitBy({
Expand Down Expand Up @@ -819,7 +852,7 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
* @param rules An array of GeoStylerStyle-Rules
* @return An array of Mapbox Layers
*/
getMapboxLayersFromRules(rules: Rule[]): {layers: NoneCustomLayer[]; geoStylerRef: GeoStylerRef}
getMapboxLayersFromRules(rules: Rule[], mapboxRef: MapboxRef): {layers: NoneCustomLayer[]; geoStylerRef: GeoStylerRef}
{
// one layer corresponds to a single symbolizer within a rule
// so filters and scaleDenominators have to be set for each symbolizer explicitly
Expand Down Expand Up @@ -852,11 +885,12 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
}
}

rule.symbolizers.forEach((symbolizer: Symbolizer, symbolizerIndex: number) => {
geoStylerRef.rules[ruleIndex] = {
name: rule.name
};
geoStylerRef.rules[ruleIndex] = {
name: rule.name,
symbolizers: []
};

rule.symbolizers.forEach((symbolizer: Symbolizer, symbolizerIndex: number) => {
// use existing layer properties
let lyr: any = {};
lyr.filter = layer.filter;
Expand All @@ -866,13 +900,24 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>

const styles = this.getStyleFromSymbolizer(symbolizer);

const shouldMergeIntoPrev = mapboxRef?.splitSymbolizers?.some(
s => s.rule === ruleIndex && s.symbolizers.length > 1 && s.symbolizers.slice(1).includes(symbolizerIndex)
);

styles.forEach((style: any, styleIndex: number) => {
const {
type, paint, layout
} = style;

let lyrClone = structuredClone(lyr);

if (shouldMergeIntoPrev) {
lyrClone = layers.at(-1);
lyrClone.paint = {...lyrClone.paint, ...paint};
lyrClone.layout = {...lyrClone.layout, ...layout};
return;
}

lyrClone.type = type;
if (!MapboxStyleUtil.allUndefined(paint)) {
lyrClone.paint = paint;
Expand All @@ -884,10 +929,17 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>

layers.push(omitBy(lyrClone, isUndefined) as NoneCustomLayer);

if (!Array.isArray(geoStylerRef?.rules?.[ruleIndex]?.symbolizers)) {
geoStylerRef.rules[ruleIndex].symbolizers = [[]];
let symbs = geoStylerRef.rules[ruleIndex].symbolizers;
if (!symbs) {
symbs = [];
}
geoStylerRef.rules[ruleIndex]?.symbolizers?.[symbolizerIndex].push(lyrClone.id);

if (!symbs[symbolizerIndex]) {
symbs[symbolizerIndex] = [];
}

symbs[symbolizerIndex].push(lyrClone.id);
geoStylerRef.rules[ruleIndex].symbolizers = symbs;
});
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/Util/MapboxStyleUtil.ts
Expand Up @@ -108,7 +108,7 @@ class MapboxStyleUtil {
// TODO: TextSymbolizer can be removed once it is fixed in the geostyler-style
public static symbolizerAllUndefined(symbolizer: Symbolizer | TextSymbolizer): boolean {
return !Object.keys(symbolizer)
.filter(val => val !== 'kind')
.filter(val => val !== 'kind' && val !== 'visibility')
.some((val: keyof Symbolizer) => typeof symbolizer[val] !== 'undefined');
}

Expand Down

0 comments on commit a02a565

Please sign in to comment.