Skip to content

Commit

Permalink
feat: read rgba color values into hex (#323)
Browse files Browse the repository at this point in the history
  • Loading branch information
jansule authored Jun 20, 2024
1 parent b02d9a5 commit e47192d
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 16 deletions.
30 changes: 30 additions & 0 deletions data/mapbox/color_rgba.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { MbStyle } from '../../src/MapboxStyleParser';

const circleSimpleCircle: MbStyle = {
version: 8,
name: 'Simple Circle',
sources: {
testsource: {
type: 'vector'
}
},
layers: [
{
id: 'Simple Circle',
source: 'testsource',
'source-layer': 'foo',
type: 'circle',
paint: {
'circle-color': 'rgba(0, 0, 0, 1)',
'circle-stroke-color': [
'case',
['<', ['get', 'mag'], 2],
'rgba(255, 0, 0, 1)',
'rgba(0, 255, 0, 1)'
]
}
}
]
};

export default circleSimpleCircle;
2 changes: 1 addition & 1 deletion data/mapbox_metadata/icontext_symbolizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const iconTextSymbolizer: MbStyle = {
source: 'testsource',
'source-layer': 'foo',
paint: {
'text-color': 'rgba(45, 45, 45, 1)',
'text-color': '#2d2d2d',
},
layout: {
'text-field': '{name}',
Expand Down
45 changes: 45 additions & 0 deletions data/styles/color_rgba.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Style } from 'geostyler-style';

const circleSimpleCircle: Style = {
name: 'Simple Circle',
rules: [{
name: 'Simple Circle',
symbolizers: [{
kind: 'Mark',
wellKnownName: 'circle',
color: '#000000',
strokeColor: {
name: 'case',
args: [{
case: {
name: 'lessThan',
args: [{
name: 'property',
args: ['mag']
}, 2]
},
value: '#ff0000'
},
'#00ff00'
]
}
}]
}],
metadata: {
'mapbox:ref': {
sources: {
testsource: {
type: 'vector'
}
},
sourceMapping: {
testsource: [0]
},
sourceLayerMapping: {
foo: [0]
}
}
}
};

export default circleSimpleCircle;
2 changes: 1 addition & 1 deletion data/styles_metadata/icontext_symbolizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const iconTextSymbolizer: Style = {
symbolizers: [
{
kind: 'Text',
color: 'rgba(45, 45, 45, 1)',
color: '#2d2d2d',
label: '{{name}}',
size: 12,
visibility: true
Expand Down
15 changes: 10 additions & 5 deletions src/Expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
StyleFunction
} from 'mapbox-gl';

import MapboxStyleUtil from './Util/MapboxStyleUtil';

const expressionNames: ExpressionName[] = ['array',
'boolean',
'collator',
Expand Down Expand Up @@ -216,12 +218,15 @@ export function gs2mbExpression<T extends PropertyType>(gsExpression?: GeoStyler

type MbInput = MapboxExpression | PropertyType | StyleFunction;

export function mb2gsExpression<T extends PropertyType>(mbExpression?: MbInput):
export function mb2gsExpression<T extends PropertyType>(mbExpression?: MbInput, isColor?: boolean):
GeoStylerExpression<T> | undefined {

// TODO: is this check valid ?
// fails for arrays like offset = [10, 20]
if (!(Array.isArray(mbExpression) && expressionNames.includes(mbExpression[0]))) {
if (typeof mbExpression === 'string' && isColor) {
return MapboxStyleUtil.getHexColor(mbExpression) as GeoStylerExpression<T>;
}
return mbExpression as GeoStylerExpression<T> | undefined;
}

Expand All @@ -240,7 +245,7 @@ export function mb2gsExpression<T extends PropertyType>(mbExpression?: MbInput):
case 'case':
const gsArgs: any[] = [];
args.forEach((a, index) => {
if (index < (args.length - 1)) {
if (index < (args.length - 1)) {
var gsIndex = index < 2 ? 0 : Math.floor(index / 2);
if (!gsArgs[gsIndex]) {
gsArgs[gsIndex] = {};
Expand All @@ -252,12 +257,12 @@ export function mb2gsExpression<T extends PropertyType>(mbExpression?: MbInput):
} else {
gsArgs[gsIndex] = {
...gsArgs[gsIndex] as any,
value: mb2gsExpression(a)
value: mb2gsExpression(a, isColor)
};
}
}
});
gsArgs.push(mbExpression.at(- 1));
gsArgs.push(mb2gsExpression(mbExpression.at(-1), isColor));
func = {
name: 'case',
args: gsArgs as Fcase['args']
Expand All @@ -275,7 +280,7 @@ export function mb2gsExpression<T extends PropertyType>(mbExpression?: MbInput):
}
func = {
name: gsFunctionName,
args: args.map(mb2gsExpression)
args: args.map(arg => mb2gsExpression(arg))
} as GeoStylerFunction;
break;
}
Expand Down
8 changes: 8 additions & 0 deletions src/MapboxStyleParser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import mb_text_placement_line_metadata from '../data/mapbox_metadata/text_placem
import mb_text_placement_point from '../data/mapbox/text_placement_point';
import text_placement_point from '../data/styles/text_placement_point';
import mb_text_placement_point_metadata from '../data/mapbox_metadata/text_placement_point';
import color_rgba from '../data/styles/color_rgba';
import mb_color_rgba from '../data/mapbox/color_rgba';

const mockFetchResult = (data: any) => {
jest.spyOn(global, 'fetch')
Expand Down Expand Up @@ -287,6 +289,12 @@ describe('MapboxStyleParser implements StyleParser', () => {
expect(geostylerStyle).toBeDefined();
expect(geostylerStyle).toEqual(source_layer_mapping);
});

it('can read a style with rgba color values', async () => {
const { output: geostylerStyle } = await styleParser.readStyle(mb_color_rgba);
expect(geostylerStyle).toBeDefined();
expect(geostylerStyle).toEqual(color_rgba);
});
});

describe('#writeStyle', () => {
Expand Down
18 changes: 9 additions & 9 deletions src/MapboxStyleParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,15 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
const symbolizer: MarkSymbolizer = {
kind: 'Mark',
blur: mb2gsExpression<number>(paint?.['circle-blur']),
color: mb2gsExpression<string>(paint?.['circle-color']),
color: mb2gsExpression<string>(paint?.['circle-color'], true),
// TODO: handle array values
offset: paint?.['circle-translate'] as MarkSymbolizer['offset'],
offsetAnchor: paint?.['circle-translate-anchor'],
fillOpacity: mb2gsExpression<number>(paint?.['circle-opacity']),
pitchAlignment: paint?.['circle-pitch-alignment'],
pitchScale: paint?.['circle-pitch-scale'],
radius: mb2gsExpression<number>(paint?.['circle-radius']),
strokeColor: mb2gsExpression<string>(paint?.['circle-stroke-color']),
strokeColor: mb2gsExpression<string>(paint?.['circle-stroke-color'], true),
strokeOpacity: mb2gsExpression<number>(paint?.['circle-stroke-opacity']),
strokeWidth: mb2gsExpression<number>(paint?.['circle-stroke-width']),
visibility: layout?.visibility && layout?.visibility !== 'none',
Expand Down Expand Up @@ -409,9 +409,9 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
allowOverlap: mb2gsExpression<boolean>(layout?.['icon-allow-overlap']),
anchor: layout?.['icon-anchor'] as IconSymbolizer['anchor'],
avoidEdges: layout?.['symbol-avoid-edges'],
color: mb2gsExpression<string>(paint?.['icon-color']),
color: mb2gsExpression<string>(paint?.['icon-color'], true),
haloBlur: mb2gsExpression<number>(paint?.['icon-halo-blur']),
haloColor: mb2gsExpression<string>(paint?.['icon-halo-color']),
haloColor: mb2gsExpression<string>(paint?.['icon-halo-color'], true),
haloWidth: mb2gsExpression<number>(paint?.['icon-halo-width']),
image,
keepUpright: mb2gsExpression<boolean>(layout?.['icon-keep-upright']),
Expand Down Expand Up @@ -476,11 +476,11 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
// TODO: handle enum values
anchor: layout?.['text-anchor'] as TextSymbolizer['anchor'],
avoidEdges: mb2gsExpression<boolean>(layout?.['symbol-avoid-edges']),
color: mb2gsExpression<string>(paint?.['text-color']),
color: mb2gsExpression<string>(paint?.['text-color'], true),
// TODO: handle array values
font: layout?.['text-font'],
haloBlur: mb2gsExpression<number>(paint?.['text-halo-blur']),
haloColor: mb2gsExpression<string>(paint?.['text-halo-color']),
haloColor: mb2gsExpression<string>(paint?.['text-halo-color'], true),
haloWidth: mb2gsExpression<number>(paint?.['text-halo-width']),
// TODO: handle enum values
justify: layout?.['text-justify'] as TextSymbolizer['justify'],
Expand Down Expand Up @@ -530,8 +530,8 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
visibility: layout?.visibility && layout?.visibility !== 'none',
antialias: mb2gsExpression<boolean>(paint?.['fill-antialias']),
opacity: mb2gsExpression<number>(paint?.['fill-opacity']),
color: mb2gsExpression<string>(paint?.['fill-color']),
outlineColor: mb2gsExpression<string>(paint?.['fill-outline-color']),
color: mb2gsExpression<string>(paint?.['fill-color'], true),
outlineColor: mb2gsExpression<string>(paint?.['fill-outline-color'], true),
graphicFill
};
return omitBy(fillSymbolizer, isUndefined) as FillSymbolizer;
Expand Down Expand Up @@ -566,7 +566,7 @@ export class MapboxStyleParser implements StyleParser<Omit<MbStyle, 'sources'>>
miterLimit: mb2gsExpression<number>(layout?.['line-miter-limit']),
roundLimit: mb2gsExpression<number>(layout?.['line-round-limit']),
opacity: mb2gsExpression<number>(paint?.['line-opacity']),
color: mb2gsExpression<string>(paint?.['line-color']),
color: mb2gsExpression<string>(paint?.['line-color'], true),
width: mb2gsExpression<number>(paint?.['line-width']),
gapWidth: mb2gsExpression<number>(paint?.['line-gap-width']),
perpendicularOffset: mb2gsExpression<number>(paint?.['line-offset']),
Expand Down
53 changes: 53 additions & 0 deletions src/Util/MapboxStyleUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,59 @@ class MapboxStyleUtil {
}
return name || '';
}

/**
* Splits a RGBA encoded color into its color values.
*
* @param {string} rgbaColor RGB(A) encoded color
* @return {number[]} Numeric color values as array
*/
public static splitRgbaColor(rgbaColor: string): number[] {
const colorsOnly = rgbaColor.substring(rgbaColor.indexOf('(') + 1, rgbaColor.lastIndexOf(')')).split(/,\s*/);
const red = parseInt(colorsOnly[0], 10);
const green = parseInt(colorsOnly[1], 10);
const blue = parseInt(colorsOnly[2], 10);
const opacity = parseFloat(colorsOnly[3]);

return [red, green, blue, opacity];
}

/**
* Returns the hex code for a given RGB(A) array.
*
* @param colorArr RGB(A) array. e.g. [255,0,0]
* @return {string} The HEX color representation of the given color
*/
public static getHexCodeFromRgbArray(colorArr: number[]): string {
return '#' + colorArr.map((x, idx) => {
const hex = x.toString(16);
// skip opacity if passed as fourth entry
if (idx < 3) {
return hex.length === 1 ? '0' + hex : hex;
}
return '';
}).join('');
}

/**
* Transforms a RGB(A) or named color value to a HEX encoded notation.
* If a HEX color is provided it will be returned untransformed.
*
* @param {string} inColor The color to transform
* @return {string | undefined} The HEX color representation of the given color
*/
public static getHexColor(inColor: string): string | undefined {
// if passing in a hex code we just return it
if (inColor.startsWith('#')) {
return inColor;
} else if (inColor.startsWith('rgb')) {
const colorArr = this.splitRgbaColor(inColor);
return this.getHexCodeFromRgbArray(colorArr);
} else {
return;
}
}

}

export default MapboxStyleUtil;

0 comments on commit e47192d

Please sign in to comment.