diff --git a/data/olStyles/function_boolean.ts b/data/olStyles/function_boolean.ts index ffd4ac73..59d08f4e 100644 --- a/data/olStyles/function_boolean.ts +++ b/data/olStyles/function_boolean.ts @@ -1,21 +1,28 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg } from '../../src/Util/OlSvgPoints'; + +let svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 20 +}); export const olBoolean1 = new OlStyle({ - image: new OlStyleCircle({ - radius: 10, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); +svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 12 +}); + export const olBoolean2 = new OlStyle({ - image: new OlStyleCircle({ - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/function_case.ts b/data/olStyles/function_case.ts index 03503c4c..2a6b0479 100644 --- a/data/olStyles/function_case.ts +++ b/data/olStyles/function_case.ts @@ -1,30 +1,40 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg } from '../../src/Util/OlSvgPoints'; + +let svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 4 +}); export const olCase1 = new OlStyle({ - image: new OlStyleCircle({ - radius: 2, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); +svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 10 +}); + export const olCase2 = new OlStyle({ - image: new OlStyleCircle({ - radius: 5, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); +svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 20 +}); + export const olCase3 = new OlStyle({ - image: new OlStyleCircle({ - radius: 10, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/function_markSymbolizer.ts b/data/olStyles/function_markSymbolizer.ts index c40342b7..d69ccdce 100644 --- a/data/olStyles/function_markSymbolizer.ts +++ b/data/olStyles/function_markSymbolizer.ts @@ -1,19 +1,17 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; -import OlStyleStroke from 'ol/style/Stroke'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg } from '../../src/Util/OlSvgPoints'; + +let svg = getShapeSvg('cross', { + stroke: '#FF0000', + dimensions: Math.PI * 2 +}); const olFunctionMark = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: Math.PI, - radius2: 0, - fill: new OlStyleFill({ - color: '#FF0000' - }), - stroke: new OlStyleStroke({ - color: '#000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/multi_twoRulesSimplepoint.ts b/data/olStyles/multi_twoRulesSimplepoint.ts index e66c9579..7a1e1492 100644 --- a/data/olStyles/multi_twoRulesSimplepoint.ts +++ b/data/olStyles/multi_twoRulesSimplepoint.ts @@ -1,22 +1,29 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg } from '../../src/Util/OlSvgPoints'; + +let svg = getShapeSvg('circle', { + fill: '#FF0000', + dimensions: 12 +}); const olSimplePoint1 = new OlStyle({ - image: new OlStyleCircle({ - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); +svg = getShapeSvg('circle', { + fill: '#FF1111', + dimensions: 8 +}); + const olSimplePoint2 = new OlStyle({ - image: new OlStyleCircle({ - radius: 4, - fill: new OlStyleFill({ - color: '#FF1111' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplebackslash.ts b/data/olStyles/point_simplebackslash.ts index a7bfa594..7ec3538d 100644 --- a/data/olStyles/point_simplebackslash.ts +++ b/data/olStyles/point_simplebackslash.ts @@ -1,16 +1,20 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; -const olSimpleSlash = new OlStyle({ - image: new OlStyleRegularshape({ - points: 2, - angle: 2 * Math.PI - (Math.PI / 4), - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) +const shape = removeDuplicateShapes('shape://backslash'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); + +const olSimpleBackSlash = new OlStyle({ + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); -export default olSimpleSlash; +export default olSimpleBackSlash; diff --git a/data/olStyles/point_simplecarrow.ts b/data/olStyles/point_simplecarrow.ts index fa962220..91b12225 100644 --- a/data/olStyles/point_simplecarrow.ts +++ b/data/olStyles/point_simplecarrow.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://carrow'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleCarrow = new OlStyle({ - image: new OlStyleRegularshape({ - points: 3, - angle: Math.PI / 2, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplecross.ts b/data/olStyles/point_simplecross.ts index 6ce072d6..3074811c 100644 --- a/data/olStyles/point_simplecross.ts +++ b/data/olStyles/point_simplecross.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('cross'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleCross = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: 6, - radius2: 0, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simpledot.ts b/data/olStyles/point_simpledot.ts index 9ecbc514..a8aed466 100644 --- a/data/olStyles/point_simpledot.ts +++ b/data/olStyles/point_simpledot.ts @@ -1,13 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://dot'); + +const svg = getShapeSvg(shape, { + fill: '#FF0000', + dimensions: 12 +}); const olSimpleDot = new OlStyle({ - image: new OlStyleCircle({ - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplehorline.ts b/data/olStyles/point_simplehorline.ts index 4f3d4b6f..c0543016 100644 --- a/data/olStyles/point_simplehorline.ts +++ b/data/olStyles/point_simplehorline.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://horline'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleHorline = new OlStyle({ - image: new OlStyleRegularshape({ - points: 2, - angle: Math.PI / 2, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simpleoarrow.ts b/data/olStyles/point_simpleoarrow.ts index 0c345acb..83724782 100644 --- a/data/olStyles/point_simpleoarrow.ts +++ b/data/olStyles/point_simpleoarrow.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://oarrow'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleOarrow = new OlStyle({ - image: new OlStyleRegularshape({ - points: 3, - angle: Math.PI / 2, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simpleoffset.ts b/data/olStyles/point_simpleoffset.ts index 488daa5f..4bba668c 100644 --- a/data/olStyles/point_simpleoffset.ts +++ b/data/olStyles/point_simpleoffset.ts @@ -1,13 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('circle'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimplePoint = new OlStyle({ - image: new OlStyleCircle({ - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }), + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous', displacement: [1,1] }) }); diff --git a/data/olStyles/point_simpleplus.ts b/data/olStyles/point_simpleplus.ts index 9e9adecb..7693075b 100644 --- a/data/olStyles/point_simpleplus.ts +++ b/data/olStyles/point_simpleplus.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://plus'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimplePlus = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: 6, - radius2: 0, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplepoint.ts b/data/olStyles/point_simplepoint.ts index 34202af6..6ebbc9f5 100644 --- a/data/olStyles/point_simplepoint.ts +++ b/data/olStyles/point_simplepoint.ts @@ -1,13 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('circle'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimplePoint = new OlStyle({ - image: new OlStyleCircle({ - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simpleslash.ts b/data/olStyles/point_simpleslash.ts index d7844c08..67dea98a 100644 --- a/data/olStyles/point_simpleslash.ts +++ b/data/olStyles/point_simpleslash.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://slash'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleSlash = new OlStyle({ - image: new OlStyleRegularshape({ - points: 2, - angle: Math.PI / 4, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplesquare.ts b/data/olStyles/point_simplesquare.ts index bf94eefb..c5603bba 100644 --- a/data/olStyles/point_simplesquare.ts +++ b/data/olStyles/point_simplesquare.ts @@ -1,14 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('square'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleSquare = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplestar.ts b/data/olStyles/point_simplestar.ts index 8544ba2a..e18c6b7f 100644 --- a/data/olStyles/point_simplestar.ts +++ b/data/olStyles/point_simplestar.ts @@ -1,15 +1,20 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('star'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + fill: '#00FF00', + dimensions: 12 +}); const olSimpleStar = new OlStyle({ - image: new OlStyleRegularshape({ - points: 5, - radius: 6, - radius2: 2, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplestartransparentfill.ts b/data/olStyles/point_simplestartransparentfill.ts new file mode 100644 index 00000000..8bf850d0 --- /dev/null +++ b/data/olStyles/point_simplestartransparentfill.ts @@ -0,0 +1,22 @@ +import OlStyle from 'ol/style/Style'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('star'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + fill: '#00FF00', + fillOpacity: 0, + dimensions: 12 +}); + +const olSimpleStar = new OlStyle({ + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' + }) +}); + +export default olSimpleStar; diff --git a/data/olStyles/point_simpletimes.ts b/data/olStyles/point_simpletimes.ts index 0e0ee83f..d7b203c1 100644 --- a/data/olStyles/point_simpletimes.ts +++ b/data/olStyles/point_simpletimes.ts @@ -1,16 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://times'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleTimes = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: 6, - radius2: 0, - angle: Math.PI / 4, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simpletriangle.ts b/data/olStyles/point_simpletriangle.ts index 3c89b97b..c4330c71 100644 --- a/data/olStyles/point_simpletriangle.ts +++ b/data/olStyles/point_simpletriangle.ts @@ -1,15 +1,20 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('triangle'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleTriangle = new OlStyle({ - image: new OlStyleRegularshape({ - points: 3, - radius: 6, - displacement: [10, 20], - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous', + displacement: [10, 20] }) }); diff --git a/data/olStyles/point_simplevertline.ts b/data/olStyles/point_simplevertline.ts index e17d8b6a..5d9a8bf7 100644 --- a/data/olStyles/point_simplevertline.ts +++ b/data/olStyles/point_simplevertline.ts @@ -1,15 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('shape://vertline'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleVertline = new OlStyle({ - image: new OlStyleRegularshape({ - points: 2, - angle: 0, - radius: 6, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/olStyles/point_simplex.ts b/data/olStyles/point_simplex.ts index f0f74aeb..5680b445 100644 --- a/data/olStyles/point_simplex.ts +++ b/data/olStyles/point_simplex.ts @@ -1,16 +1,19 @@ import OlStyle from 'ol/style/Style'; -import OlStyleRegularshape from 'ol/style/RegularShape'; -import OlStyleFill from 'ol/style/Fill'; +import OlStyleIcon from 'ol/style/Icon'; +import OlStyleUtil from '../../src/Util/OlStyleUtil'; +import { getShapeSvg, removeDuplicateShapes } from '../../src/Util/OlSvgPoints'; + +const shape = removeDuplicateShapes('x'); + +const svg = getShapeSvg(shape, { + stroke: '#FF0000', + dimensions: 12 +}); const olSimpleX = new OlStyle({ - image: new OlStyleRegularshape({ - points: 4, - radius: 6, - radius2: 0, - angle: Math.PI / 4, - fill: new OlStyleFill({ - color: '#FF0000' - }) + image: new OlStyleIcon({ + src: OlStyleUtil.getEncodedSvg(svg), + crossOrigin: 'anonymous' }) }); diff --git a/data/styles/filter_comparison_propertyFunction.ts b/data/styles/filter_comparison_propertyFunction.ts index f6a316d8..82e9a153 100644 --- a/data/styles/filter_comparison_propertyFunction.ts +++ b/data/styles/filter_comparison_propertyFunction.ts @@ -1,23 +1,23 @@ -import {Fproperty, Style} from "geostyler-style"; +import {Fproperty, Style} from 'geostyler-style'; const value: Fproperty = { - name: "property", + name: 'property', args: [ - "value" + 'value' ] }; const min: Fproperty = { - name: "property", + name: 'property', args: [ - "min" + 'min' ] } const max: Fproperty = { - name: "property", + name: 'property', args: [ - "max" + 'max' ] } @@ -25,52 +25,52 @@ const filterComparisonPropertyFunction: Style = { name: 'OL Style', rules: [ { - name: "between min and max", + name: 'between min and max', filter: [ - "&&", + '&&', [ - ">=", + '>=', value, min ], [ - "<=", + '<=', value, max ] ], symbolizers: [ { - kind: "Text", - label: "between min and max", + kind: 'Text', + label: 'between min and max', } ] }, { name: 'above max', filter: [ - ">", + '>', value, max ], symbolizers: [ { - kind: "Text", - label: "above max" + kind: 'Text', + label: 'above max' } ] }, { - name: "below min", + name: 'below min', filter: [ - "<", + '<', value, min ], symbolizers: [ { - kind: "Text", - label: "below min", + kind: 'Text', + label: 'below min', } ] } diff --git a/data/styles/function_markSymbolizer.ts b/data/styles/function_markSymbolizer.ts index 66f807b5..1b614273 100644 --- a/data/styles/function_markSymbolizer.ts +++ b/data/styles/function_markSymbolizer.ts @@ -8,7 +8,7 @@ const functionMarkSymbolizer: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'cross', - color: '#FF0000', + strokeColor: '#FF0000', radius: { name: 'pi' } diff --git a/data/styles/point_simplebackslash.ts b/data/styles/point_simplebackslash.ts index 4a08ea99..8cc108ff 100644 --- a/data/styles/point_simplebackslash.ts +++ b/data/styles/point_simplebackslash.ts @@ -8,9 +8,8 @@ const pointSimpleBackSlash: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://backslash', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplecarrow.ts b/data/styles/point_simplecarrow.ts index 50b4fb86..cd7f96ad 100644 --- a/data/styles/point_simplecarrow.ts +++ b/data/styles/point_simplecarrow.ts @@ -8,9 +8,8 @@ const pointSimpleCarrow: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://carrow', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplecross.ts b/data/styles/point_simplecross.ts index 05a8d7af..465e4d5a 100644 --- a/data/styles/point_simplecross.ts +++ b/data/styles/point_simplecross.ts @@ -8,9 +8,8 @@ const pointSimpleCross: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'cross', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplehorline.ts b/data/styles/point_simplehorline.ts index aef98ccf..e05b644c 100644 --- a/data/styles/point_simplehorline.ts +++ b/data/styles/point_simplehorline.ts @@ -8,9 +8,8 @@ const pointSimpleHorline: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://horline', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simpleoarrow.ts b/data/styles/point_simpleoarrow.ts index 31986094..47d03cb1 100644 --- a/data/styles/point_simpleoarrow.ts +++ b/data/styles/point_simpleoarrow.ts @@ -8,9 +8,8 @@ const pointSimpleOarrow: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://oarrow', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simpleoffset.ts b/data/styles/point_simpleoffset.ts index 441e16ce..2e3712a9 100644 --- a/data/styles/point_simpleoffset.ts +++ b/data/styles/point_simpleoffset.ts @@ -8,7 +8,7 @@ const pointSimpleOffset: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'circle', - color: '#FF0000', + strokeColor: '#FF0000', radius: 6, offset: [1, 1] }] diff --git a/data/styles/point_simpleplus.ts b/data/styles/point_simpleplus.ts index f23905b6..dad0ac37 100644 --- a/data/styles/point_simpleplus.ts +++ b/data/styles/point_simpleplus.ts @@ -8,9 +8,8 @@ const pointSimplePlus: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://plus', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplepoint.ts b/data/styles/point_simplepoint.ts index c64f2b15..bc963ce1 100644 --- a/data/styles/point_simplepoint.ts +++ b/data/styles/point_simplepoint.ts @@ -8,7 +8,7 @@ const pointSimplePoint: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'circle', - color: '#FF0000', + strokeColor: '#FF0000', radius: 6 }] } diff --git a/data/styles/point_simpleslash.ts b/data/styles/point_simpleslash.ts index 58f4ba68..aef49912 100644 --- a/data/styles/point_simpleslash.ts +++ b/data/styles/point_simpleslash.ts @@ -8,9 +8,8 @@ const pointSimpleSlash: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://slash', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplesquare.ts b/data/styles/point_simplesquare.ts index 12495546..2cabb370 100644 --- a/data/styles/point_simplesquare.ts +++ b/data/styles/point_simplesquare.ts @@ -8,9 +8,8 @@ const pointSimpleSquare: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'square', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplestar.ts b/data/styles/point_simplestar.ts index ec8d80e2..a43db715 100644 --- a/data/styles/point_simplestar.ts +++ b/data/styles/point_simplestar.ts @@ -8,9 +8,9 @@ const pointSimpleStar: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'star', - color: '#FF0000', - radius: 6, - rotate: 0 + color: '#00FF00', + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplestartransparentfill.ts b/data/styles/point_simplestartransparentfill.ts new file mode 100644 index 00000000..b8888035 --- /dev/null +++ b/data/styles/point_simplestartransparentfill.ts @@ -0,0 +1,20 @@ +import { Style } from 'geostyler-style'; + +const pointSimpleStar: Style = { + name: 'OL Style', + rules: [ + { + name: 'OL Style Rule 0', + symbolizers: [{ + kind: 'Mark', + wellKnownName: 'star', + color: '#00FF00', + fillOpacity: 0, + strokeColor: '#FF0000', + radius: 6 + }] + } + ] +}; + +export default pointSimpleStar; diff --git a/data/styles/point_simpletimes.ts b/data/styles/point_simpletimes.ts index cea18171..1558f513 100644 --- a/data/styles/point_simpletimes.ts +++ b/data/styles/point_simpletimes.ts @@ -8,9 +8,8 @@ const pointSimpleTimes: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://times', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simpletriangle.ts b/data/styles/point_simpletriangle.ts index b79834c9..a829a92e 100644 --- a/data/styles/point_simpletriangle.ts +++ b/data/styles/point_simpletriangle.ts @@ -8,9 +8,8 @@ const pointSimpleTriangle: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'triangle', - color: '#FF0000', + strokeColor: '#FF0000', radius: 6, - rotate: 0, offset: [10, 20] }] } diff --git a/data/styles/point_simplevertline.ts b/data/styles/point_simplevertline.ts index 3bae97eb..e6fdd6d6 100644 --- a/data/styles/point_simplevertline.ts +++ b/data/styles/point_simplevertline.ts @@ -8,9 +8,8 @@ const pointSimpleVertline: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'shape://vertline', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/data/styles/point_simplex.ts b/data/styles/point_simplex.ts index 65789134..c47a5c5a 100644 --- a/data/styles/point_simplex.ts +++ b/data/styles/point_simplex.ts @@ -8,9 +8,8 @@ const pointSimpleX: Style = { symbolizers: [{ kind: 'Mark', wellKnownName: 'x', - color: '#FF0000', - radius: 6, - rotate: 0 + strokeColor: '#FF0000', + radius: 6 }] } ] diff --git a/package-lock.json b/package-lock.json index 9ea3b824..57245484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "jest": "^29.6.1", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.6.1", - "ol": "^8.2.0", + "ol": "^10.2.1", "regenerator-runtime": "^0.14.0", "semantic-release": "^22.0.8", "typescript": "^5.4.5", @@ -5450,6 +5450,13 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/rbush": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/rbush/-/rbush-3.0.3.tgz", + "integrity": "sha512-lX55lR0iYCgapxD3IrgujpQA1zDxwZI5qMRelKvmKAsSMplFVr7wmMpG7/6+Op2tjrgEex8o3vjg8CRDrRNYxg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -7592,10 +7599,11 @@ } }, "node_modules/earcut": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", - "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", - "dev": true + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz", + "integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==", + "dev": true, + "license": "ISC" }, "node_modules/ecc-jsbn": { "version": "0.1.2", @@ -9095,26 +9103,6 @@ "node": ">=10.17.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -14954,17 +14942,19 @@ } }, "node_modules/ol": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ol/-/ol-8.2.0.tgz", - "integrity": "sha512-/m1ddd7Jsp4Kbg+l7+ozR5aKHAZNQOBAoNZ5pM9Jvh4Etkf0WGkXr9qXd7PnhmwiC1Hnc2Toz9XjCzBBvexfXw==", + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/ol/-/ol-10.2.1.tgz", + "integrity": "sha512-2bB/y2vEnmzjqynP0NA7Cp8k86No3Psn63Dueicep3E3i09axWRVIG5IS/bylEAGfWQx0QXD/uljkyFoY60Wig==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { + "@types/rbush": "3.0.3", "color-rgba": "^3.0.0", "color-space": "^2.0.1", - "earcut": "^2.2.3", + "earcut": "^3.0.0", "geotiff": "^2.0.7", - "pbf": "3.2.1", - "rbush": "^3.0.1" + "pbf": "4.0.1", + "rbush": "^4.0.0" }, "funding": { "type": "opencollective", @@ -15220,12 +15210,12 @@ } }, "node_modules/pbf": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz", - "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pbf/-/pbf-4.0.1.tgz", + "integrity": "sha512-SuLdBvS42z33m8ejRbInMapQe8n0D3vN/Xd5fmWM3tufNgRQFBpaW2YVJxQZV4iPNqb0vEFvssMEo5w9c6BTIA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ieee754": "^1.1.12", "resolve-protobuf-schema": "^2.1.0" }, "bin": { @@ -15546,7 +15536,8 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/psl": { "version": "1.9.0", @@ -15628,10 +15619,11 @@ } }, "node_modules/quickselect": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", - "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", + "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", + "dev": true, + "license": "ISC" }, "node_modules/randombytes": { "version": "2.1.0", @@ -15644,12 +15636,13 @@ } }, "node_modules/rbush": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", - "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", + "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", "dev": true, + "license": "MIT", "dependencies": { - "quickselect": "^2.0.0" + "quickselect": "^3.0.0" } }, "node_modules/rc": { @@ -16118,6 +16111,7 @@ "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==", "dev": true, + "license": "MIT", "dependencies": { "protocol-buffers-schema": "^3.3.1" } diff --git a/package.json b/package.json index bbf15d57..a8f8ea70 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "jest": "^29.6.1", "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^29.6.1", - "ol": "^8.2.0", + "ol": "^10.2.1", "regenerator-runtime": "^0.14.0", "semantic-release": "^22.0.8", "typescript": "^5.4.5", diff --git a/src/OlStyleParser.spec.ts b/src/OlStyleParser.spec.ts index 7edc3094..c0302161 100644 --- a/src/OlStyleParser.spec.ts +++ b/src/OlStyleParser.spec.ts @@ -3,8 +3,6 @@ import 'regenerator-runtime/runtime'; import OlStyle, { Options as StyleOptions } from 'ol/style/Style'; -import OlStyleCircle from 'ol/style/Circle'; -import OlStyleRegularshape from 'ol/style/RegularShape'; import OlStyleIcon from 'ol/style/Icon'; import OlStyleText, { Options as TextOptions } from 'ol/style/Text'; import OlStyleFill, { Options as FillOptions } from 'ol/style/Fill'; @@ -19,6 +17,7 @@ import point_icon_sprite from '../data/styles/point_icon_sprite'; import point_dynamic_icon from '../data/styles/point_dynamic_icon'; import point_simplesquare from '../data/styles/point_simplesquare'; import point_simplestar from '../data/styles/point_simplestar'; +import point_simplestartransparentfill from '../data/styles/point_simplestartransparentfill'; import point_simpletriangle from '../data/styles/point_simpletriangle'; import point_simplecross from '../data/styles/point_simplecross'; import point_simplex from '../data/styles/point_simplex'; @@ -64,6 +63,7 @@ import ol_point_icon from '../data/olStyles/point_icon'; import ol_point_icon_sprite from '../data/olStyles/point_icon_sprite'; import ol_point_simplesquare from '../data/olStyles/point_simplesquare'; import ol_point_simplestar from '../data/olStyles/point_simplestar'; +import ol_point_simplestartransparentfill from '../data/olStyles/point_simplestartransparentfill'; import ol_point_simpletriangle from '../data/olStyles/point_simpletriangle'; import ol_point_simplecross from '../data/olStyles/point_simplecross'; import ol_point_simplex from '../data/olStyles/point_simplex'; @@ -105,8 +105,8 @@ import { Sprite } from 'geostyler-style/dist/style'; -import OlStyleUtil from './Util/OlStyleUtil'; -import exp from 'constants'; +import OlStyleUtil, { DEGREES_TO_RADIANS } from './Util/OlStyleUtil'; +import { getSvgProperties, removeDuplicateShapes } from './Util/OlSvgPoints'; // reverse calculation of resolution for scale (from ol-util MapUtil) function getResolutionForScale (scale, units) { @@ -181,6 +181,11 @@ describe('OlStyleParser implements StyleParser', () => { expect(geoStylerStyle).toBeDefined(); expect(geoStylerStyle).toEqual(point_simplestar); }); + it('can read an OpenLayers MarkSymbolizer as WellKnownName Star Transparent Fill', async () => { + const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplestartransparentfill); + expect(geoStylerStyle).toBeDefined(); + expect(geoStylerStyle).toEqual(point_simplestartransparentfill); + }); it('can read an OpenLayers MarkSymbolizer as WellKnownName Triangle', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpletriangle); expect(geoStylerStyle).toBeDefined(); @@ -194,60 +199,61 @@ describe('OlStyleParser implements StyleParser', () => { it('can read an OpenLayers MarkSymbolizer as WellKnownName X', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplex); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simplex); + expect(JSON.stringify(geoStylerStyle).replace('cross2', 'x')).toEqual(JSON.stringify(point_simplex)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://slash', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpleslash); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simpleslash); + expect(JSON.stringify(geoStylerStyle).replace('slash', 'shape://slash')) + .toEqual(JSON.stringify(point_simpleslash)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://backslash', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplebackslash); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simplebackslash); + expect(JSON.stringify(geoStylerStyle).replace('backslash', 'shape://backslash')) + .toEqual(JSON.stringify(point_simplebackslash)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://vertline', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplevertline); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simplevertline); + expect(JSON.stringify(geoStylerStyle).replace('line', 'shape://vertline')) + .toEqual(JSON.stringify(point_simplevertline)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://horline', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplehorline); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simplehorline); + expect(JSON.stringify(geoStylerStyle).replace('horline', 'shape://horline')) + .toEqual(JSON.stringify(point_simplehorline)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://carrow', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simplecarrow); expect(geoStylerStyle).toBeDefined(); - expect(geoStylerStyle).toEqual(point_simplecarrow); + expect(JSON.stringify(geoStylerStyle).replace('carrow', 'shape://carrow')) + .toEqual(JSON.stringify(point_simplecarrow)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://oarrow', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpleoarrow); expect(geoStylerStyle).toBeDefined(); - // using point_simplecarrow here since reading OlStyle cannot distinguish - // between carrow and oarrow - expect(geoStylerStyle).toEqual(point_simplecarrow); + expect(JSON.stringify(geoStylerStyle).replace('oarrow', 'shape://oarrow')) + .toEqual(JSON.stringify(point_simpleoarrow)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://dot', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpledot); expect(geoStylerStyle).toBeDefined(); - // using point_simplepoint here since reading OlStyle cannot distinguish - // between circle and dot - expect(geoStylerStyle).toEqual(point_simplepoint); + expect(JSON.stringify(geoStylerStyle).replace('circle', 'shape://dot')) + .toEqual(JSON.stringify(point_simpledot)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://plus', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpleplus); expect(geoStylerStyle).toBeDefined(); - // using point_simplecross here since reading OlStyle cannot distinguish - // between cross and plus - expect(geoStylerStyle).toEqual(point_simplecross); + expect(JSON.stringify(geoStylerStyle).replace('cross', 'shape://plus')) + .toEqual(JSON.stringify(point_simpleplus)); }); it('can read an OpenLayers MarkSymbolizer as WellKnownName shape://times', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_point_simpletimes); expect(geoStylerStyle).toBeDefined(); - // using point_simplex here since reading OlStyle cannot distinguish - // between x and times - expect(geoStylerStyle).toEqual(point_simplex); + expect(JSON.stringify(geoStylerStyle).replace('cross2', 'shape://times')) + .toEqual(JSON.stringify(point_simpletimes)); }); it('can read an OpenLayers LineSymbolizer', async () => { const { output: geoStylerStyle } = await styleParser.readStyle(ol_line_simpleline); @@ -397,7 +403,7 @@ describe('OlStyleParser implements StyleParser', () => { expect(result.offset).toHaveLength(2); expect(result.offset?.[0]).toBeCloseTo(offsetX); expect(result.offset?.[1]).toBeCloseTo(offsetY); - expect(result.rotate).toBeCloseTo(rotation / Math.PI * 180); + expect(result.rotate).toBeCloseTo(rotation / DEGREES_TO_RADIANS); }); it('generates correct TextSymbolizer for sophisticated fonst styles', () => { @@ -460,11 +466,16 @@ describe('OlStyleParser implements StyleParser', () => { expect(olStyle).toBeDefined(); const expecSymb = point_simplepoint.rules[0].symbolizers[0] as MarkSymbolizer; - const olCircle: OlStyleCircle = olStyle.getImage() as OlStyleCircle; + const olSimplePoint: OlStyleIcon = olStyle.getImage() as OlStyleIcon; - expect(olCircle).toBeDefined(); - expect(olCircle.getRadius()).toBeCloseTo(expecSymb.radius as number); - expect(olCircle.getFill()?.getColor()).toEqual(expecSymb.color); + expect(olSimplePoint).toBeDefined(); + + const svgString = OlStyleUtil.getDecodedSvg(olSimplePoint.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); + + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); }); it('can write an OpenLayers PointSymbolizer with displacement', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpleoffset); @@ -472,7 +483,7 @@ describe('OlStyleParser implements StyleParser', () => { expect(olStyle).toBeDefined(); const expecSymb = point_simpleoffset.rules[0].symbolizers[0] as MarkSymbolizer; - const olCircle: OlStyleCircle = olStyle.getImage() as OlStyleCircle; + const olCircle: OlStyleIcon = olStyle.getImage() as OlStyleIcon; expect(olCircle).toBeDefined(); expect(olCircle.getDisplacement()).toEqual(expecSymb.offset); @@ -487,7 +498,7 @@ describe('OlStyleParser implements StyleParser', () => { expect(olIcon.getSrc()).toEqual(expecSymb.image); // Rotation in openlayers is radians while we use degree - expect(olIcon.getRotation()).toBeCloseTo((expecSymb.rotate as number) * Math.PI / 180); + expect(olIcon.getRotation()).toBeCloseTo(expecSymb.rotate as number * DEGREES_TO_RADIANS); expect(olIcon.getOpacity()).toBeCloseTo(expecSymb.opacity as number); expect(olIcon).toBeDefined(); @@ -524,296 +535,267 @@ describe('OlStyleParser implements StyleParser', () => { expect(olIcon.getSrc()).toEqual(dummyFeat.get('path')); }); }); - it('can write an OpenLayers RegularShape square', async () => { + it('can write an OpenLayers Marker square', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplesquare); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplesquare.rules[0].symbolizers[0] as MarkSymbolizer; - const olSquare: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olSquare = olStyle.getImage() as OlStyleIcon; expect(olSquare).toBeDefined(); - expect(olSquare.getPoints()).toBeCloseTo(4); - expect(olSquare.getRadius()).toBeCloseTo(expecSymb.radius as number); - expect(olSquare.getAngle()).toBeCloseTo(45 * Math.PI / 180); - expect(olSquare.getRotation()).toBeCloseTo((expecSymb.rotate as number) * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olSquare.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); - const olSquareFill = olSquare.getFill(); - expect(olSquareFill).toBeDefined(); - expect(olSquareFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(JSON.stringify(ol_point_simplesquare)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape star', async () => { + it('can write an OpenLayers Marker star', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplestar); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplestar.rules[0].symbolizers[0] as MarkSymbolizer; - const olStar: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olStar = olStyle.getImage() as OlStyleIcon; expect(olStar).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; + const svgString = OlStyleUtil.getDecodedSvg(olStar.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); + + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(JSON.stringify(ol_point_simplestar)).toEqual(JSON.stringify(olStyle)); + }); + it('can write an OpenLayers Marker star transparent fill', async () => { + let { output: olStyle } = await styleParser.writeStyle(point_simplestartransparentfill); + olStyle = olStyle as OlStyle; + expect(olStyle).toBeDefined(); + + const expecSymb = point_simplestartransparentfill.rules[0].symbolizers[0] as MarkSymbolizer; + const olStarTransparentFill = olStyle.getImage() as OlStyleIcon; + expect(olStarTransparentFill).toBeDefined(); - expect(olStar.getPoints()).toBeCloseTo(5); - expect(olStar.getRadius()).toBeCloseTo(expecSymb.radius as number); - expect(olStar.getRadius2()).toBeCloseTo(expecSymb.radius / 2.5); - expect(olStar.getAngle()).toBeCloseTo(0); - expect(olStar.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olStarTransparentFill.getSrc() as string); + const { id, dimensions, fill, fillOpacity } = getSvgProperties(svgString); - const olStarFill = olStar.getFill(); - expect(olStarFill).toBeDefined(); - expect(olStarFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(fillOpacity).toEqual(expecSymb.fillOpacity); + expect(JSON.stringify(ol_point_simplestartransparentfill)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape triangle', async () => { + it('can write an OpenLayers Marker triangle', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpletriangle); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpletriangle.rules[0].symbolizers[0] as MarkSymbolizer; - const olTriangle: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olTriangle = olStyle.getImage() as OlStyleIcon; expect(olTriangle).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olTriangle.getPoints()).toBeCloseTo(3); - expect(olTriangle.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olTriangle.getAngle()).toBeCloseTo(0); - expect(olTriangle.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olTriangle.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); - const olTriangleFill = olTriangle.getFill(); - expect(olTriangleFill).toBeDefined(); - expect(olTriangleFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(JSON.stringify(ol_point_simpletriangle)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape cross', async () => { + it('can write an OpenLayers Marker cross', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplecross); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplecross.rules[0].symbolizers[0] as MarkSymbolizer; - const olCross: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olCross = olStyle.getImage() as OlStyleIcon; expect(olCross).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olCross.getPoints()).toBeCloseTo(4); - expect(olCross.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olCross.getRadius2()).toBeCloseTo(0); - expect(olCross.getAngle()).toBeCloseTo(0); - expect(olCross.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olCross.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olCrossFill = olCross.getFill(); - expect(olCrossFill).toBeDefined(); - expect(olCrossFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(expecSymb.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simplecross)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape x', async () => { + it('can write an OpenLayers Marker x', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplex); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplex.rules[0].symbolizers[0] as MarkSymbolizer; - const olX: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olX = olStyle.getImage() as OlStyleIcon; expect(olX).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olX.getPoints()).toBeCloseTo(4); - expect(olX.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olX.getRadius2()).toBeCloseTo(0); - expect(olX.getAngle()).toBeCloseTo(45 * Math.PI / 180); - expect(olX.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olX.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olXFill = olX.getFill(); - expect(olXFill).toBeDefined(); - expect(olXFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simplex).replace('%22x%22', '%22cross2%22')).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://slash', async () => { + it('can write an OpenLayers Marker shape://slash', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpleslash); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpleslash.rules[0].symbolizers[0] as MarkSymbolizer; - const olSlash: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olSlash = olStyle.getImage() as OlStyleIcon; expect(olSlash).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; + const svgString = OlStyleUtil.getDecodedSvg(olSlash.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - expect(olSlash.getPoints()).toBeCloseTo(2); - expect(olSlash.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olSlash.getAngle()).toBeCloseTo(Math.PI / 4); - expect(olSlash.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); - - const olSlashFill = olSlash.getFill(); - expect(olSlashFill).toBeDefined(); - expect(olSlashFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simpleslash).replace('shape://slash', 'slash')).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://backslash', async () => { + it('can write an OpenLayers Marker shape://backslash', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplebackslash); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplebackslash.rules[0].symbolizers[0] as MarkSymbolizer; - const olBackSlash: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olBackSlash = olStyle.getImage() as OlStyleIcon; expect(olBackSlash).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; + const svgString = OlStyleUtil.getDecodedSvg(olBackSlash.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - expect(olBackSlash.getPoints()).toBeCloseTo(2); - expect(olBackSlash.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olBackSlash.getAngle()).toBeCloseTo(2 * Math.PI - (Math.PI / 4)); - expect(olBackSlash.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); - - const olBackSlashFill = olBackSlash.getFill(); - expect(olBackSlashFill).toBeDefined(); - expect(olBackSlashFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simplebackslash).replace('shape://backslash', 'backslash')) + .toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://vertline', async () => { + it('can write an OpenLayers Marker shape://vertline', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplevertline); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplevertline.rules[0].symbolizers[0] as MarkSymbolizer; - const olVertline: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olVertline = olStyle.getImage() as OlStyleIcon; expect(olVertline).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olVertline.getPoints()).toBeCloseTo(2); - expect(olVertline.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olVertline.getAngle()).toBeCloseTo(0, 0); - expect(olVertline.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olVertline.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olVertlineFill = olVertline.getFill(); - expect(olVertlineFill).toBeDefined(); - expect(olVertlineFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simplevertline).replace('shape://line', 'vertline')) + .toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://horline', async () => { + it('can write an OpenLayers Marker shape://horline', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplehorline); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplehorline.rules[0].symbolizers[0] as MarkSymbolizer; - const olHorline: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olHorline = olStyle.getImage() as OlStyleIcon; expect(olHorline).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olHorline.getPoints()).toBeCloseTo(2); - expect(olHorline.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olHorline.getAngle()).toBeCloseTo(Math.PI / 2); - expect(olHorline.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olHorline.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olHorlineFill = olHorline.getFill(); - expect(olHorlineFill).toBeDefined(); - expect(olHorlineFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simplehorline).replace('shape://horline', 'horline')) + .toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://carrow', async () => { + it('can write an OpenLayers Marker shape://carrow', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simplecarrow); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simplecarrow.rules[0].symbolizers[0] as MarkSymbolizer; - const olCarrow: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olCarrow = olStyle.getImage() as OlStyleIcon; expect(olCarrow).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; + const svgString = OlStyleUtil.getDecodedSvg(olCarrow.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); - expect(olCarrow.getPoints()).toBeCloseTo(3); - expect(olCarrow.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olCarrow.getAngle()).toBeCloseTo(Math.PI / 2); - expect(olCarrow.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); - - const olCarrowFill = olCarrow.getFill(); - expect(olCarrowFill).toBeDefined(); - expect(olCarrowFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(JSON.stringify(ol_point_simplecarrow).replace('shape://carrow', 'carrow')) + .toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://oarrow', async() => { + it('can write an OpenLayers Marker shape://oarrow', async() => { let { output: olStyle } = await styleParser.writeStyle(point_simpleoarrow); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpleoarrow.rules[0].symbolizers[0] as MarkSymbolizer; - const olOarrow: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olOarrow = olStyle.getImage() as OlStyleIcon; expect(olOarrow).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olOarrow.getPoints()).toBeCloseTo(3); - expect(olOarrow.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olOarrow.getAngle()).toBeCloseTo(Math.PI / 2); - expect(olOarrow.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olOarrow.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olOarrowFill = olOarrow.getFill(); - expect(olOarrowFill).toBeDefined(); - expect(olOarrowFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simpleoarrow).replace('shape://oarrow', 'oarrow')) + .toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://dot', async () => { + it('can write an OpenLayers Marker shape://dot', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpledot); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpledot.rules[0].symbolizers[0] as MarkSymbolizer; - const olDot: OlStyleCircle = olStyle.getImage() as OlStyleCircle; + const olDot = olStyle.getImage() as OlStyleIcon; + expect(olDot).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; + const svgString = OlStyleUtil.getDecodedSvg(olDot.getSrc() as string); + const { id, dimensions, fill } = getSvgProperties(svgString); - expect(olDot).toBeDefined(); - expect(olDot.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olDot.getFill()?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(fill).toEqual(expecSymb.color); + expect(JSON.stringify(ol_point_simpledot)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://plus', async () => { + it('can write an OpenLayers Marker shape://plus', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpleplus); olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpleplus.rules[0].symbolizers[0] as MarkSymbolizer; - const olPlus: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olPlus = olStyle.getImage() as OlStyleIcon; expect(olPlus).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olPlus.getPoints()).toBeCloseTo(4); - expect(olPlus.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olPlus.getRadius2()).toBeCloseTo(0); - expect(olPlus.getAngle()).toBeCloseTo(0); - expect(olPlus.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olPlus.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olPlusFill = olPlus.getFill(); - expect(olPlusFill).toBeDefined(); - expect(olPlusFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simpleplus)).toEqual(JSON.stringify(olStyle)); }); - it('can write an OpenLayers RegularShape shape://times', async () => { + it('can write an OpenLayers Marker shape://times', async () => { let { output: olStyle } = await styleParser.writeStyle(point_simpletimes); - olStyle = olStyle as OlStyle; expect(olStyle).toBeDefined(); const expecSymb = point_simpletimes.rules[0].symbolizers[0] as MarkSymbolizer; - const olTimes: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; + const olTimes = olStyle.getImage() as OlStyleIcon; expect(olTimes).toBeDefined(); - expecSymb.radius = expecSymb.radius as number; - expecSymb.rotate = expecSymb.rotate as number; - - expect(olTimes.getPoints()).toBeCloseTo(4); - expect(olTimes.getRadius()).toBeCloseTo(expecSymb.radius); - expect(olTimes.getRadius2()).toBeCloseTo(0); - expect(olTimes.getAngle()).toBeCloseTo(45 * Math.PI / 180); - expect(olTimes.getRotation()).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + const svgString = OlStyleUtil.getDecodedSvg(olTimes.getSrc() as string); + const { id, dimensions, stroke } = getSvgProperties(svgString); - const olTimesFill = olTimes.getFill(); - expect(olTimesFill).toBeDefined(); - expect(olTimesFill?.getColor()).toEqual(expecSymb.color); + expect(id).toEqual(removeDuplicateShapes(expecSymb.wellKnownName)); + expect((dimensions / 2)).toBeCloseTo(expecSymb.radius as number); + expect(stroke).toEqual(expecSymb.strokeColor); + expect(JSON.stringify(ol_point_simpletimes)) + .toEqual(JSON.stringify(olStyle)); }); it('can write an OpenLayers Style based on a font glyph (WellKnownName starts with ttf://)', async () => { let { output: olStyle } = await styleParser.writeStyle(point_fontglyph); @@ -913,7 +895,7 @@ describe('OlStyleParser implements StyleParser', () => { expecSymb.rotate = expecSymb.rotate as number; const olTextRotation = olText?.getRotation(); - expect(olTextRotation).toBeCloseTo(expecSymb.rotate * Math.PI / 180); + expect(olTextRotation).toBeCloseTo(expecSymb.rotate * DEGREES_TO_RADIANS); const olTextOffsetX = olText?.getOffsetX(); const olTextOffsetY = olText?.getOffsetY(); @@ -935,7 +917,7 @@ describe('OlStyleParser implements StyleParser', () => { const expecText = expecSymb.label; const expecOffset = expecSymb.offset; expecSymb.rotate = expecSymb.rotate as number; - const expecRotation = expecSymb.rotate * Math.PI / 180; + const expecRotation = expecSymb.rotate * DEGREES_TO_RADIANS; // openlayers adds default font-style const expecFont = `normal normal ${expecSymb.size}px ${expecSymb.font?.join(', ')}`; @@ -995,15 +977,23 @@ describe('OlStyleParser implements StyleParser', () => { const expecSymb1 = multi_twoRulesSimplepoint.rules[0].symbolizers[0] as MarkSymbolizer; const expecSymb2 = multi_twoRulesSimplepoint.rules[1].symbolizers[0] as MarkSymbolizer; - const olCircle1 = styles[0].getImage() as OlStyleCircle; + const olCircle1 = styles[0].getImage() as OlStyleIcon; expect(olCircle1).toBeDefined(); - expect(olCircle1.getRadius()).toBeCloseTo(expecSymb1.radius as number); - expect(olCircle1.getFill()?.getColor()).toEqual(expecSymb1.color); + const svgString = OlStyleUtil.getDecodedSvg(olCircle1.getSrc() as string); + let { id, dimensions, fill } = getSvgProperties(svgString); + + expect(id).toEqual(expecSymb1.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb1.radius as number); + expect(fill).toEqual(expecSymb1.color); - const olCircle2 = styles[1].getImage() as OlStyleCircle; + const olCircle2 = styles[1].getImage() as OlStyleIcon; expect(olCircle2).toBeDefined(); - expect(olCircle2.getRadius()).toBeCloseTo(expecSymb2.radius as number); - expect(olCircle2.getFill()?.getColor()).toEqual(expecSymb2.color); + const svgString2 = OlStyleUtil.getDecodedSvg(olCircle2.getSrc() as string); + ({ id, dimensions, fill } = getSvgProperties(svgString2)); + + expect(id).toEqual(expecSymb2.wellKnownName); + expect((dimensions / 2)).toBeCloseTo(expecSymb2.radius as number); + expect(fill).toEqual(expecSymb2.color); }); it('transforms labels values based on fields to string ', async () => { // change the field as base for the label text to a numeric one @@ -1025,7 +1015,6 @@ describe('OlStyleParser implements StyleParser', () => { const olTextContent = olText.getText(); expect(typeof olTextContent).toEqual('string'); expect(olTextContent).toEqual(dummyFeat.get('id') + ''); - }); it('returns style if scale is within scaleDenominators', async () => { let { output: olStyle } = await styleParser.writeStyle(scaleDenomLine); @@ -1077,10 +1066,12 @@ describe('OlStyleParser implements StyleParser', () => { const styleSecond: OlStyle = styleWithinSecond[0]; const expecSecond = scaleDenomLineCircle.rules[1].symbolizers[0] as MarkSymbolizer; - const olCircle: OlStyleCircle = styleSecond.getImage() as OlStyleCircle; + const olCircle = styleSecond.getImage() as OlStyleIcon; expect(olCircle).toBeDefined(); - expect(olCircle.getRadius()).toBeCloseTo(expecSecond.radius as number); - expect(olCircle.getFill()?.getColor()).toEqual(expecSecond.color); + const olCircleSvg = OlStyleUtil.getDecodedSvg(olCircle.getSrc() as string); + const { dimensions, fill } = getSvgProperties(olCircleSvg); + expect(dimensions / 2).toBeCloseTo(expecSecond.radius as number); + expect(fill).toEqual(expecSecond.color); }); it('returns styles of all rules that lie within scaleDenominator', async () => { let { output: olStyle } = await styleParser.writeStyle(scaleDenomLineCircleOverlap); @@ -1113,17 +1104,21 @@ describe('OlStyleParser implements StyleParser', () => { bonnFeat.set('Name', 'Bonn'); const bonnStyle = olStyle(bonnFeat, 1); expect(bonnStyle).toBeDefined(); - const bonnRadius = bonnStyle[0].getImage().getRadius(); + const bonnFilterIcon = bonnStyle[0].getImage(); + const bonnFilterSvg = OlStyleUtil.getDecodedSvg(bonnFilterIcon.getSrc() as string); + let { dimensions } = getSvgProperties(bonnFilterSvg); const expecBonnSymbolizer: MarkSymbolizer = filter_simplefilter.rules[0].symbolizers[0] as MarkSymbolizer; - expect(bonnRadius).toBeCloseTo(expecBonnSymbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecBonnSymbolizer.radius as number); const notBonnFeat = new OlFeature(); notBonnFeat.set('Name', 'Koblenz'); const notBonnStyle = olStyle(notBonnFeat, 1); expect(notBonnStyle).toBeDefined(); - const notBonnRadius = notBonnStyle[0].getImage().getRadius(); + const notBonnFilterIcon = notBonnStyle[0].getImage() as OlStyleIcon; + const notBonnFilterSvg = OlStyleUtil.getDecodedSvg(notBonnFilterIcon.getSrc() as string); + ({ dimensions } = getSvgProperties(notBonnFilterSvg)); const expecNotBonnSymbolizer: MarkSymbolizer = filter_simplefilter.rules[1].symbolizers[0] as MarkSymbolizer; - expect(notBonnRadius).toBeCloseTo(expecNotBonnSymbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecNotBonnSymbolizer.radius as number); }); it('can write an OpenLayers style with a nested filter', async () => { let { output: olStyle } = await styleParser.writeStyle(filter_nestedfilter); @@ -1136,9 +1131,11 @@ describe('OlStyleParser implements StyleParser', () => { matchFilterFeat.set('name', 'Dortmund'); const matchStyle = olStyle(matchFilterFeat, 1); expect(matchStyle).toBeDefined(); - const matchRadius = matchStyle[0].getImage().getRadius(); + const matchFilterIcon = matchStyle[0].getImage() as OlStyleIcon; + const matchFilterSvg = OlStyleUtil.getDecodedSvg(matchFilterIcon.getSrc() as string); + let { dimensions } = getSvgProperties(matchFilterSvg); const expecMatchSymbolizer: MarkSymbolizer = filter_nestedfilter.rules[0].symbolizers[0] as MarkSymbolizer; - expect(matchRadius).toBeCloseTo(expecMatchSymbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecMatchSymbolizer.radius as number); const noMatchFilterFeat = new OlFeature(); noMatchFilterFeat.set('state', 'germany'); @@ -1146,9 +1143,11 @@ describe('OlStyleParser implements StyleParser', () => { noMatchFilterFeat.set('name', 'Schalke'); const noMatchStyle = olStyle(noMatchFilterFeat, 1); expect(noMatchStyle).toBeDefined(); - const noMatchRadius = noMatchStyle[0].getImage().getRadius(); + const noMatchFilterIcon = noMatchStyle[0].getImage() as OlStyleIcon; + const noMatchFilterSvg = OlStyleUtil.getDecodedSvg(noMatchFilterIcon.getSrc() as string); + ({ dimensions } = getSvgProperties(noMatchFilterSvg)); const expecNoMatchSymbolizer: MarkSymbolizer = filter_nestedfilter.rules[1].symbolizers[0] as MarkSymbolizer; - expect(noMatchRadius).toBeCloseTo(expecNoMatchSymbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecNoMatchSymbolizer.radius as number); const noMatchFilterFeat2 = new OlFeature(); noMatchFilterFeat2.set('state', 'germany'); @@ -1156,9 +1155,11 @@ describe('OlStyleParser implements StyleParser', () => { noMatchFilterFeat2.set('name', 'Schalke'); const noMatchStyle2 = olStyle(noMatchFilterFeat2, 1); expect(noMatchStyle2).toBeDefined(); - const noMatchRadius2 = noMatchStyle2[0].getImage().getRadius(); + const noMatchFilter2Icon = noMatchStyle2[0].getImage() as OlStyleIcon; + const noMatchFilter2Svg = OlStyleUtil.getDecodedSvg(noMatchFilter2Icon.getSrc() as string); + ({ dimensions } = getSvgProperties(noMatchFilter2Svg)); const expecNoMatch2Symbolizer: MarkSymbolizer = filter_nestedfilter.rules[1].symbolizers[0] as MarkSymbolizer; - expect(noMatchRadius2).toBeCloseTo(expecNoMatch2Symbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecNoMatch2Symbolizer.radius as number); }); it('does neither match nor crash if filters are invalid', async () => { let { output: olStyle } = await styleParser.writeStyle(filter_invalidfilter); @@ -1171,9 +1172,11 @@ describe('OlStyleParser implements StyleParser', () => { noMatchFilterFeat.set('name', 'Schalke'); const noMatchStyle = olStyle(noMatchFilterFeat, 1); expect(noMatchStyle).toBeDefined(); - const noMatchRadius = noMatchStyle[0].getImage().getRadius(); + const noMatchFilterIcon = noMatchStyle[0].getImage() as OlStyleIcon; + const noMatchFilterSvg = OlStyleUtil.getDecodedSvg(noMatchFilterIcon.getSrc() as string); + const { dimensions } = getSvgProperties(noMatchFilterSvg); const expecNoMatchSymbolizer: MarkSymbolizer = filter_invalidfilter.rules[1].symbolizers[0] as MarkSymbolizer; - expect(noMatchRadius).toBeCloseTo(expecNoMatchSymbolizer.radius as number); + expect(dimensions / 2).toBeCloseTo(expecNoMatchSymbolizer.radius as number); }); it('can write a simple polygon with just fill', async () => { @@ -1317,7 +1320,6 @@ describe('OlStyleParser implements StyleParser', () => { expect(inBetweenOLStyle[0].getText().getText()).toBe(inBetweenLabel); expect(aboveOLStyle[0].getText().getText()).toBe(aboveLabel); expect(belowOLStyle[0].getText().getText()).toBe(belowLabel); - }); it('adds unsupportedProperties to the write output', async () => { diff --git a/src/OlStyleParser.ts b/src/OlStyleParser.ts index efa358aa..bece8f18 100644 --- a/src/OlStyleParser.ts +++ b/src/OlStyleParser.ts @@ -31,22 +31,24 @@ import { } from 'geostyler-style/dist/typeguards'; import OlImageState from 'ol/ImageState'; - import OlGeomPoint from 'ol/geom/Point'; import OlStyle, { StyleFunction as OlStyleFunction, StyleLike as OlStyleLike} from 'ol/style/Style'; import OlStyleImage from 'ol/style/Image'; import OlStyleStroke from 'ol/style/Stroke'; import OlStyleText, { Options as OlStyleTextOptions } from 'ol/style/Text'; -import OlStyleCircle, { Options as OlStyleCircleOptions } from 'ol/style/Circle'; +import OlStyleCircle from 'ol/style/Circle'; import OlStyleFill from 'ol/style/Fill'; import OlStyleIcon, { Options as OlStyleIconOptions } from 'ol/style/Icon'; -import OlStyleRegularshape from 'ol/style/RegularShape'; +import OlStyleRegularshape, { Options as OlStyleRegularshapeOptions } from 'ol/style/RegularShape'; import { METERS_PER_UNIT } from 'ol/proj/Units'; -import OlStyleUtil from './Util/OlStyleUtil'; +import OlStyleUtil, { DEGREES_TO_RADIANS, LINE_WELLKNOWNNAMES, NOFILL_WELLKNOWNNAMES } from './Util/OlStyleUtil'; +import { getShapeSvg, getSvgProperties, isPointDefinedAsSvg, removeDuplicateShapes, + SvgOptions } from './Util/OlSvgPoints'; import { toContext } from 'ol/render'; import OlFeature from 'ol/Feature'; +import { getRegularShapeDefinition, isPointDefinedAsRegularShape } from './Util/OlRegularShapePoints'; export interface OlParserStyleFct { (feature?: any, resolution?: number): any; @@ -208,7 +210,6 @@ export class OlStyleParser implements StyleParser { }; pointSymbolizer = circleSymbolizer; } else if (olStyle.getImage() instanceof this.OlStyleRegularshapeConstructor) { - // square, triangle, star, cross or x const olRegularStyle: OlStyleRegularshape = olStyle.getImage() as OlStyleRegularshape; const olFillStyle = olRegularStyle.getFill(); const olStrokeStyle = olRegularStyle.getStroke(); @@ -228,7 +229,7 @@ export class OlStyleParser implements StyleParser { strokeWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined, radius: (radius !== 0) ? radius : 5, // Rotation in openlayers is radians while we use degree - rotate: olRegularStyle.getRotation() / Math.PI * 180, + rotate: olRegularStyle.getRotation() / DEGREES_TO_RADIANS, offset: offset[0] || offset[1] ? offset : undefined } as MarkSymbolizer; @@ -265,25 +266,41 @@ export class OlStyleParser implements StyleParser { break; case 4: if (Number.isFinite(radius2)) { - // cross or x if (olRegularStyle.getAngle() === 0) { - // cross - markSymbolizer.wellKnownName = 'cross'; + if (olRegularStyle.getRadius2() === 0) { + markSymbolizer.wellKnownName = 'shape://plus'; + } else { + markSymbolizer.wellKnownName = 'star_diamond'; + } } else { - // x markSymbolizer.wellKnownName = 'x'; } } else { - // square - markSymbolizer.wellKnownName = 'square'; + if (olRegularStyle.getAngle() === 0) { + markSymbolizer.wellKnownName = 'diamond'; + } else { + markSymbolizer.wellKnownName = 'square'; + } } break; case 5: - // star - markSymbolizer.wellKnownName = 'star'; + if (Number.isFinite(radius2)) { + markSymbolizer.wellKnownName = 'star'; + } else { + markSymbolizer.wellKnownName = 'pentagon'; + } + break; + case 6: + markSymbolizer.wellKnownName = 'hexagon'; + break; + case 8: + markSymbolizer.wellKnownName = 'octagon'; + break; + case 10: + markSymbolizer.wellKnownName = 'decagon'; break; default: - throw new Error('Could not parse OlStyle. Only 2, 3, 4 or 5 point regular shapes are allowed'); + throw new Error('Could not parse OlStyle.'); } pointSymbolizer = markSymbolizer; } else if (olStyle.getText() instanceof this.OlStyleTextConstructor) { @@ -311,7 +328,7 @@ export class OlStyleParser implements StyleParser { strokeWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined, radius: (radius !== 0) ? radius : 5, // Rotation in openlayers is radians while we use degree - rotate: rotation ? rotation / Math.PI * 180 : 0, + rotate: rotation ? rotation / DEGREES_TO_RADIANS : 0, offset: offset[0] || offset[1] ? offset : undefined } as MarkSymbolizer; } else { @@ -319,22 +336,51 @@ export class OlStyleParser implements StyleParser { const olIconStyle = olStyle.getImage() as OlStyleIcon; const displacement = olIconStyle.getDisplacement() as [number, number]; // initialOptions_ as fallback when image is not yet loaded - const image = this.getImageFromIconStyle(olIconStyle); // this always gets calculated from ol so this might not have been set initially let size = olIconStyle.getWidth(); - const rotation = olIconStyle.getRotation() / Math.PI * 180; + const rotation = olIconStyle.getRotation() / DEGREES_TO_RADIANS; const opacity = olIconStyle.getOpacity(); - const iconSymbolizer: IconSymbolizer = { - kind: 'Icon', - image, - opacity: opacity < 1 ? opacity : undefined, - size, - // Rotation in openlayers is radians while we use degree - rotate: rotation !== 0 ? rotation : undefined, - offset: displacement[0] || displacement[1] ? displacement : undefined - }; - pointSymbolizer = iconSymbolizer; + // If the image is an SVG string try to extract the properties and build a MarkSymbolizer + try { + const svgString = OlStyleUtil.getDecodedSvg(olIconStyle.getSrc() as string); + const { id, dimensions, fill, stroke, strokeWidth } = getSvgProperties(svgString); + let { fillOpacity, strokeOpacity } = getSvgProperties(svgString); + + fillOpacity = OlStyleUtil.checkOpacity(fillOpacity) + ? fillOpacity : OlStyleUtil.getOpacity(String(fill)); + strokeOpacity = OlStyleUtil.checkOpacity(strokeOpacity) + ? strokeOpacity : OlStyleUtil.getOpacity(String(stroke)); + + pointSymbolizer = { + kind: 'Mark', + wellKnownName: id, + ...fill && { color: fill }, + ...OlStyleUtil.checkOpacity(fillOpacity) && { fillOpacity }, + ...stroke && { strokeColor: stroke.substring(0, 7) }, + ...strokeWidth && { strokeWidth }, + ...OlStyleUtil.checkOpacity(strokeOpacity) && { strokeOpacity }, + ...dimensions / 2 !== 0 && { radius: dimensions / 2 }, + ...OlStyleUtil.checkOpacity(opacity) && { opacity }, + ...rotation && { rotate: rotation }, + ...(displacement[0] || displacement[1]) && { offset: displacement }, + } as MarkSymbolizer; + } catch { + // initialOptions_ as fallback when image is not yet loaded + const image = this.getImageFromIconStyle(olIconStyle); + + const iconSymbolizer: IconSymbolizer = { + kind: 'Icon', + image, + size, + ...OlStyleUtil.checkOpacity(opacity) && { opacity }, + // Rotation in openlayers is radians while we use degree + ...rotation && { rotate: rotation }, + ...(displacement[0] || displacement[1]) && { offset: displacement }, + }; + pointSymbolizer = iconSymbolizer; + } + } return pointSymbolizer; } @@ -487,7 +533,7 @@ export class OlStyleParser implements StyleParser { haloColor: olStrokeStyle && olStrokeStyle.getColor() ? OlStyleUtil.getHexColor(olStrokeStyle.getColor() as string) : undefined, haloWidth: olStrokeStyle ? olStrokeStyle.getWidth() : undefined, - rotate: (rotation !== undefined) ? rotation / Math.PI * 180 : undefined + rotate: (rotation !== undefined) ? rotation / DEGREES_TO_RADIANS : undefined }; } @@ -633,7 +679,7 @@ export class OlStyleParser implements StyleParser { * @return The Promise resolving with one of above mentioned style types. */ writeStyle(geoStylerStyle: Style): Promise> { - return new Promise((resolve) => { + return new Promise(async (resolve) => { const clonedStyle = structuredClone(geoStylerStyle); const unsupportedProperties = this.checkForUnsupportedProperties(clonedStyle); try { @@ -1001,14 +1047,18 @@ export class OlStyleParser implements StyleParser { } /** - * Get the OL Style object from an GeoStyler-Style MarkSymbolizer. + * Get the OL Style object from an GeoStyler-Style MarkSymbolizer. * * @param markSymbolizer A GeoStyler-Style MarkSymbolizer. + * @param preferSvg Whether to use SVG or regular shapes. SVG has more flexibility to produce a variety of + * shapes, but has a timing issue (even as a data URL) when used to create canvas fill patterns. * @return The OL Style object */ - getOlPointSymbolizerFromMarkSymbolizer(markSymbolizer: MarkSymbolizer, feature?: OlFeature): OlStyleRegularshape { - let stroke: any; - + getOlPointSymbolizerFromMarkSymbolizer( + markSymbolizer: MarkSymbolizer, + feature?: OlFeature, + preferSvg: boolean = true + ): OlStyle { for (const key of Object.keys(markSymbolizer)) { if (isGeoStylerFunction(markSymbolizer[key as keyof MarkSymbolizer])) { (markSymbolizer as any)[key] = OlStyleUtil.evaluateFunction((markSymbolizer as any)[key], feature); @@ -1017,205 +1067,102 @@ export class OlStyleParser implements StyleParser { const strokeColor = markSymbolizer.strokeColor as string; const strokeOpacity = markSymbolizer.strokeOpacity as number; + let strokeWidth = markSymbolizer.strokeWidth as number; + const fillColor = markSymbolizer.color as string; + const fillOpacity = markSymbolizer.fillOpacity as number; + const rotation = Number(markSymbolizer.rotate) * DEGREES_TO_RADIANS; + const displacement = markSymbolizer.offset as [number, number]; - const sColor = strokeColor && (strokeOpacity !== undefined) - ? OlStyleUtil.getRgbaColor(strokeColor, strokeOpacity) - : markSymbolizer.strokeColor as string; + let shape = removeDuplicateShapes(markSymbolizer.wellKnownName); + let olStyle: any; - if (markSymbolizer.strokeColor || markSymbolizer.strokeWidth !== undefined) { - stroke = new this.OlStyleStrokeConstructor({ - color: sColor, - width: markSymbolizer.strokeWidth as number + if (isPointDefinedAsSvg(shape) && preferSvg) { + const dimensions = (markSymbolizer?.radius as number ?? 6) * 2; // Default to 12 pixels + const opacity = markSymbolizer.opacity as number; + + const svgOpts: SvgOptions = { + dimensions, + ...fillColor && { fill: fillColor }, + ...OlStyleUtil.checkOpacity(fillOpacity) && { fillOpacity }, + ...strokeColor && { stroke: strokeColor }, + ...strokeWidth && { strokeWidth }, + ...OlStyleUtil.checkOpacity(strokeOpacity) && { strokeOpacity } + }; + + const svg = getShapeSvg(shape, svgOpts); + + olStyle = new this.OlStyleConstructor({ + image: new this.OlStyleIconConstructor({ + crossOrigin: 'anonymous', + ...displacement && { displacement }, + ...OlStyleUtil.checkOpacity(opacity) && { opacity }, + ...rotation && { rotation }, + scale: 1, + src: OlStyleUtil.getEncodedSvg(svg) + }) }); - } + } else if (isPointDefinedAsRegularShape(shape)) { + const radius = markSymbolizer?.radius as number ?? 6; // Default to 6 pixels + const radius2 = shape === 'star' || shape === 'star_diamond' ? radius / 2.5 : undefined; + if (strokeWidth && NOFILL_WELLKNOWNNAMES.includes(String(shape))) { + strokeWidth = strokeWidth / 2; + } - const color = markSymbolizer.color as string; - const opacity = markSymbolizer.opacity as number; - const radius = markSymbolizer.radius as number; - const fillOpacity = markSymbolizer.fillOpacity as number; - const fColor = color && (fillOpacity !== undefined) - ? OlStyleUtil.getRgbaColor(color, fillOpacity ?? 1) - : color; + const strokeRgbaColor = (strokeColor && OlStyleUtil.checkOpacity(strokeOpacity) ? + OlStyleUtil.getRgbaColor(strokeColor, strokeOpacity) : + strokeColor) as string; + const fillRgbaColor = (fillColor && OlStyleUtil.checkOpacity(fillOpacity) ? + OlStyleUtil.getRgbaColor(fillColor, fillOpacity) : + fillColor) as string; - const fill = new this.OlStyleFillConstructor({ - color: fColor - }); + const stroke = new this.OlStyleStrokeConstructor({ + ...strokeRgbaColor && { color: strokeRgbaColor as string }, + ...strokeWidth && { width: strokeWidth } + }); + const fill = new this.OlStyleFillConstructor({ + ...fillRgbaColor && { color: fillRgbaColor as string } + }); - let olStyle: any; - const shapeOpts = { - fill: fill, - radius: radius ?? 5, - rotation: typeof(markSymbolizer.rotate) === 'number' ? markSymbolizer.rotate * Math.PI / 180 : undefined, - stroke: stroke, - displacement: Array.isArray(markSymbolizer.offset) ? markSymbolizer.offset.map(Number) : undefined - }; + const shapeOpts: Partial = { + radius, + ...radius2 && { radius2 }, + ...fillColor && { fill }, + ...strokeWidth && strokeColor && { stroke }, + ...strokeWidth && { strokeWidth }, + ...rotation && { rotation } + }; - switch (markSymbolizer.wellKnownName) { - case 'shape://dot': - case 'circle': - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleCircleConstructor(shapeOpts as OlStyleCircleOptions) - }); - break; - case 'square': - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 4, - angle: 45 * Math.PI / 180 - }) - }); - break; - case 'triangle': - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 3, - angle: 0 - }) - }); - break; - case 'star': - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 5, - radius2: shapeOpts.radius! / 2.5, - angle: 0 - }) - }); - break; - case 'shape://plus': - case 'cross': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 4, - radius2: 0, - angle: 0 - }) - }); - break; - case 'shape://times': - case 'x': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 4, - radius2: 0, - angle: 45 * Math.PI / 180 - }) - }); - break; - case 'shape://backslash': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 2, - angle: 2 * Math.PI - (Math.PI / 4) - }) - }); - break; - case 'shape://horline': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 2, - angle: Math.PI / 2 - }) - }); - break; - // so far, both arrows are closed arrows. Also, shape is a regular triangle with - // all sides of equal length. In geoserver arrows only have two sides of equal length. - // TODO redefine shapes of arrows? - case 'shape://oarrow': - case 'shape://carrow': - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 3, - angle: Math.PI / 2 - }) - }); - break; - case 'shape://slash': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 2, - angle: Math.PI / 4 - }) - }); - break; - case 'shape://vertline': - // openlayers does not seem to set a default stroke color, - // which is needed for regularshapes with radius2 = 0 - if (shapeOpts.stroke === undefined) { - shapeOpts.stroke = new this.OlStyleStrokeConstructor({ - color: '#000' - }); - } - olStyle = new this.OlStyleConstructor({ - image: new this.OlStyleRegularshapeConstructor({ - ...shapeOpts, - points: 2, - angle: 0 - }) - }); - break; - default: - if (OlStyleUtil.getIsFontGlyphBased(markSymbolizer)) { - olStyle = new this.OlStyleConstructor({ - text: new this.OlStyleTextConstructor({ - text: OlStyleUtil.getCharacterForMarkSymbolizer(markSymbolizer), - font: OlStyleUtil.getTextFontForMarkSymbolizer(markSymbolizer), - fill: shapeOpts.fill, - stroke: shapeOpts.stroke, - rotation: shapeOpts.rotation - }) - }); - break; - } - throw new Error('MarkSymbolizer cannot be parsed. Unsupported WellKnownName.'); - } + olStyle = getRegularShapeDefinition(shape, shapeOpts); + } else if (OlStyleUtil.getIsFontGlyphBased(markSymbolizer)) { + const strokeRgbaColor = (strokeColor && OlStyleUtil.checkOpacity(strokeOpacity) ? + OlStyleUtil.getRgbaColor(strokeColor, strokeOpacity) : + strokeColor) as string; + const fillRgbaColor = (fillColor && OlStyleUtil.checkOpacity(fillOpacity) ? + OlStyleUtil.getRgbaColor(fillColor, fillOpacity) : + fillColor) as string; + + const stroke = new this.OlStyleStrokeConstructor({ + ...strokeRgbaColor && { color: strokeRgbaColor as string }, + ...strokeWidth && { width: strokeWidth } + }); - if (Number.isFinite(opacity) && olStyle.getImage()) { - olStyle.getImage().setOpacity(opacity); + const fill = new this.OlStyleFillConstructor({ + ...fillRgbaColor && { color: fillRgbaColor as string } + }); + + olStyle = new this.OlStyleConstructor({ + text: new this.OlStyleTextConstructor({ + text: OlStyleUtil.getCharacterForMarkSymbolizer(markSymbolizer), + font: OlStyleUtil.getTextFontForMarkSymbolizer(markSymbolizer), + ...fill && { fill }, + ...displacement && { offsetX: displacement[0] }, + ...displacement && { offsetY: displacement[1] }, + ...stroke && { stroke }, + ...rotation && { rotation }, + }) + }); + } else { + throw new Error('MarkSymbolizer cannot be parsed. Unsupported WellKnownName.'); } return olStyle; @@ -1243,7 +1190,7 @@ export class OlStyleParser implements StyleParser { opacity: symbolizer.opacity as number, width: symbolizer.size as number, // Rotation in openlayers is radians while we use degree - rotation: (typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * Math.PI / 180 : undefined) as number, + rotation: (typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * DEGREES_TO_RADIANS : undefined) as number, displacement: symbolizer.offset as [number, number], size: isSprite(symbolizer.image) ? symbolizer.image.size as [number, number] : undefined, offset: isSprite(symbolizer.image) ? symbolizer.image.position as [number, number] : undefined, @@ -1389,6 +1336,10 @@ export class OlStyleParser implements StyleParser { */ getOlPatternFromGraphicFill(graphicFill: PointSymbolizer): CanvasPattern | null { let graphicFillStyle: any; + let iconSize: [number, number] = [16, 16]; + let iconSpacing = 1; + let scaleFactor = 1; + if (isIconSymbolizer(graphicFill)) { graphicFillStyle = this.getOlIconSymbolizerFromIconSymbolizer(graphicFill); const graphicFillImage = graphicFillStyle?.getImage(); @@ -1398,38 +1349,45 @@ export class OlStyleParser implements StyleParser { return null; } } else if (isMarkSymbolizer(graphicFill)) { - graphicFillStyle = this.getOlPointSymbolizerFromMarkSymbolizer(graphicFill); + graphicFillStyle = this.getOlPointSymbolizerFromMarkSymbolizer(graphicFill, undefined, false); + const diameter = graphicFill.radius as number * 2; + iconSize = [diameter, diameter]; + // Hack to try to join lines for hatch patterns, but space out icon patterns. + // Diagonal lines still do not render nicely in the corners, due to tiling. + // TODO: Maybe use VendorOption's to control spacing? + if (LINE_WELLKNOWNNAMES.includes(String(graphicFill.wellKnownName))) { + const iconRotation = graphicFill.rotate as number || 0; + // Extend lines that aren't horizontal or vertical to be full size of the canvas + const isNotVerticalOrHorizontal = (iconRotation / DEGREES_TO_RADIANS) % 1 !== 0; + if (isNotVerticalOrHorizontal) { + scaleFactor = Math.sin(iconRotation * DEGREES_TO_RADIANS); + } + } else { + iconSpacing = 2; + }; } else { return null; } - - // We need to clone the style and image since we'll be changing the scale below (hack) - const graphicFillStyleCloned = graphicFillStyle.clone(); - const imageCloned = graphicFillStyleCloned.getImage(); + iconSize = graphicFillStyle.getImage().getSize(); + const canvasSize = iconSize.map(item => item * iconSpacing * scaleFactor); // Temporary canvas. // TODO: Can/should we reuse an pre-existing one for efficiency? const tmpCanvas: HTMLCanvasElement = document.createElement('canvas'); + tmpCanvas.width = canvasSize[0]; + tmpCanvas.height = canvasSize[1]; const tmpContext = tmpCanvas.getContext('2d') as CanvasRenderingContext2D; - // Hack to make scaling work for Icons. - // TODO: find a better way than this. - const scale = imageCloned.getScale() || 1; - const pixelRatio = scale; - imageCloned.setScale(1); - - const size: [number, number] = imageCloned.getSize(); - // Create the context where we'll be drawing the style on const vectorContext = toContext(tmpContext, { - pixelRatio, - size + size: canvasSize, + pixelRatio: 1 }); - // Draw the graphic - vectorContext.setStyle(graphicFillStyleCloned); - const pointCoords = size.map(item => item / 2); - vectorContext.drawGeometry(new OlGeomPoint(pointCoords)); + const pointCoords = canvasSize.map(item => item / 2); + const pointFeature = new OlFeature(new OlGeomPoint(pointCoords)); + + vectorContext.drawFeature(pointFeature, graphicFillStyle); // Create the actual pattern and return style return tmpContext.createPattern(tmpCanvas, 'repeat'); @@ -1484,7 +1442,7 @@ export class OlStyleParser implements StyleParser { overflow: symbolizer.allowOverlap as boolean, offsetX: (symbolizer.offset ? symbolizer.offset[0] : 0) as number, offsetY: (symbolizer.offset ? symbolizer.offset[1] : 0) as number, - rotation: typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * Math.PI / 180 : undefined, + rotation: typeof(symbolizer.rotate) === 'number' ? symbolizer.rotate * DEGREES_TO_RADIANS : undefined, placement: placement as 'line' | 'point' // TODO check why props match // textAlign: symbolizer.pitchAlignment, diff --git a/src/Util/OlRegularShapePoints.ts b/src/Util/OlRegularShapePoints.ts new file mode 100644 index 00000000..99dbe2c1 --- /dev/null +++ b/src/Util/OlRegularShapePoints.ts @@ -0,0 +1,48 @@ +import OlStyle from 'ol/style/Style'; +import OlStyleCircle, { Options as OlStyleCircleOptions } from 'ol/style/Circle'; +import OlStyleRegularshape, { Options as OlStyleRegularshapeOptions } from 'ol/style/RegularShape'; +import { DEGREES_TO_RADIANS } from './OlStyleUtil'; + +type regularShapeOptions = { + [key: string]: Partial; +}; + +export const staticRegularShapeOptions: regularShapeOptions = { + square: { points: 4, angle: 45 * DEGREES_TO_RADIANS }, + triangle: { points: 3, angle: 0 }, + equilateral_triangle: { points: 3, angle: 0 }, + pentagon: { points: 5, angle: 0 }, + hexagon: { points: 6, angle: 0 }, + octagon: { points: 8, angle: 0 }, + decagon: { points: 10, angle: 0 }, + star: { points: 5, angle: 0 }, + star_diamond: { points: 4, angle: 0 }, + cross: { points: 4, radius2: 0, angle: 0 }, + cross2: { points: 4, radius2: 0, angle: 45 * DEGREES_TO_RADIANS }, + diamond: { points: 4, angle: 0 }, + backslash: { points: 2, angle: 315 * DEGREES_TO_RADIANS }, + slash: { points: 2, angle: 45 * DEGREES_TO_RADIANS }, + horline: { points: 2, angle: 90 * DEGREES_TO_RADIANS }, + carrow: { points: 3, angle: 90 * DEGREES_TO_RADIANS }, + line: { points: 2, angle: 0 } +}; + +export const getRegularShapeDefinition = ( + shape: string = 'circle', + shapeOpts: Partial +): OlStyle => { + let regularShape: OlStyleRegularshape | OlStyleCircle; + if (shape === 'circle') { + regularShape = new OlStyleCircle(shapeOpts as OlStyleCircleOptions); + } else { + regularShape = new OlStyleRegularshape({ + ...staticRegularShapeOptions[shape] as OlStyleRegularshapeOptions, + ...shapeOpts + }); + } + return new OlStyle({ + image: regularShape + }); +}; + +export const isPointDefinedAsRegularShape = (shape: string) => shape in staticRegularShapeOptions || shape === 'circle'; diff --git a/src/Util/OlStyleUtil.ts b/src/Util/OlStyleUtil.ts index b15e62ab..467263f0 100644 --- a/src/Util/OlStyleUtil.ts +++ b/src/Util/OlStyleUtil.ts @@ -27,7 +27,11 @@ import OlFeature from 'ol/Feature'; import { colors } from './colors'; const WELLKNOWNNAME_TTF_REGEXP = /^ttf:\/\/(.+)#(.+)$/; +const SVG_URI_SCHEME = 'data:image/svg+xml;utf8,'; +export const LINE_WELLKNOWNNAMES = ['horline', 'vertline', 'line']; +export const NOFILL_WELLKNOWNNAMES = ['horline', 'vertline', 'line', 'cross', 'cross2', 'slash', 'backslash', 'oarrow']; export const DUMMY_MARK_SYMBOLIZER_FONT = 'geostyler-mark-symbolizer'; +export const DEGREES_TO_RADIANS = Math.PI / 180; /** * Offers some utility functions to work with OpenLayers Styles. @@ -150,6 +154,18 @@ class OlStyleUtil { } } + /** + * Checks if the given opacity value is valid. + * A valid opacity is a number between 0 and 1. + * Value 1 is ignored as this the default value. + * If the value is not valid, false is returned. + * @param opacity The opacity value to check + * @return true if the opacity is valid, false otherwise + */ + public static checkOpacity(opacity: number | string | undefined): boolean { + return typeof opacity === 'number' && opacity >= 0 && opacity < 1; + } + /** * Returns an OL compliant font string. * @@ -245,6 +261,26 @@ class OlStyleUtil { return parts ? parseInt(parts[1], 10) : 0; } + /** + * Encodes the given SVG string using URI encoding to remove special characters. + * + * @param svgString the SVG string to encode + * @returns the URI encoded SVG string + */ + public static getEncodedSvg(svgString: string) { + return SVG_URI_SCHEME + encodeURIComponent(svgString); + } + + /** + * Decodes a URI encoded SVG string. + * + * @param svgEncodedString The URI encoded SVG string to decode. + * @returns The decoded SVG string. + */ + public static getDecodedSvg(svgEncodedString: string) { + return decodeURIComponent(svgEncodedString).replace(SVG_URI_SCHEME, ''); + } + /** * Resolves the given template string with the given feature attributes, e.g. * the template "Size of area is {{AREA_SIZE}} kmĀ²" would be to resolved @@ -464,9 +500,9 @@ class OlStyleUtil { case 'tan': return Math.tan(args[0] as number); case 'toDegrees': - return (args[0] as number) * (180/Math.PI); + return (args[0] as number) / DEGREES_TO_RADIANS; case 'toRadians': - return (args[0] as number) * (Math.PI/180); + return (args[0] as number) * DEGREES_TO_RADIANS; default: return args[0] as number; } diff --git a/src/Util/OlSvgPoints.ts b/src/Util/OlSvgPoints.ts new file mode 100644 index 00000000..76972d99 --- /dev/null +++ b/src/Util/OlSvgPoints.ts @@ -0,0 +1,239 @@ +import OlStyleUtil, { LINE_WELLKNOWNNAMES } from './OlStyleUtil'; + +export interface SvgOptions { + id?: string; + dimensions: number; + fill?: string; + fillOpacity?: number; + stroke?: string; + strokeWidth?: number; + strokeOpacity?: number; +} + +type svgDefinition = { + [key: string]: string; +}; + +// Shape definitions, all are roughly scaled to 20x20 in coordinates between (-10,-10) to (10,10) +export const pointSvgs: svgDefinition = { + arrow: 'd="M 0,-10 L 5,-5 L 2.5,-5 L 2.5,10 L -2.5,10 L -2.5,-5 L -5,-5 L 0,-10 Z"', + arrowhead: 'd="M -10 -10 L 0 0 L -10 10"', + asterisk_fill: 'd="M -1.5,-10 L 1.5,-10L 1.5,-3.939 L 6.011,-8.132 L 8.132,-6.011 L 3.939,-1.5 L 10,-1.5 L 10,1.5 ' + + 'L 3.939,1.5 L 8.132,6.011 L 6.011,8.132 L 1.5,3.939 L 1.5,10 L -1.5,10 L -1.5,3.939 L -6.011,8.132 ' + + 'L -8.132,6.011 L -3.939,1.5 L -10,1.5 L -10,-1.5 L -3.939,-1.5 L -8.132,-6.011 L -6.011,-8.132 ' + + 'L -1.5,-3.939 L -1.5,-10 Z"', + backslash: 'd="M -12 -12 L 12 12"', + carrow: 'd="M -10 10 L 0 -10 L 10 10 Z"', + circle: 'cx="0" cy="0" r="10"', + cross: 'd="M -12 0 L 12 0 M 0 -12 L 0 12"', + cross_fill: 'd="M -10,-2 L -10,-2 L -10,2 L -2,2 L -2,10 L 2,10 L 2,2 L 10,2 L 10,-2 L 2,-2 L 2,-10 L -2,-10 ' + + 'L -2,-2 L -10,-2 Z"', + cross2: 'd="M -12 -12 L 12 12 M 12 -12 L -12 12"', + decagon: 'points="5.878,8.09 9.511,3.09 9.511,-3.09 5.878,-8.09 0,-10 -5.878,-8.09 -9.511,-3.09 -9.511,3.09 ' + + '-5.878,8.09 0,10 5.878,8.09"', + diagonal_half_square: 'points="-10,-10 10,10 -10,10 -10,-10"', + diamond: 'points="-10,0 0,10 10,0 0,-10 -10,0"', + equilateral_triangle: 'points="-8.66,5 8.66,5 0,-10 -8.66,5"', + filled_arrowhead: 'd="M 0,0 L -10,10 L -10,-10 L 0,0 Z"', + half_arc: 'd="M -10 0 A -10 -10 0 0 1 10 0"', + half_square: 'points="-10,-10 0,-10 0,10 -10,10 -10,-10"', + heart: 'd="M -9.5 -2 A 1 1 0 0 1 0 -7.5 A 1 1 0 0 1 9.5 -2 L 0 10 Z"', + hexagon: 'points="-8.66,-5 -8.66,5 0,10 8.66,5 8.66,-5 0,-10 -8.66,-5"', + horline: 'd="M -12 0 L 12 0"', + left_half_triangle: 'points="0,10 10,10 0,-10 0,10"', + line: 'd="M 0 -12 L 0 12"', + oarrow: 'd="M -10 10 L 0 -10 L 10 10"', + octagon: 'points="-4.142,10 4.142,10 10,4.142 10,-4.142 4.142,-10 -4.142,-10 -10,-4.142 -10,4.142 -4.142,10"', + parallelogram_left: 'points="10,5 5,-5 -10,-5 -5,5 10,5"', + parallelogram_right: 'points="5,5 10,-5 -5,-5 -10,5 5,5"', + pentagon: 'points="-9.511,-3.09 -5.878,8.09 5.878,8.09 9.511,-3.09 0,-10 -9.511,-3.09"', + quarter_arc: 'd="M 0 -10 A 10 10 0 0 0 -10 0"', + quarter_circle: 'd="M 0 -10 A 10 10 0 0 0 -10 0 L 0 0 Z"', + quarter_square: 'points="-10,-10 0,-10 0,0 -10,0 -10,-10"', + right_half_triangle: 'points="-10,10 0,10 0,-10 -10,10"', + rounded_square: 'x="-10" y="-10" width="20" height="20" rx="2.5" ry="2.5"', + semi_circle: 'd="M -10 0 A -10 -10 0 0 1 10 0 L 0 0 Z"', + shield: 'points="10,5 10,-10 -10,-10 -10,5 0,10 10,5"', + slash: 'd="M 12 -12 L -12 12"', + square: 'points="-10,-10 10,-10 10,10 -10,10"', + square_with_corners: 'points="-6.072,10 6.072,10 10,6.072 10,-6.072 6.072,-10 -6.072,-10 -10,-6.072 ' + + '-10,6.072 -6.072,10"', + star: 'd="M -2.24514,-3.09017 -9.51057,-3.09017 -3.63271,1.18034 -5.87785,8.09017 0,3.81966 ' + + '5.87785,8.09017 3.63271,1.18034 9.51057,-3.09017 2.24514,-3.09017 0,-10 -2.24514,-3.09017 Z"', + star_diamond: 'd="M -2.70091,-2.70091 -10,0 -2.70091,2.70091 0,10 2.70091,2.70091 10,0 2.70091,-2.70091 0,-10 Z"', + third_arc: 'd="M 0 -10 A 10 10 0 0 0 -5 8.66"', + third_circle: 'd="M 0 -10 A 10 10 0 0 0 -5 8.66 L 0 0 Z"', + trapezoid: 'points="5,-5 10,5 -10,5 -5,-5 5,-5"', + triangle: 'points="-10,10 10,10 0,-10 -10,10"' +}; + +export const removeDuplicateShapes = (shape: string) => { + switch (shape) { + case 'shape://backslash': + return 'backslash'; + case 'shape://carrow': + return 'carrow'; + case 'shape://dot': + return 'circle'; + case 'shape://horline': + return 'horline'; + case 'shape://oarrow': + return 'oarrow'; + case 'shape://plus': + return 'cross'; + case 'shape://slash': + return 'slash'; + case 'shape://times': + case 'x': + return 'cross2'; + case 'shape://vertline': + return 'line'; + default: + return shape; + } +}; + +export const isPointDefinedAsSvg = (shape: string) => shape in pointSvgs; + +/** + * Returns an SVG string for a given shape type with the specified options. + * + * @param {string} [shape='circle'] The shape type. Supported values are: + * 'arrow', 'arrowhead', 'asterisk_fill', 'circle', 'cross', 'cross2', 'cross_fill', + * 'decagon', 'diamond', 'diagonal_half_square', 'equilateral_triangle', 'filled_arrowhead', + * 'half_arc', 'half_square', 'heart', 'hexagon', 'left_half_triangle', 'line', + * 'octagon', 'parallelogram_left', 'parallelogram_right', 'pentagon', 'quarter_arc', + * 'quarter_circle', 'quarter_square', 'right_half_triangle', 'rounded_square', + * 'semi_circle', 'shield', 'square', 'square_with_corners', 'star', 'star_diamond', + * 'third_arc', 'third_circle', 'trapezoid', 'triangle' + * @param {SvgOptions} [options={}] The options to use for the shape. + * The following options are supported: + * - fill: The color to use for filling the shape. Default is '#fff'. + * - fillOpacity: The opacity to use for filling the shape. Default is '1'. + * - stroke: The color to use for the shape's stroke. Default is '#000'. + * - strokeWidth: The width of the shape's stroke. Default is '1'. + * - strokeOpacity: The opacity to use for the shape's stroke. Default is '1'. + * - dimensions: The width and height of the resulting SVG. Default is '40'. + * @returns {string} An SVG string for the given shape type with the specified options. + */ +export const getShapeSvg = ( + shape = 'circle', + options: SvgOptions = { dimensions: 40, fill: '#fff', fillOpacity: 1, + stroke: '#000', strokeWidth: 1, strokeOpacity: 1 } +) => { + const { dimensions, fill, fillOpacity, stroke, strokeWidth, strokeOpacity } = options; + + if (!isPointDefinedAsSvg(shape)) { + throw new Error('Unknown shape: ' + shape); + } + + const svgHeader = ''; + const svgFooter = ''; + + let svgBody = pointSvgs[shape] + ' '; + + // Depending on the shape definition use different SVG elements + if (svgBody.startsWith('points=')) { + svgBody = ''; + + return svgHeader + svgBody + svgFooter; +}; + +/** + * Extracts the properties of an SVG string into an object. + * + * @param svgString the SVG string to parse + * @returns an object containing the SVG properties + */ +export const getSvgProperties = (svgString: string): SvgOptions => { + try { + // Parse the XML string into a document + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(svgString, 'application/xml'); + + // Check for parsing errors + const parseError = xmlDoc.querySelector('parsererror'); + if (parseError) { + throw new Error('Invalid XML format'); + } + + // Get the first element + const svgElement = xmlDoc.querySelector('svg'); + if (!svgElement) { + throw new Error(' element not found'); + } + + // If exists, return the value of the 'width' attribute + const width = svgElement?.getAttribute('width'); + if (!width) { + throw new Error(' element must include dimensions (no width attribute exists)'); + } + + // Get the first child element of + const firstChildElement = Array.from(svgElement?.children).find((child) => { + return child instanceof Element; + }); + + // Get the id and style from the first child element + const id = firstChildElement?.getAttribute('id') ?? ''; + const styleString = firstChildElement?.getAttribute('style') ?? ''; + + // Split the style string into individual declarations + const styles = styleString.split(';').filter((style) => style.trim() !== ''); + + // Convert the declarations into a key-value map + const styleMap: Record = {}; + for (const style of styles) { + const [key, value] = style.split(':').map((str) => str.trim()); + if (key && value) { + styleMap[key] = value; + } + } + + const svgOpts: SvgOptions = { + id, + dimensions: Number(width), + ...styleMap.fill && { fill: styleMap.fill }, + ...styleMap['fill-opacity'] && { fillOpacity: Number(styleMap['fill-opacity']) }, + ...styleMap.stroke && { stroke: styleMap.stroke }, + ...styleMap['stroke-width'] && { strokeWidth: Number(styleMap['stroke-width']) }, + ...styleMap['stroke-opacity'] && { strokeOpacity: Number(styleMap['stroke-opacity']) } + }; + + return svgOpts; + } catch (error) { + throw new Error('Error parsing SVG'); + } +}; diff --git a/src/Util/svgs.ts b/src/Util/svgs.ts new file mode 100644 index 00000000..76972d99 --- /dev/null +++ b/src/Util/svgs.ts @@ -0,0 +1,239 @@ +import OlStyleUtil, { LINE_WELLKNOWNNAMES } from './OlStyleUtil'; + +export interface SvgOptions { + id?: string; + dimensions: number; + fill?: string; + fillOpacity?: number; + stroke?: string; + strokeWidth?: number; + strokeOpacity?: number; +} + +type svgDefinition = { + [key: string]: string; +}; + +// Shape definitions, all are roughly scaled to 20x20 in coordinates between (-10,-10) to (10,10) +export const pointSvgs: svgDefinition = { + arrow: 'd="M 0,-10 L 5,-5 L 2.5,-5 L 2.5,10 L -2.5,10 L -2.5,-5 L -5,-5 L 0,-10 Z"', + arrowhead: 'd="M -10 -10 L 0 0 L -10 10"', + asterisk_fill: 'd="M -1.5,-10 L 1.5,-10L 1.5,-3.939 L 6.011,-8.132 L 8.132,-6.011 L 3.939,-1.5 L 10,-1.5 L 10,1.5 ' + + 'L 3.939,1.5 L 8.132,6.011 L 6.011,8.132 L 1.5,3.939 L 1.5,10 L -1.5,10 L -1.5,3.939 L -6.011,8.132 ' + + 'L -8.132,6.011 L -3.939,1.5 L -10,1.5 L -10,-1.5 L -3.939,-1.5 L -8.132,-6.011 L -6.011,-8.132 ' + + 'L -1.5,-3.939 L -1.5,-10 Z"', + backslash: 'd="M -12 -12 L 12 12"', + carrow: 'd="M -10 10 L 0 -10 L 10 10 Z"', + circle: 'cx="0" cy="0" r="10"', + cross: 'd="M -12 0 L 12 0 M 0 -12 L 0 12"', + cross_fill: 'd="M -10,-2 L -10,-2 L -10,2 L -2,2 L -2,10 L 2,10 L 2,2 L 10,2 L 10,-2 L 2,-2 L 2,-10 L -2,-10 ' + + 'L -2,-2 L -10,-2 Z"', + cross2: 'd="M -12 -12 L 12 12 M 12 -12 L -12 12"', + decagon: 'points="5.878,8.09 9.511,3.09 9.511,-3.09 5.878,-8.09 0,-10 -5.878,-8.09 -9.511,-3.09 -9.511,3.09 ' + + '-5.878,8.09 0,10 5.878,8.09"', + diagonal_half_square: 'points="-10,-10 10,10 -10,10 -10,-10"', + diamond: 'points="-10,0 0,10 10,0 0,-10 -10,0"', + equilateral_triangle: 'points="-8.66,5 8.66,5 0,-10 -8.66,5"', + filled_arrowhead: 'd="M 0,0 L -10,10 L -10,-10 L 0,0 Z"', + half_arc: 'd="M -10 0 A -10 -10 0 0 1 10 0"', + half_square: 'points="-10,-10 0,-10 0,10 -10,10 -10,-10"', + heart: 'd="M -9.5 -2 A 1 1 0 0 1 0 -7.5 A 1 1 0 0 1 9.5 -2 L 0 10 Z"', + hexagon: 'points="-8.66,-5 -8.66,5 0,10 8.66,5 8.66,-5 0,-10 -8.66,-5"', + horline: 'd="M -12 0 L 12 0"', + left_half_triangle: 'points="0,10 10,10 0,-10 0,10"', + line: 'd="M 0 -12 L 0 12"', + oarrow: 'd="M -10 10 L 0 -10 L 10 10"', + octagon: 'points="-4.142,10 4.142,10 10,4.142 10,-4.142 4.142,-10 -4.142,-10 -10,-4.142 -10,4.142 -4.142,10"', + parallelogram_left: 'points="10,5 5,-5 -10,-5 -5,5 10,5"', + parallelogram_right: 'points="5,5 10,-5 -5,-5 -10,5 5,5"', + pentagon: 'points="-9.511,-3.09 -5.878,8.09 5.878,8.09 9.511,-3.09 0,-10 -9.511,-3.09"', + quarter_arc: 'd="M 0 -10 A 10 10 0 0 0 -10 0"', + quarter_circle: 'd="M 0 -10 A 10 10 0 0 0 -10 0 L 0 0 Z"', + quarter_square: 'points="-10,-10 0,-10 0,0 -10,0 -10,-10"', + right_half_triangle: 'points="-10,10 0,10 0,-10 -10,10"', + rounded_square: 'x="-10" y="-10" width="20" height="20" rx="2.5" ry="2.5"', + semi_circle: 'd="M -10 0 A -10 -10 0 0 1 10 0 L 0 0 Z"', + shield: 'points="10,5 10,-10 -10,-10 -10,5 0,10 10,5"', + slash: 'd="M 12 -12 L -12 12"', + square: 'points="-10,-10 10,-10 10,10 -10,10"', + square_with_corners: 'points="-6.072,10 6.072,10 10,6.072 10,-6.072 6.072,-10 -6.072,-10 -10,-6.072 ' + + '-10,6.072 -6.072,10"', + star: 'd="M -2.24514,-3.09017 -9.51057,-3.09017 -3.63271,1.18034 -5.87785,8.09017 0,3.81966 ' + + '5.87785,8.09017 3.63271,1.18034 9.51057,-3.09017 2.24514,-3.09017 0,-10 -2.24514,-3.09017 Z"', + star_diamond: 'd="M -2.70091,-2.70091 -10,0 -2.70091,2.70091 0,10 2.70091,2.70091 10,0 2.70091,-2.70091 0,-10 Z"', + third_arc: 'd="M 0 -10 A 10 10 0 0 0 -5 8.66"', + third_circle: 'd="M 0 -10 A 10 10 0 0 0 -5 8.66 L 0 0 Z"', + trapezoid: 'points="5,-5 10,5 -10,5 -5,-5 5,-5"', + triangle: 'points="-10,10 10,10 0,-10 -10,10"' +}; + +export const removeDuplicateShapes = (shape: string) => { + switch (shape) { + case 'shape://backslash': + return 'backslash'; + case 'shape://carrow': + return 'carrow'; + case 'shape://dot': + return 'circle'; + case 'shape://horline': + return 'horline'; + case 'shape://oarrow': + return 'oarrow'; + case 'shape://plus': + return 'cross'; + case 'shape://slash': + return 'slash'; + case 'shape://times': + case 'x': + return 'cross2'; + case 'shape://vertline': + return 'line'; + default: + return shape; + } +}; + +export const isPointDefinedAsSvg = (shape: string) => shape in pointSvgs; + +/** + * Returns an SVG string for a given shape type with the specified options. + * + * @param {string} [shape='circle'] The shape type. Supported values are: + * 'arrow', 'arrowhead', 'asterisk_fill', 'circle', 'cross', 'cross2', 'cross_fill', + * 'decagon', 'diamond', 'diagonal_half_square', 'equilateral_triangle', 'filled_arrowhead', + * 'half_arc', 'half_square', 'heart', 'hexagon', 'left_half_triangle', 'line', + * 'octagon', 'parallelogram_left', 'parallelogram_right', 'pentagon', 'quarter_arc', + * 'quarter_circle', 'quarter_square', 'right_half_triangle', 'rounded_square', + * 'semi_circle', 'shield', 'square', 'square_with_corners', 'star', 'star_diamond', + * 'third_arc', 'third_circle', 'trapezoid', 'triangle' + * @param {SvgOptions} [options={}] The options to use for the shape. + * The following options are supported: + * - fill: The color to use for filling the shape. Default is '#fff'. + * - fillOpacity: The opacity to use for filling the shape. Default is '1'. + * - stroke: The color to use for the shape's stroke. Default is '#000'. + * - strokeWidth: The width of the shape's stroke. Default is '1'. + * - strokeOpacity: The opacity to use for the shape's stroke. Default is '1'. + * - dimensions: The width and height of the resulting SVG. Default is '40'. + * @returns {string} An SVG string for the given shape type with the specified options. + */ +export const getShapeSvg = ( + shape = 'circle', + options: SvgOptions = { dimensions: 40, fill: '#fff', fillOpacity: 1, + stroke: '#000', strokeWidth: 1, strokeOpacity: 1 } +) => { + const { dimensions, fill, fillOpacity, stroke, strokeWidth, strokeOpacity } = options; + + if (!isPointDefinedAsSvg(shape)) { + throw new Error('Unknown shape: ' + shape); + } + + const svgHeader = ''; + const svgFooter = ''; + + let svgBody = pointSvgs[shape] + ' '; + + // Depending on the shape definition use different SVG elements + if (svgBody.startsWith('points=')) { + svgBody = ''; + + return svgHeader + svgBody + svgFooter; +}; + +/** + * Extracts the properties of an SVG string into an object. + * + * @param svgString the SVG string to parse + * @returns an object containing the SVG properties + */ +export const getSvgProperties = (svgString: string): SvgOptions => { + try { + // Parse the XML string into a document + const parser = new DOMParser(); + const xmlDoc = parser.parseFromString(svgString, 'application/xml'); + + // Check for parsing errors + const parseError = xmlDoc.querySelector('parsererror'); + if (parseError) { + throw new Error('Invalid XML format'); + } + + // Get the first element + const svgElement = xmlDoc.querySelector('svg'); + if (!svgElement) { + throw new Error(' element not found'); + } + + // If exists, return the value of the 'width' attribute + const width = svgElement?.getAttribute('width'); + if (!width) { + throw new Error(' element must include dimensions (no width attribute exists)'); + } + + // Get the first child element of + const firstChildElement = Array.from(svgElement?.children).find((child) => { + return child instanceof Element; + }); + + // Get the id and style from the first child element + const id = firstChildElement?.getAttribute('id') ?? ''; + const styleString = firstChildElement?.getAttribute('style') ?? ''; + + // Split the style string into individual declarations + const styles = styleString.split(';').filter((style) => style.trim() !== ''); + + // Convert the declarations into a key-value map + const styleMap: Record = {}; + for (const style of styles) { + const [key, value] = style.split(':').map((str) => str.trim()); + if (key && value) { + styleMap[key] = value; + } + } + + const svgOpts: SvgOptions = { + id, + dimensions: Number(width), + ...styleMap.fill && { fill: styleMap.fill }, + ...styleMap['fill-opacity'] && { fillOpacity: Number(styleMap['fill-opacity']) }, + ...styleMap.stroke && { stroke: styleMap.stroke }, + ...styleMap['stroke-width'] && { strokeWidth: Number(styleMap['stroke-width']) }, + ...styleMap['stroke-opacity'] && { strokeOpacity: Number(styleMap['stroke-opacity']) } + }; + + return svgOpts; + } catch (error) { + throw new Error('Error parsing SVG'); + } +};