geostyler-qgis-parser
Version:
GeoStyler Style Parser implementation for QGIS Style
1,003 lines • 38 kB
JavaScript
import { isGeoStylerFunction } from 'geostyler-style';
import { CqlParser } from 'geostyler-cql-parser';
import Color from 'color';
import { parseString, Builder } from 'xml2js';
const get = (obj, path, defaultValue = undefined) => {
const travel = (regexp) => String.prototype.split
.call(path, regexp)
.filter(Boolean)
.reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
return result === undefined || result === obj ? defaultValue : result;
};
const dot = [2, 2];
const dash = [10, 2];
const lineStyleDashArrays = {
solid: undefined,
dot: dot,
dash: dash,
'dash dot': [...dash, ...dot],
'dash dot dot': [...dash, ...dot, ...dot]
};
const AnchorMap = {
left: 'L',
right: 'R',
top: 'T',
bottom: 'B',
'top-left': 'TL',
'top-right': 'TR',
'bottom-left': 'BL',
'bottom-right': 'BR'
};
/**
* This parser can be used with the GeoStyler.
* It implements the GeoStyler-Style StyleParser interface.
*
* @class QGISStyleParser
* @implements StyleParser
*/
export class QGISStyleParser {
cqlParser = new CqlParser();
/**
* The name of the QGIS Style Parser.
*/
static title = 'QGIS Style Parser';
title = 'QGIS Style Parser';
qgisVersion;
// QGIS 3.28 and later use <Option> instead of <prop> to serialize individual properties.
writePropsAsOptions;
constructor(opts = {}) {
this.qgisVersion = opts.qgisVersion ?? '3.28.0-Firenze';
this.writePropsAsOptions = this.compareVersions('3.28.0-Firenze', this.qgisVersion) >= 0;
}
compareVersions(version1, version2) {
try {
const parseVersion = (version) => version.split('-')[0].split('.').map(Number);
const [major1, minor1, patch1] = parseVersion(version1);
const [major2, minor2, patch2] = parseVersion(version2);
return Math.sign(major2 - major1) || Math.sign(minor2 - minor1) || Math.sign(patch2 - patch1);
}
catch (error) {
return 0;
}
}
/**
* The readStyle implementation of the GeoStyler-Style StyleParser interface.
* It reads a QML as a string and returns a Promise containing the
* GeoStyler-Style Style.
*
* @param {string} qmlString A QML as a string.
* @return {Promise} The Promise resolving with the GeoStyler-Style Style
*/
readStyle = async (qmlString) => {
const options = {};
try {
let readResult;
parseString(qmlString, options, (err, result) => {
if (err) {
readResult = {
errors: [err]
};
return;
}
const geoStylerStyle = this.qmlObjectToGeoStylerStyle(result);
readResult = {
output: geoStylerStyle
};
});
return readResult || {
errors: []
};
}
catch (error) {
return {
errors: [error]
};
}
};
/**
*
* @param qmlLabel
*/
parseLabelTemplates(qmlLabel) {
let geostylerLabel = '';
const qmlLabelArray = qmlLabel.split('||');
qmlLabelArray.forEach((part) => {
const singleQuotedText = part.match(/[']([^']+)[']/);
if (singleQuotedText) {
geostylerLabel += singleQuotedText[1];
}
else {
geostylerLabel += `{{${part.trim()}}}`;
}
});
return geostylerLabel;
}
/**
*
* @param qmlSymbolizer
*/
qmlSymbolizerLayerPropsToObject(qmlSymbolizer) {
const qmlMarkerProps = {};
// Read props from QML written by QGIS<3.28.0
if (qmlSymbolizer.prop) {
qmlSymbolizer.prop.forEach((prop) => {
const key = prop.$.k;
const value = prop.$.v;
qmlMarkerProps[key] = value;
});
}
// Read props from QML written by QGIS>=3.28.0
if (qmlSymbolizer.Option && qmlSymbolizer.Option[0].Option) {
qmlSymbolizer.Option[0].Option.forEach((option) => {
const key = option.$.name;
const value = option.$.value;
qmlMarkerProps[key] = value;
});
}
return qmlMarkerProps;
}
/**
* Get the GeoStyler-Style Style from an QML Object (created with xml2js).
*
* @param {object} qmlObject The QML object representation (created with xml2js)
* @return {Style} The GeoStyler-Style Style
*/
qmlObjectToGeoStylerStyle(qmlObject) {
const rules = this.getRulesFromQmlObject(qmlObject);
return {
name: 'QGIS Style',
rules
};
}
/**
*
* @param hex
* @param opacity
*/
qmlColorFromHexAndOpacity(hex, opacity) {
opacity = Number.isNaN(Number(opacity)) ? 1 : opacity;
const colorArray = Color(hex).alpha(opacity || 1).rgb().array();
const alpha = colorArray[3] === undefined || isNaN(colorArray[3]) ? 255
: colorArray[3] === 0 ? 0
: 255 * colorArray[3];
const color = `${colorArray[0]},${colorArray[1]},${colorArray[2]},${Math.round(alpha)}`;
return color;
}
/**
*
* @param color
*/
qmlColorToOpacity(color) {
const colorArray = color.split(',');
const opacity = parseFloat(colorArray[3]) / 255;
return Math.round(opacity * 100) / 100;
}
/**
*
* @param color
*/
qmlColorToHex(qmlColor) {
const colorArray = qmlColor.split(',');
const color = Color(`rgb(${colorArray[0]},${colorArray[1]},${colorArray[2]})`);
return color.hex();
}
/**
* Get the GeoStyler-Style Rule from an QML Object (created with xml2js).
*
* @param {object} qmlObject The QML object representation (created with xml2js)
* @return {Rule} The GeoStyler-Style Rule
*/
getRulesFromQmlObject(qmlObject) {
const qmlRenderer = get(qmlObject, 'qgis.renderer-v2.[0]');
const qmlRules = get(qmlRenderer, 'rules[0].rule');
const qmlCategories = get(qmlRenderer, 'categories[0].category');
const qmlRanges = get(qmlRenderer, 'ranges[0].range');
const qmlSymbols = get(qmlRenderer, 'symbols[0].symbol');
const qmlLabeling = get(qmlObject, 'qgis.labeling.[0]');
let rules = [];
let symbolizerMap = {};
let labelMap = {};
if (Array.isArray(qmlSymbols)) {
symbolizerMap = this.parseQmlSymbolizers(qmlSymbols);
}
if (qmlLabeling) {
labelMap = this.parseQmlLabeling(qmlLabeling);
}
if (Array.isArray(qmlRules) && qmlRules.length > 0) {
qmlRules.forEach((qmlRule, index) => {
const filter = this.getFilterFromQmlRule(qmlRule);
const scaleDenominator = this.getScaleDenominatorFromRule(qmlRule);
const name = qmlRule.$.label || qmlRule.$.filter;
let rule = {
name
};
if (filter) {
rule.filter = filter;
}
if (scaleDenominator) {
rule.scaleDenominator = scaleDenominator;
}
if (Object.keys(symbolizerMap).length > 0 && symbolizerMap[qmlRule.$.symbol]) {
rule.symbolizers = symbolizerMap[qmlRule.$.symbol];
}
rules.push(rule);
});
}
else if (Array.isArray(qmlCategories) && qmlCategories.length > 0) {
const attribute = get(qmlObject, 'qgis.renderer-v2.[0].$.attr');
qmlCategories.forEach((qmlCategory, index) => {
const value = qmlCategory.$.value;
const filter = ['==', attribute, value];
const name = `${attribute} = ${value}`;
let rule = {
name,
filter
};
if (symbolizerMap && symbolizerMap[qmlCategory.$.symbol]) {
rule.symbolizers = symbolizerMap[qmlCategory.$.symbol];
}
rules.push(rule);
});
}
else if (Array.isArray(qmlRanges) && qmlRanges.length > 0) {
const attribute = get(qmlObject, 'qgis.renderer-v2.[0].$.attr');
qmlRanges.forEach((qmlRange, index) => {
const name = qmlRange.$.label;
const lower = qmlRange.$.lower;
const upper = qmlRange.$.upper;
const filter = [
'&&',
['>=', attribute, lower],
['<=', attribute, upper]
];
let rule = {
name,
filter
};
if (symbolizerMap && symbolizerMap[qmlRange.$.symbol]) {
rule.symbolizers = symbolizerMap[qmlRange.$.symbol];
}
rules.push(rule);
});
}
else {
const symbolizers = symbolizerMap[Object.keys(symbolizerMap)[0]] || [];
const labels = labelMap[Object.keys(labelMap)[0]] || [];
const rule = {
name: 'QGIS Simple Symbol',
symbolizers: [
...symbolizers,
...labels
]
};
try {
const filter = this.cqlParser.read(Object.keys(labelMap)[0]);
if (filter) {
rule.filter = filter;
}
}
catch (e) {
// in the case of made up filters
}
rules.push(rule);
}
return rules;
}
/**
* Get the GeoStyler-Style Filter from an QML Rule.
*
* Currently only supports one Filter per Rule.
*
* @param {object} qmlRule The QML Rule
* @return {Filter} The GeoStyler-Style Filter
*/
getFilterFromQmlRule(qmlRule) {
const qmlFilter = get(qmlRule, '$.filter');
if (qmlFilter) {
const f = this.cqlParser.read(qmlFilter);
return f;
}
return undefined;
}
/**
* Get the GeoStyler-Style ScaleDenominator from an QML Rule.
*
* @param {object} qmlRule The QML Rule
* @return {ScaleDenominator} The GeoStyler-Style ScaleDenominator
*/
getScaleDenominatorFromRule(qmlRule) {
const maxScaleDenominator = get(qmlRule, '$.scalemaxdenom');
const minScaleDenominator = get(qmlRule, '$.scalemindenom');
let scaleDenominator = {};
if (minScaleDenominator) {
scaleDenominator.min = Number(minScaleDenominator);
}
if (maxScaleDenominator) {
scaleDenominator.max = Number(maxScaleDenominator);
}
return (scaleDenominator.min || scaleDenominator.max)
? scaleDenominator
: undefined;
}
/**
*
* @param qmlLabels
*/
parseQmlLabeling(qmlLabeling) {
const type = qmlLabeling.$.type;
const labelMap = {};
if (type === 'rule-based') {
const rules = get(qmlLabeling, 'rules[0].rule');
rules.forEach((rule, index) => {
const settings = get(rule, 'settings[0]');
const textSymbolizer = this.getTextSymbolizerFromLabelSettings(settings);
labelMap[rule.$.filter || index] = [textSymbolizer];
});
}
if (type === 'simple') {
const settings = get(qmlLabeling, 'settings[0]');
const textSymbolizer = this.getTextSymbolizerFromLabelSettings(settings);
labelMap.a = [textSymbolizer];
}
return labelMap;
}
/**
*
* @param settings
*/
getTextSymbolizerFromLabelSettings(settings) {
let textSymbolizer = {
kind: 'Text',
};
const styleProperties = get(settings, 'text-style[0].$');
const placementProperties = get(settings, 'placement[0].$');
if (styleProperties.textColor) {
textSymbolizer.color = this.qmlColorToHex(styleProperties.textColor);
}
if (styleProperties.fieldName) {
// TODO parse fieldName templates like: "'ID: ' || ID"
textSymbolizer.label = this.parseLabelTemplates(styleProperties.fieldName);
}
if (styleProperties.fontSize) {
textSymbolizer.size = parseFloat(styleProperties.fontSize);
}
if (styleProperties.fontFamily) {
textSymbolizer.font = [styleProperties.fontFamily];
}
if (styleProperties.fontLetterSpacing && parseFloat(styleProperties.fontLetterSpacing) > 0) {
textSymbolizer.letterSpacing = parseFloat(styleProperties.fontLetterSpacing);
}
if (styleProperties.multilineHeight && parseFloat(styleProperties.multilineHeight) > 0) {
textSymbolizer.lineHeight = parseFloat(styleProperties.multilineHeight);
}
if (parseFloat(placementProperties.xOffset) > 0 || parseFloat(placementProperties.yOffset) > 0) {
textSymbolizer.offset = [
parseFloat(placementProperties.xOffset),
parseFloat(placementProperties.yOffset)
];
}
return textSymbolizer;
}
/**
*
*/
parseQmlSymbolizers(qmlSymbolizers) {
const symbolizerMap = {};
qmlSymbolizers.forEach((qmlSymbolizer) => {
const symbolizerKey = get(qmlSymbolizer, '$.name');
const symbolizerType = get(qmlSymbolizer, '$.type');
let symbolizers;
switch (symbolizerType) {
case 'marker':
symbolizers = this.getPointSymbolizersFromQmlSymbolizer(qmlSymbolizer);
break;
case 'line':
symbolizers = this.getLineSymbolizersFromQmlSymbolizer(qmlSymbolizer);
break;
case 'fill':
symbolizers = this.getFillSymbolizerFromQmlSymbolizer(qmlSymbolizer);
break;
default:
throw new Error('Failed to parse SymbolizerKind from qmlSymbolizer');
}
symbolizerMap[symbolizerKey] = symbolizers;
});
return symbolizerMap;
}
/**
* Get the GeoStyler-Style PointSymbolizer from an QGIS Symbolizer.
*
* The opacity of the Symbolizer is taken from the <Graphic>.
*
* @param {object} qmlSymbolizer The QGIS Symbolizer
* @return {PointSymbolizer} The GeoStyler-Style PointSymbolizer
*/
getPointSymbolizersFromQmlSymbolizer(qmlSymbolizer) {
return qmlSymbolizer.layer.map((symbolizerLayer) => {
const markerClass = symbolizerLayer.$.class;
switch (markerClass) {
case 'SimpleMarker':
return this.getPointSymbolizerFromMarkLayer(symbolizerLayer);
case 'SvgMarker':
return this.getPointSymbolizerFromSvgLayer(symbolizerLayer);
default:
throw new Error(`Failed to parse MarkerClass ${markerClass} from qmlSymbolizer`);
}
});
}
/**
* Get the GeoStyler-Style MarkSymbolizer from an QML Symbolizer
*
* @param {object} symbolizerLayer The QML SymbolizerLayer
* @return {MarkSymbolizer} The GeoStyler-Style MarkSymbolizer
*/
getPointSymbolizerFromMarkLayer(symbolizerLayer) {
let markSymbolizer = {
kind: 'Mark',
};
const qmlMarkerProps = this.qmlSymbolizerLayerPropsToObject(symbolizerLayer);
markSymbolizer.wellKnownName = qmlMarkerProps.name;
if (qmlMarkerProps.color) {
markSymbolizer.opacity = this.qmlColorToOpacity(qmlMarkerProps.color);
markSymbolizer.color = this.qmlColorToHex(qmlMarkerProps.color);
}
if (qmlMarkerProps.angle) {
markSymbolizer.rotate = parseFloat(qmlMarkerProps.angle);
}
if (qmlMarkerProps.size) {
markSymbolizer.radius = parseFloat(qmlMarkerProps.size) / 2;
}
// TODO Fix in style declaration
// if (qmlMarkerProps.offset) {
// markSymbolizer.offset = qmlMarkerProps.offset.split(',').map(parseFloat);
// }
if (qmlMarkerProps.outline_color) {
markSymbolizer.strokeOpacity = this.qmlColorToOpacity(qmlMarkerProps.outline_color);
markSymbolizer.strokeColor = this.qmlColorToHex(qmlMarkerProps.outline_color);
}
if (qmlMarkerProps.outline_width) {
markSymbolizer.strokeWidth = parseFloat(qmlMarkerProps.outline_width);
}
return markSymbolizer;
}
/**
* Get the GeoStyler-Style IconSymbolizer from an QML Symbolizer
*
* @param {object} qmlSymbolizer The QML Symbolizer
* @return {LineSymbolizer} The GeoStyler-Style LineSymbolizer
*/
getLineSymbolizersFromQmlSymbolizer(qmlSymbolizer) {
const { qmlSymbolizerLayerPropsToObject } = this;
return qmlSymbolizer.layer.map((symbolizerLayer) => {
let lineSymbolizer = {
kind: 'Line',
};
const qmlMarkerProps = qmlSymbolizerLayerPropsToObject(symbolizerLayer);
if (qmlMarkerProps.line_color) {
lineSymbolizer.opacity = this.qmlColorToOpacity(qmlMarkerProps.line_color);
lineSymbolizer.color = this.qmlColorToHex(qmlMarkerProps.line_color);
}
if (qmlMarkerProps.capstyle) {
lineSymbolizer.cap = qmlMarkerProps.capstyle;
}
if (qmlMarkerProps.joinstyle) {
lineSymbolizer.join = qmlMarkerProps.joinstyle;
}
if (!qmlMarkerProps.use_custom_dash || qmlMarkerProps.use_custom_dash === '0') {
const lineStyle = qmlMarkerProps?.line_style || 'solid';
const lineDashArray = lineStyleDashArrays[lineStyle];
if (lineDashArray) {
lineSymbolizer.dasharray = lineDashArray;
}
}
else {
if (qmlMarkerProps.customdash) {
lineSymbolizer.dasharray = qmlMarkerProps.customdash.split(';').map(parseFloat);
}
}
if (qmlMarkerProps.offset) {
lineSymbolizer.perpendicularOffset = parseFloat(qmlMarkerProps.offset);
}
if (qmlMarkerProps.line_width) {
lineSymbolizer.width = parseFloat(qmlMarkerProps.line_width);
}
return lineSymbolizer;
});
}
/**
* Get the GeoStyler-Style IconSymbolizer from an QML Symbolizer
*
* @param {object} qmlSymbolizer The QML Symbolizer
* @return {FillSymbolizer} The GeoStyler-Style FillSymbolizer
*/
getFillSymbolizerFromQmlSymbolizer(qmlSymbolizer) {
const { qmlSymbolizerLayerPropsToObject } = this;
return qmlSymbolizer.layer.map((symbolizerLayer) => {
let fillSymbolizer = {
kind: 'Fill',
};
const qmlMarkerProps = qmlSymbolizerLayerPropsToObject(symbolizerLayer);
const outlineStyle = qmlMarkerProps?.outline_style || 'solid';
if (qmlMarkerProps.outline_color && 'no' !== outlineStyle) {
fillSymbolizer.outlineColor = this.qmlColorToHex(qmlMarkerProps.outline_color);
}
let fillStyle = qmlMarkerProps?.style || 'solid';
if (qmlMarkerProps.color && 'solid' === fillStyle) {
fillSymbolizer.opacity = this.qmlColorToOpacity(qmlMarkerProps.color);
fillSymbolizer.color = this.qmlColorToHex(qmlMarkerProps.color);
}
const outlineDashArray = lineStyleDashArrays[outlineStyle];
if (outlineDashArray) {
fillSymbolizer.outlineDasharray = outlineDashArray;
}
if (qmlMarkerProps.outline_width && 'no' !== outlineStyle) {
fillSymbolizer.outlineWidth = parseFloat(qmlMarkerProps.outline_width);
}
return fillSymbolizer;
});
}
/**
* Get the GeoStyler-Style IconSymbolizer from an QML Symbolizer
*
* @param {object} symbolizerLayer The QML Symbolizer Layer
* @return {IconSymbolizer} The GeoStyler-Style IconSymbolizer
*/
getPointSymbolizerFromSvgLayer(symbolizerLayer) {
let iconSymbolizer = {
kind: 'Icon',
};
const qmlMarkerProps = this.qmlSymbolizerLayerPropsToObject(symbolizerLayer);
if (qmlMarkerProps.color) {
const colorArray = qmlMarkerProps.color.split(',');
iconSymbolizer.opacity = parseFloat(colorArray[3]) / 255;
const color = Color(`rgb(${colorArray[0]},${colorArray[1]},${colorArray[2]})`);
iconSymbolizer.color = color.hex();
}
if (qmlMarkerProps.angle) {
iconSymbolizer.rotate = parseFloat(qmlMarkerProps.angle);
}
if (qmlMarkerProps.size) {
iconSymbolizer.size = parseFloat(qmlMarkerProps.size);
}
if (qmlMarkerProps.offset) {
const offsetArray = qmlMarkerProps.offset.split(',').map(parseFloat);
if (offsetArray[0] > 0 || offsetArray[1] > 0) {
iconSymbolizer.offset = qmlMarkerProps.offset.split(',').map(parseFloat);
}
}
if (qmlMarkerProps.name) {
iconSymbolizer.image = qmlMarkerProps.name;
}
return iconSymbolizer;
}
/**
* The writeStyle implementation of the GeoStyler-Style StyleParser interface.
* It reads a GeoStyler-Style Style and returns a Promise containing
* the QML string.
*
* @param {Style} geoStylerStyle A GeoStyler-Style Style.
* @return {Promise} The Promise resolving with the QML.
*/
writeStyle(geoStylerStyle) {
return new Promise(resolve => {
try {
const builder = new Builder();
const qmlObject = this.geoStylerStyleToQmlObject(geoStylerStyle);
this.convertTextSymbolizers(qmlObject, geoStylerStyle);
const qmlString = builder
.buildObject(qmlObject)
.replace('', ' {
const symbol = this.getQmlSymbolFromRule(rule, index);
if (symbol) {
rules.push(this.getQmlRuleFromRule(rule, index));
}
return symbol;
}).filter(s => s);
}
/**
*
* @param rule
*/
getQmlSymbolFromRule(rule, index) {
const layer = this.getQmlLayersFromRule(rule);
let type;
if (!rule.symbolizers.length) {
return;
}
switch (rule.symbolizers[0].kind) {
case 'Line':
type = 'line';
break;
case 'Fill':
type = 'fill';
break;
default:
type = 'marker';
}
return layer && layer[0] ? {
$: {
type,
name: index.toString()
},
layer
} : undefined;
}
/**
*
*/
getQmlLineSymbolFromSymbolizer(symbolizer) {
let lineColor;
if (!isGeoStylerFunction(symbolizer.color) && !isGeoStylerFunction(symbolizer.opacity)) {
lineColor = this.qmlColorFromHexAndOpacity(symbolizer.color, symbolizer.opacity);
}
const qmlProps = {
line_color: lineColor,
offset: symbolizer.perpendicularOffset,
offset_map_unit_scale: '3x:0,0,0,0,0,0',
offset_unit: 'Pixel',
joinstyle: symbolizer.join,
capstyle: symbolizer.cap,
line_width: symbolizer.width,
line_width_unit: 'Pixel'
};
if (symbolizer.dasharray) {
qmlProps.customdash = symbolizer.dasharray.join(';');
qmlProps.use_custom_dash = '1';
}
return {
$: {
class: 'SimpleLine'
},
...this.propsObjectToQmlSymbolOptions(qmlProps)
};
}
getQmlFillSymbolFromSymbolizer(symbolizer) {
const fillOpacity = symbolizer.fillOpacity !== undefined ? symbolizer.fillOpacity : symbolizer.opacity;
const outlineOpacity = symbolizer.outlineOpacity !== undefined ? symbolizer.outlineOpacity : symbolizer.opacity;
let color;
if (!isGeoStylerFunction(symbolizer.color) && !isGeoStylerFunction(fillOpacity)) {
color = this.qmlColorFromHexAndOpacity(symbolizer.color, fillOpacity);
}
let outlineColor;
if (!isGeoStylerFunction(symbolizer.outlineColor) && !isGeoStylerFunction(outlineOpacity)) {
outlineColor = this.qmlColorFromHexAndOpacity(symbolizer.outlineColor, outlineOpacity);
}
const qmlProps = {
color: color,
offset_map_unit_scale: '3x:0,0,0,0,0,0',
offset_unit: 'Pixel',
outline_style: symbolizer.outlineDasharray ? 'dash' : 'solid',
outline_width: symbolizer.outlineWidth || '0',
outline_width_map_unit_scale: '3x:0,0,0,0,0,0',
outline_width_unit: 'Pixel',
customdash: symbolizer.outlineDasharray ? symbolizer.outlineDasharray.join(';') : undefined,
outline_color: outlineColor
};
return {
$: {
class: 'SimpleFill'
},
...this.propsObjectToQmlSymbolOptions(qmlProps)
};
}
/**
*
* @param rule
*/
getQmlLayersFromRule(rule) {
const symbolizers = rule.symbolizers.map(this.getQmlLayerFromSymbolizer.bind(this)).filter(s => s);
return symbolizers.length ? symbolizers : undefined;
}
/**
*
* @param symbolizer
*/
getQmlLayerFromSymbolizer(symbolizer) {
switch (symbolizer.kind) {
case 'Fill':
return this.getQmlFillSymbolFromSymbolizer(symbolizer);
case 'Icon':
return this.getQmlMarkSymbolFromIconSymbolizer(symbolizer);
case 'Line':
return this.getQmlLineSymbolFromSymbolizer(symbolizer);
case 'Mark':
return this.getQmlMarkSymbolFromSymbolizer(symbolizer);
default:
break;
}
}
getQmlMarkSymbolFromIconSymbolizer(symbolizer) {
let color;
if (!isGeoStylerFunction(symbolizer.color) && !isGeoStylerFunction(symbolizer.opacity)) {
color = this.qmlColorFromHexAndOpacity(symbolizer.color, symbolizer.opacity);
}
const qmlProps = {
angle: symbolizer.rotate || 0,
color: color,
name: symbolizer.image,
size: symbolizer.size,
size_map_unit_scale: '3x:0,0,0,0,0,0',
size_unit: 'Pixel'
};
return {
$: {
class: 'SvgMarker'
},
...this.propsObjectToQmlSymbolOptions(qmlProps)
};
}
/**
*
*/
getQmlMarkSymbolFromSymbolizer(symbolizer) {
let color;
if (!isGeoStylerFunction(symbolizer.color) && !isGeoStylerFunction(symbolizer.opacity)) {
color = this.qmlColorFromHexAndOpacity(symbolizer.color, symbolizer.opacity);
}
let outlineColor;
if (!isGeoStylerFunction(symbolizer.strokeColor) && !isGeoStylerFunction(symbolizer.strokeOpacity)) {
outlineColor = this.qmlColorFromHexAndOpacity(symbolizer.strokeColor, symbolizer.strokeOpacity);
}
let size;
if (!isGeoStylerFunction(symbolizer.radius)) {
size = symbolizer.radius ? symbolizer.radius * 2 : undefined;
}
const qmlProps = {
angle: symbolizer.rotate || 0,
color: color,
name: symbolizer.wellKnownName.toLowerCase(),
outline_color: outlineColor,
outline_style: 'solid',
outline_width: symbolizer.strokeWidth || 0,
outline_width_map_unit_scale: '3x:0,0,0,0,0,0',
outline_width_unit: 'Pixel',
size: size,
size_map_unit_scale: '3x:0,0,0,0,0,0',
size_unit: 'Pixel'
};
return {
$: {
class: 'SimpleMarker'
},
...this.propsObjectToQmlSymbolOptions(qmlProps)
};
}
/**
* @param {object} properties Object containing key-value pairs to serialize as QML.
* @return an XML object representing either <Option type="map"><repeated Option /></Option> or <repeated prop... />
*/
propsObjectToQmlSymbolOptions(properties) {
return this.writePropsAsOptions
? this.propsObjectToQmlSymbol_NewerOptions(properties)
: this.propsObjectToQmlSymbol_OlderProps(properties);
}
propsObjectToQmlSymbol_NewerOptions(properties) {
const options = Object.keys(properties).map(k => {
const v = properties[k];
return {
$: {
name: k,
value: v,
type: 'QString'
}
};
}).filter(o => o.$.value !== undefined);
return {
Option: {
$: {
type: 'Map'
},
Option: options
}
};
}
propsObjectToQmlSymbol_OlderProps(properties) {
const props = Object.keys(properties).map(k => {
const v = properties[k];
return {
$: {
k: k,
v: v,
}
};
}).filter(p => p.$.v !== undefined);
return { prop: props };
}
/**
* Get the QML Object (readable with xml2js) from an GeoStyler-Style Style
*
* @param {Style} geoStylerStyle A GeoStyler-Style Style.
* @return {object} The object representation of a QML Style (readable with xml2js)
*/
geoStylerStyleToQmlObject(geoStylerStyle) {
const type = 'RuleRenderer';
const rules = [];
const symbols = this.getQmlSymbolsFromStyle(geoStylerStyle, rules);
if (rules.length > 0 || symbols.length > 0) {
return {
qgis: {
$: {
version: this.qgisVersion
},
'renderer-v2': [{
$: {
type
},
rules: [{
$: {
key: 'renderer_rules'
},
rule: rules
}],
symbols: [{
symbol: symbols
}]
}]
}
};
}
else {
return {
qgis: {
$: {
version: this.qgisVersion
},
'renderer-v2': [{
$: {
type: 'nullSymbol'
}
}]
}
};
}
}
convertTextSymbolizerRule(qmlRuleList, rule) {
let textSymbolizer;
rule.symbolizers.forEach(symbolizer => {
if (symbolizer.kind === 'Text') {
textSymbolizer = symbolizer;
let textColor;
let predefinedPositionOrder;
if (textSymbolizer.color && !isGeoStylerFunction(textSymbolizer.color)) {
textColor = this.qmlColorFromHexAndOpacity(textSymbolizer.color, 1);
}
const textStyleAttributes = {
fontSize: textSymbolizer.size || 12,
fontLetterSpacing: textSymbolizer.letterSpacing || 0,
multilineHeight: textSymbolizer.lineHeight !== undefined ? textSymbolizer.lineHeight : 1,
textColor: textSymbolizer.color ? textColor : '0,0,0,255'
};
if (textSymbolizer.font) {
textStyleAttributes.fontFamily = textSymbolizer.font[0];
}
if (textSymbolizer.label && !isGeoStylerFunction(textSymbolizer.label)) {
textStyleAttributes.fieldName = textSymbolizer.label.replace('{{', '').replace('}}', '');
}
if (textSymbolizer.anchor
&& !isGeoStylerFunction(textSymbolizer.anchor)
&& textSymbolizer.anchor !== 'center') {
predefinedPositionOrder = AnchorMap[textSymbolizer.anchor];
}
else {
predefinedPositionOrder = 'TR,TL,BR,BL,R,L,TSR,BSR';
}
const textRule = {
$: {
key: `labeling_rule_${qmlRuleList.length}`
},
settings: [{
'text-style': [{
$: textStyleAttributes
}],
placement: [{
$: {
predefinedPositionOrder,
xOffset: textSymbolizer.offset ? `${textSymbolizer.offset[0]}` : '0',
yOffset: textSymbolizer.offset ? `${textSymbolizer.offset[1]}` : '0',
rotationAngle: textSymbolizer.rotate ? textSymbolizer.rotate : '0'
}
}]
}]
};
if (textSymbolizer.haloColor) {
let bufferColor;
if (!isGeoStylerFunction(textSymbolizer.haloColor) && !isGeoStylerFunction(textSymbolizer.haloOpacity)) {
bufferColor = this.qmlColorFromHexAndOpacity(textSymbolizer.haloColor, textSymbolizer.haloOpacity);
}
textRule.settings[0]['text-buffer'] = [{
$: {
bufferSize: textSymbolizer.haloWidth || '0',
bufferColor: bufferColor,
bufferDraw: 1,
bufferSizeUnits: 'Pixel',
bufferSizeMapUnitScale: '3x:0,0,0,0,0,0'
}
}];
}
if (rule.filter) {
textRule.$.filter = this.cqlParser.write(rule.filter);
}
qmlRuleList.push(textRule);
}
});
}
convertTextSymbolizers(qmlObject, geoStylerStyle) {
const textSymbolizerRules = [];
geoStylerStyle.rules.forEach(rule => {
rule.symbolizers.forEach(symbolizer => {
if (symbolizer.kind === 'Text' && !textSymbolizerRules.includes(rule)) {
textSymbolizerRules.push(rule);
}
});
});
if (textSymbolizerRules.length > 0) {
qmlObject.qgis.labeling = [{
$: {
type: 'rule-based'
},
rules: [{
$: {
key: 'labeling_rules'
},
rule: []
}]
}];
textSymbolizerRules.forEach(rule => this.convertTextSymbolizerRule(qmlObject.qgis.labeling[0].rules[0].rule, rule));
}
}
}
export default QGISStyleParser;
//# sourceMappingURL=QGISStyleParser.js.map