Skip to content

Commit

Permalink
feat: add vendor-option graphic-margin (#971)
Browse files Browse the repository at this point in the history
  • Loading branch information
ger-benjamin authored Jan 13, 2025
1 parent 0093c6b commit be950ea
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 35 deletions.
37 changes: 37 additions & 0 deletions data/slds/geoserver/pattern_polygon.sld
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<StyledLayerDescriptor version="1.0.0" xmlns="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">
<NamedLayer>

<Name>Pattern polygon</Name>

<UserStyle>
<Name>pattern_polygon</Name>
<Title>Pattern polygon</Title>
<Abstract>Polygon with spaced purple circle symbols</Abstract>
<FeatureTypeStyle>
<Rule>
<Name>Polygon with spaced purple circle symbols</Name>
<Abstract>Polygon with spaced purple circle symbols</Abstract>
<PolygonSymbolizer>
<VendorOption name="graphic-margin">4,6,2,3</VendorOption>
<Fill>
<GraphicFill>
<Graphic>
<Mark>
<WellKnownName>circle</WellKnownName>
<Fill>
<CssParameter name="fill">#880088</CssParameter>
</Fill>
</Mark>
<Size>6</Size>
</Graphic>
</GraphicFill>
</Fill>
</PolygonSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</NamedLayer>
</StyledLayerDescriptor>
1 change: 0 additions & 1 deletion data/styles/geoserver/default_polygon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ const style: Style = {
]
};


export default style;
24 changes: 24 additions & 0 deletions data/styles/geoserver/pattern_polygon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Style } from 'geostyler-style';

const style: Style = {
name: 'pattern_polygon',
rules: [
{
name: 'Polygon with spaced purple circle symbols',
symbolizers: [
{
kind: 'Fill',
graphicFill: {
kind: 'Mark',
wellKnownName: 'circle',
color: '#880088',
radius: 3
},
graphicFillPadding: [4, 6, 2, 3],
}
]
}
]
};

export default style;
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"dependencies": {
"fast-xml-parser": "^4.4.1",
"geostyler-style": "^9.1.0",
"geostyler-style": "^9.2.0",
"lodash": "^4.17.21"
},
"devDependencies": {
Expand Down
22 changes: 20 additions & 2 deletions src/SldStyleParser.geoserver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import restricted from '../data/styles/geoserver/restricted';
import simple_streams from '../data/styles/geoserver/simple_streams';
import simpleRoads from '../data/styles/geoserver/simpleRoads';
import tiger_roads from '../data/styles/geoserver/tiger_roads';
import pattern_polygon from '../data/styles/geoserver/pattern_polygon';

it('SldStyleParser is defined', () => {
expect(SldStyleParser).toBeDefined();
Expand All @@ -36,7 +37,7 @@ describe('SldStyleParser implements StyleParser', () => {
let styleParser: SldStyleParser;

beforeEach(() => {
styleParser = new SldStyleParser({sldVersion: '1.0.0'});
styleParser = new SldStyleParser({sldVersion: '1.0.0', withGeoServerVendorOption: true});
});

describe('#readStyle', () => {
Expand Down Expand Up @@ -178,6 +179,12 @@ describe('SldStyleParser implements StyleParser', () => {
expect(geoStylerStyle).toBeDefined();
expect(geoStylerStyle).toEqual(tiger_roads);
});
it('can read the geoserver pattern_polygon.sld', async () => {
const sld = fs.readFileSync('./data/slds/geoserver/pattern_polygon.sld', 'utf8');
const { output: geoStylerStyle } = await styleParser.readStyle(sld);
expect(geoStylerStyle).toBeDefined();
expect(geoStylerStyle).toEqual(pattern_polygon);
});
});

describe('#writeStyle', () => {
Expand Down Expand Up @@ -536,7 +543,18 @@ describe('SldStyleParser implements StyleParser', () => {
const { output: readStyle} = await styleParser.readStyle(sldString!);
expect(readStyle).toEqual(tiger_roads);
});

it('can write the geoserver pattern_polygon.sld', async () => {
const {
output: sldString,
errors
} = await styleParser.writeStyle(pattern_polygon);
expect(sldString).toBeDefined();
expect(errors).toBeUndefined();
// As string comparison between two XML-Strings is awkward and nonsens
// we read it again and compare the json input with the parser output
const { output: readStyle} = await styleParser.readStyle(sldString!);
expect(readStyle).toEqual(pattern_polygon);
});
});

});
71 changes: 65 additions & 6 deletions src/SldStyleParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
getAttribute,
getChildren,
getParameterValue,
getVendorOptionValue,
isSymbolizer,
keysByValue,
numberExpression
Expand Down Expand Up @@ -80,6 +81,7 @@ export type ConstructorParams = {
boolFilterFields?: string[];
/* optional for reading style (it will be guessed from sld style) and mandatory for writing */
sldVersion?: SldVersion;
withGeoServerVendorOption?: boolean;
symbolizerUnits?: string;
parserOptions?: ParserOptions;
builderOptions?: XmlBuilderOptions;
Expand Down Expand Up @@ -275,6 +277,7 @@ export class SldStyleParser implements StyleParser<string> {
preserveOrder: true,
trimValues: true
});

this.builder = new XMLBuilder({
...opts?.builderOptions,
// Fixed attributes
Expand All @@ -283,10 +286,15 @@ export class SldStyleParser implements StyleParser<string> {
suppressEmptyNode: true,
preserveOrder: true
});

if (opts?.sldVersion) {
this.sldVersion = opts?.sldVersion;
}

if (opts?.withGeoServerVendorOption !== undefined) {
this.withGeoServerVendorOption = opts.withGeoServerVendorOption;
}

if (opts?.locale) {
this.locale = opts.locale;
}
Expand Down Expand Up @@ -386,6 +394,26 @@ export class SldStyleParser implements StyleParser<string> {
this._sldVersion = sldVersion;
}

/**
* Indicates whether additional GeoServer vendorOption should be included in
* sld write/parse operations. Set to `false` by default.
*/
private _withGeoServerVendorOption = false;

/**
* Getter for _withGeoServerVendorOption
*/
get withGeoServerVendorOption(): boolean {
return this._withGeoServerVendorOption;
}

/**
* Setter for _withGeoServerVendorOption
*/
set withGeoServerVendorOption(withVendorOption: boolean) {
this._withGeoServerVendorOption = withVendorOption;
}


/**
* String indicating the SLD version used in reading mode
Expand Down Expand Up @@ -959,6 +987,12 @@ export class SldStyleParser implements StyleParser<string> {
graphicFill
);
}
if (this.withGeoServerVendorOption) {
const graphicFillPadding = getVendorOptionValue(sldSymbolizer, 'graphic-margin');
if (!isNil(graphicFillPadding)) {
fillSymbolizer.graphicFillPadding = graphicFillPadding.split(',').map(numberExpression);
}
}
if (!isNil(color)) {
fillSymbolizer.color = color;
}
Expand Down Expand Up @@ -1865,6 +1899,30 @@ export class SldStyleParser implements StyleParser<string> {
}];
}

/**
* Push a new GeoServerVendorOption in the given array if such options are allowed.
*/
pushGeoServerVendorOption(elementArray: any[], name: string, text: string) {
if (this.withGeoServerVendorOption) {
elementArray.push(this.createGeoServerVendorOption(name, text));
}
}

/**
* @returns <VendorOption name="name">text</VendorOption>
*/
createGeoServerVendorOption(name: string, text: string) {
const VendorOption = this.getTagName('VendorOption');
return {
[VendorOption]: [{
'#text': text,
}],
':@': {
'@_name': name,
}
};
}

/**
* Get the SLD Object (readable with fast-xml-parser) from a geostyler-style IconSymbolizer.
*
Expand Down Expand Up @@ -2454,16 +2512,17 @@ export class SldStyleParser implements StyleParser<string> {

const polygonSymbolizer: any = [];
if (fillCssParameters.length > 0 || graphicFill) {
if (!Array.isArray(polygonSymbolizer?.[0]?.[Fill])) {
polygonSymbolizer[0] = { [Fill]: [] };
const fillArray: any[] = [];
const graphicFillPadding = fillSymbolizer.graphicFillPadding;
if (graphicFillPadding) {
this.pushGeoServerVendorOption(polygonSymbolizer, 'graphic-margin', `${graphicFillPadding}`);
}
polygonSymbolizer.push({ [Fill]: fillArray });
if (fillCssParameters.length > 0) {
polygonSymbolizer[0][Fill].push(...fillCssParameters);
fillArray.push(...fillCssParameters);
}
if (graphicFill) {
polygonSymbolizer[0][Fill].push({
GraphicFill: graphicFill
});
fillArray.push({ GraphicFill: graphicFill });
}
}

Expand Down
53 changes: 32 additions & 21 deletions src/Util/SldUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isGeoStylerFunction, isGeoStylerNumberFunction } from 'geostyler-style/
* Cast to Number if it is not a GeoStylerFunction
*
* @param exp The GeoStylerExpression
* @returns The value casted to a number or the GeoStylerNumberFunction
* @returns The value cast to a number or the GeoStylerNumberFunction
*/
export function numberExpression(exp: Expression<PropertyType>): GeoStylerNumberFunction | number {
return isGeoStylerNumberFunction(exp) ? exp : Number(exp);
Expand Down Expand Up @@ -56,14 +56,12 @@ export function geoStylerFunctionToSldFunction(geostylerFunction: GeoStylerFunct
}
});

const sldFunctionObj = [{
return [{
Function: sldFunctionArgs,
':@': {
'@_name': name
}
}];

return sldFunctionObj;
}

/**
Expand Down Expand Up @@ -99,37 +97,25 @@ export function sldFunctionToGeoStylerFunction(sldFunction: any[]): GeoStylerFun
* Get all child objects with a given tag name.
*
* @param elements An array of objects as created by the fast-xml-parser.
* @param tagName The tagname to get.
* @param tagName The tagName to get.
* @returns An array of objects as created by the fast-xml-parser.
*/
export function getChildren(elements: any[], tagName: string): any[] {
return elements?.filter(obj => Object.keys(obj).includes(tagName));
}

/**
* Get the child object with a given tag name.
*
* @param elements An array of objects as created by the fast-xml-parser.
* @param tagName The tagname to get.
* @returns An object as created by the fast-xml-parser.
*/
export function getChild(elements: any[], tagName: string): any {
return elements?.find(obj => Object.keys(obj).includes(tagName));
}

/**
* Get the value of a Css-/SvgParameter.
* Get the value of a parameter from a specific objects in a list of sld elements.
*
* @param elements An array of objects as created by the fast-xml-parser.
* @param paramKey The name of the parameter to find in the elements.
* @param parameter The parameter name to get.
* @param sldVersion The sldVersion to distinguish if CssParameter or SvgParameter is used.
* @returns The string value of the searched parameter.
*/
export function getParameterValue(elements: any[], parameter: string, sldVersion: SldVersion): any {
export function getTextValueInSldObject(elements: any[], parameter: string, paramKey: string): any {
if (!elements) {
return undefined;
}
const paramKey = sldVersion === '1.0.0' ? 'CssParameter' : 'SvgParameter';
const element = elements
.filter(obj => Object.keys(obj)?.includes(paramKey))
.find(obj => obj?.[':@']?.['@_name'] === parameter);
Expand All @@ -146,6 +132,30 @@ export function getParameterValue(elements: any[], parameter: string, sldVersion
return element?.[paramKey]?.[0]?.['#text'];
}

/**
* Get the value of a Css-/SvgParameter.
*
* @param elements An array of objects as created by the fast-xml-parser.
* @param parameter The parameter name to get.
* @param sldVersion The sldVersion to distinguish if CssParameter or SvgParameter is used.
* @returns The string value of the searched parameter.
*/
export function getParameterValue(elements: any[], parameter: string, sldVersion: SldVersion): any {
const paramKey = sldVersion === '1.0.0' ? 'CssParameter' : 'SvgParameter';
return getTextValueInSldObject(elements, parameter, paramKey);
}

/**
* Get the value of a (GeoServer) VendorOption.
*
* @param elements An array of objects as created by the fast-xml-parser.
* @param name The vendorOption name to get.
* @returns The string value of the searched parameter.
*/
export function getVendorOptionValue(elements: any[], name: string): any {
return getTextValueInSldObject(elements, name, 'VendorOption');
}

/**
* Get the attribute value of an object.
*
Expand Down Expand Up @@ -174,7 +184,7 @@ export function isSymbolizer(obj: any): boolean {
* e.g.
* Get text value: get(sldSymbolizer, 'Graphic.Mark.WellKnownName.#text')
* Get an attribute value: get(sldSymbolizer, 'Graphic.ExternalGraphic.OnlineResource.@xlink:href')
* Get an Css-/SvgParameter value: get(sldSymbolizer, 'Graphic.Mark.Fill.$fill-opacity', '1.1.0')
* Get a Css-/SvgParameter value: get(sldSymbolizer, 'Graphic.Mark.Fill.$fill-opacity', '1.1.0')
* Use with an index: get(sldObject, 'StyledLayerDescriptor.NamedLayer[1].UserStyle.Title.#text')
*
* @param obj A part of the parser result of the fast-xml-parser.
Expand Down Expand Up @@ -240,3 +250,4 @@ export function get(obj: any, path: string, sldVersion?: SldVersion): any | unde
export function keysByValue(object: any, value: any): string[] {
return Object.keys(object).filter(key => object[key] === value);
}

0 comments on commit be950ea

Please sign in to comment.