From 7a77793d697fa5c8c07497cd1b9c78f20655bb45 Mon Sep 17 00:00:00 2001
From: mike-000 <49240900+mike-000@users.noreply.github.com>
Date: Tue, 11 Feb 2020 14:58:57 +0000
Subject: [PATCH] Write fill and outline in PolyStyle
Write styles based on style objects appropriate for geometry.
Write fill and outline in PolyStyle if false (i.e. non-default)
Handle MultiLineString, MultiPoint and MultiPolygon within heterogenous MultiGeometry when writing features
Add getGeometriesArrayRecursive method to ol/geom/GeometryCollection to allow for nested MultiGeometry
Enhanced write GeometryCollection geometries test
A more rigorous write GeometryCollection geometries test including nested collections (the output is simplified to a single MultiGeomtry)
Add writeFeatures to outline and fill tests, setting geometry for geometry specific tests
Add 0 and 0 to some existing tests
---
src/ol/format/KML.js | 173 ++++++++++++++++++-------
src/ol/geom/GeometryCollection.js | 17 +++
test/spec/ol/format/kml.test.js | 205 +++++++++++++++++++++++++++++-
3 files changed, 344 insertions(+), 51 deletions(-)
diff --git a/src/ol/format/KML.js b/src/ol/format/KML.js
index d94a4c473b..68506c466e 100644
--- a/src/ol/format/KML.js
+++ b/src/ol/format/KML.js
@@ -918,11 +918,7 @@ function createNameStyleFunction(foundStyle, name) {
const nameStyle = new Style({
image: imageStyle,
- text: textStyle,
- // although nameStyle will be used only for Point geometries
- // fill and stroke are included to assist writing of MultiGeometry styles
- fill: foundStyle.getFill(),
- stroke: foundStyle.getStroke()
+ text: textStyle
});
return nameStyle;
}
@@ -953,7 +949,7 @@ function createFeatureStyleFunction(style, styleUrl, defaultStyle, sharedStyles,
if (geometry) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
- multiGeometryPoints = geometry.getGeometriesArray().filter(function(geometry) {
+ multiGeometryPoints = geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
});
@@ -1806,7 +1802,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
- geometry.getGeometriesArray().filter(function(geometry) {
+ geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type !== GeometryType.POLYGON && type !== GeometryType.MULTI_POLYGON;
})
@@ -1827,7 +1823,7 @@ function readStyle(node, objectStack) {
const type = geometry.getType();
if (type === GeometryType.GEOMETRY_COLLECTION) {
return new GeometryCollection(
- geometry.getGeometriesArray().filter(function(geometry) {
+ geometry.getGeometriesArrayRecursive().filter(function(geometry) {
const type = geometry.getType();
return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
})
@@ -2703,20 +2699,35 @@ function writeMultiGeometry(node, geometry, objectStack) {
const context = {node: node};
const type = geometry.getType();
/** @type {Array} */
- let geometries;
+ let geometries = [];
/** @type {function(*, Array<*>, string=): (Node|undefined)} */
let factory;
- if (type == GeometryType.GEOMETRY_COLLECTION) {
- geometries = /** @type {GeometryCollection} */ (geometry).getGeometries();
+ if (type === GeometryType.GEOMETRY_COLLECTION) {
+ /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().forEach(function(geometry) {
+ const type = geometry.getType();
+ if (type === GeometryType.MULTI_POINT) {
+ geometries = geometries.concat(/** @type {MultiPoint} */ (geometry).getPoints());
+ } else if (type === GeometryType.MULTI_LINE_STRING) {
+ geometries = geometries.concat(/** @type {MultiLineString} */ (geometry).getLineStrings());
+ } else if (type === GeometryType.MULTI_POLYGON) {
+ geometries = geometries.concat(/** @type {MultiPolygon} */ (geometry).getPolygons());
+ } else if (type === GeometryType.POINT
+ || type === GeometryType.LINE_STRING
+ || type === GeometryType.POLYGON) {
+ geometries.push(geometry);
+ } else {
+ assert(false, 39); // Unknown geometry type
+ }
+ });
factory = GEOMETRY_NODE_FACTORY;
- } else if (type == GeometryType.MULTI_POINT) {
+ } else if (type === GeometryType.MULTI_POINT) {
geometries = /** @type {MultiPoint} */ (geometry).getPoints();
factory = POINT_NODE_FACTORY;
- } else if (type == GeometryType.MULTI_LINE_STRING) {
+ } else if (type === GeometryType.MULTI_LINE_STRING) {
geometries =
(/** @type {MultiLineString} */ (geometry)).getLineStrings();
factory = LINE_STRING_NODE_FACTORY;
- } else if (type == GeometryType.MULTI_POLYGON) {
+ } else if (type === GeometryType.MULTI_POLYGON) {
geometries =
(/** @type {MultiPolygon} */ (geometry)).getPolygons();
factory = POLYGON_NODE_FACTORY;
@@ -2831,13 +2842,61 @@ function writePlacemark(node, feature, objectStack) {
// resolution-independent here
const styles = styleFunction(feature, 0);
if (styles) {
- const style = Array.isArray(styles) ? styles[0] : styles;
- if (this.writeStyles_) {
- properties['Style'] = style;
+ const styleArray = Array.isArray(styles) ? styles : [styles];
+ let pointStyles = styleArray;
+ if (feature.getGeometry()) {
+ pointStyles = styleArray.filter(function(style) {
+ const geometry = style.getGeometryFunction()(feature);
+ if (geometry) {
+ const type = geometry.getType();
+ if (type === GeometryType.GEOMETRY_COLLECTION) {
+ return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
+ const type = geometry.getType();
+ return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
+ }).length;
+ }
+ return type === GeometryType.POINT || type === GeometryType.MULTI_POINT;
+ }
+ });
}
- const textStyle = style.getText();
- if (textStyle) {
- properties['name'] = textStyle.getText();
+ if (this.writeStyles_) {
+ let lineStyles = styleArray;
+ let polyStyles = styleArray;
+ if (feature.getGeometry()) {
+ lineStyles = styleArray.filter(function(style) {
+ const geometry = style.getGeometryFunction()(feature);
+ if (geometry) {
+ const type = geometry.getType();
+ if (type === GeometryType.GEOMETRY_COLLECTION) {
+ return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
+ const type = geometry.getType();
+ return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
+ }).length;
+ }
+ return type === GeometryType.LINE_STRING || type === GeometryType.MULTI_LINE_STRING;
+ }
+ });
+ polyStyles = styleArray.filter(function(style) {
+ const geometry = style.getGeometryFunction()(feature);
+ if (geometry) {
+ const type = geometry.getType();
+ if (type === GeometryType.GEOMETRY_COLLECTION) {
+ return /** @type {GeometryCollection} */ (geometry).getGeometriesArrayRecursive().filter(function(geometry) {
+ const type = geometry.getType();
+ return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
+ }).length;
+ }
+ return type === GeometryType.POLYGON || type === GeometryType.MULTI_POLYGON;
+ }
+ });
+ }
+ properties['Style'] = {pointStyles: pointStyles, lineStyles: lineStyles, polyStyles: polyStyles};
+ }
+ if (pointStyles.length && properties['name'] === undefined) {
+ const textStyle = pointStyles[0].getText();
+ if (textStyle) {
+ properties['name'] = textStyle.getText();
+ }
}
}
}
@@ -2913,6 +2972,17 @@ function writePrimitiveGeometry(node, geometry, objectStack) {
}
+/**
+ * @const
+ * @type {Object>}
+ */
+// @ts-ignore
+const POLY_STYLE_SEQUENCE = makeStructureNS(
+ NAMESPACE_URIS, [
+ 'color', 'fill', 'outline'
+ ]);
+
+
/**
* @const
* @type {Object>}
@@ -2972,27 +3042,31 @@ function writePolygon(node, polygon, objectStack) {
// @ts-ignore
const POLY_STYLE_SERIALIZERS = makeStructureNS(
NAMESPACE_URIS, {
- 'color': makeChildAppender(writeColorTextNode)
+ 'color': makeChildAppender(writeColorTextNode),
+ 'fill': makeChildAppender(writeBooleanTextNode),
+ 'outline': makeChildAppender(writeBooleanTextNode)
});
-/**
- * A factory for creating coordinates nodes.
- * @const
- * @type {function(*, Array<*>, string=): (Node|undefined)}
- */
-const COLOR_NODE_FACTORY = makeSimpleNodeFactory('color');
-
-
/**
* @param {Node} node Node.
- * @param {Fill} style Style.
+ * @param {Style} style Style.
* @param {Array<*>} objectStack Object stack.
*/
function writePolyStyle(node, style, objectStack) {
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
+ const fill = style.getFill();
+ const stroke = style.getStroke();
+ const properties = {
+ 'color': fill ? fill.getColor() : undefined,
+ 'fill': fill ? undefined : false,
+ 'outline': stroke ? undefined : false
+ };
+ const parentNode = objectStack[objectStack.length - 1].node;
+ const orderedKeys = POLY_STYLE_SEQUENCE[parentNode.namespaceURI];
+ const values = makeSequence(properties, orderedKeys);
pushSerializeAndPop(context, POLY_STYLE_SERIALIZERS,
- COLOR_NODE_FACTORY, [style.getColor()], objectStack);
+ OBJECT_PROPERTY_NODE_FACTORY, values, objectStack, orderedKeys);
}
@@ -3034,27 +3108,34 @@ const STYLE_SERIALIZERS = makeStructureNS(
/**
* @param {Node} node Node.
- * @param {Style} style Style.
+ * @param {Object>} styles Styles.
* @param {Array<*>} objectStack Object stack.
*/
-function writeStyle(node, style, objectStack) {
+function writeStyle(node, styles, objectStack) {
const /** @type {import("../xml.js").NodeStackItem} */ context = {node: node};
const properties = {};
- const fillStyle = style.getFill();
- const strokeStyle = style.getStroke();
- const imageStyle = style.getImage();
- const textStyle = style.getText();
- if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
- properties['IconStyle'] = imageStyle;
+ if (styles.pointStyles.length) {
+ const textStyle = styles.pointStyles[0].getText();
+ if (textStyle) {
+ properties['LabelStyle'] = textStyle;
+ }
+ const imageStyle = styles.pointStyles[0].getImage();
+ if (imageStyle && typeof /** @type {?} */ (imageStyle).getSrc === 'function') {
+ properties['IconStyle'] = imageStyle;
+ }
}
- if (textStyle) {
- properties['LabelStyle'] = textStyle;
+ if (styles.lineStyles.length) {
+ const strokeStyle = styles.lineStyles[0].getStroke();
+ if (strokeStyle) {
+ properties['LineStyle'] = strokeStyle;
+ }
}
- if (strokeStyle) {
- properties['LineStyle'] = strokeStyle;
- }
- if (fillStyle) {
- properties['PolyStyle'] = fillStyle;
+ if (styles.polyStyles.length) {
+ const strokeStyle = styles.polyStyles[0].getStroke();
+ if (strokeStyle && !properties['LineStyle']) {
+ properties['LineStyle'] = strokeStyle;
+ }
+ properties['PolyStyle'] = styles.polyStyles[0];
}
const parentNode = objectStack[objectStack.length - 1].node;
const orderedKeys = STYLE_SEQUENCE[parentNode.namespaceURI];
diff --git a/src/ol/geom/GeometryCollection.js b/src/ol/geom/GeometryCollection.js
index e1b63d76dd..1d407f2fdc 100644
--- a/src/ol/geom/GeometryCollection.js
+++ b/src/ol/geom/GeometryCollection.js
@@ -126,6 +126,23 @@ class GeometryCollection extends Geometry {
return this.geometries_;
}
+ /**
+ * @return {Array} Geometries.
+ */
+ getGeometriesArrayRecursive() {
+ /** @type {Array} */
+ let geometriesArray = [];
+ const geometries = this.geometries_;
+ for (let i = 0, ii = geometries.length; i < ii; ++i) {
+ if (geometries[i].getType() === this.getType()) {
+ geometriesArray = geometriesArray.concat(/** @type {GeometryCollection} */ (geometries[i]).getGeometriesArrayRecursive());
+ } else {
+ geometriesArray.push(geometries[i]);
+ }
+ }
+ return geometriesArray;
+ }
+
/**
* @inheritDoc
*/
diff --git a/test/spec/ol/format/kml.test.js b/test/spec/ol/format/kml.test.js
index e1a248bb75..60d235387f 100644
--- a/test/spec/ol/format/kml.test.js
+++ b/test/spec/ol/format/kml.test.js
@@ -1207,9 +1207,16 @@ describe('ol.format.KML', function() {
it('can write GeometryCollection geometries', function() {
const collection = new GeometryCollection([
- new Point([1, 2]),
- new LineString([[1, 2], [3, 4]]),
- new Polygon([[[1, 2], [3, 4], [3, 2], [1, 2]]])
+ new GeometryCollection([
+ new Point([1, 2]),
+ new LineString([[1, 2], [3, 4]]),
+ new Polygon([[[1, 2], [3, 4], [3, 2], [1, 2]]])
+ ]),
+ new GeometryCollection([
+ new MultiPoint([[5, 6], [9, 10]]),
+ new MultiLineString([[[5, 6], [7, 8]], [[9, 10], [11, 12]]]),
+ new MultiPolygon([[[[5, 6], [7, 8], [7, 6], [5, 6]]], [[[9, 10], [11, 12], [11, 10], [9, 10]]]])
+ ])
]);
const features = [new Feature(collection)];
const node = format.writeFeaturesNode(features);
@@ -1234,6 +1241,32 @@ describe('ol.format.KML', function() {
' ' +
' ' +
' ' +
+ ' ' +
+ ' 5,6' +
+ ' ' +
+ ' ' +
+ ' 9,10' +
+ ' ' +
+ ' ' +
+ ' 5,6 7,8' +
+ ' ' +
+ ' ' +
+ ' 9,10 11,12' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 5,6 7,8 7,6 5,6' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 9,10 11,12 11,10 9,10' +
+ ' ' +
+ ' ' +
+ ' ' +
' ' +
' ' +
'';
@@ -1621,6 +1654,9 @@ describe('ol.format.KML', function() {
' ff332211' +
' 2' +
' ' +
+ ' ' +
+ ' 0' +
+ ' ' +
' ' +
' ' +
' ' +
@@ -2164,6 +2200,43 @@ describe('ol.format.KML', function() {
expect(strokeStyle.getWidth()).to.be(9);
expect(style.getText()).to.be(getDefaultTextStyle());
expect(style.getZIndex()).to.be(undefined);
+
+ const lineString = new LineString([[1, 2], [3, 4]]);
+ const polygon = new Polygon([[[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]]);
+ const collection = new GeometryCollection([lineString, polygon]);
+ f.setGeometry(collection);
+ const node = format.writeFeaturesNode(fs);
+ const text1 =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 1,2 3,4' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 0,0 0,2 2,2 2,0 0,0' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(parse(text1));
});
it('disables the stroke when outline is \'0\'', function() {
@@ -2233,6 +2306,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(fillStyle);
expect(style1.getStroke()).to.be(null);
expect(style1.getZIndex()).to.be(undefined);
+
+ f.setGeometry(collectionFeature.getGeometry());
+ const node = format.writeFeaturesNode(fs);
+ const text1 =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 1,2 3,4' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 0,0 0,2 2,2 2,0 0,0' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(parse(text1));
});
it('disables both fill and stroke when fill and outline are \'0\'',
@@ -2302,6 +2410,41 @@ describe('ol.format.KML', function() {
expect(style1.getFill()).to.be(null);
expect(style1.getStroke()).to.be(null);
expect(style1.getZIndex()).to.be(undefined);
+
+ f.setGeometry(collectionFeature.getGeometry());
+ const node = format.writeFeaturesNode(fs);
+ const text1 =
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 1,2 3,4' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 0,0 0,2 2,2 2,0 0,0' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(parse(text1));
});
it('can create text style for named point placemarks (including html character codes)', function() {
@@ -2392,6 +2535,10 @@ describe('ol.format.KML', function() {
' ' +
' ' +
+ ' ' +
+ ' 0' +
+ ' 0' +
+ ' ' +
' ' +
' ' +
'';
@@ -2440,6 +2587,10 @@ describe('ol.format.KML', function() {
' https://developers.google.com/kml/schema/kml22gx.xsd">' +
' ' +
' ' +
' ' +
'';
@@ -2472,13 +2623,17 @@ describe('ol.format.KML', function() {
' ffdf220c' +
' 0.5' +
' ' +
+ ' ' +
+ ' 0' +
+ ' 0' +
+ ' ' +
' ' +
' ' +
'';
expect(node).to.xmleql(parse(text));
});
- it('can write an feature\'s stroke style', function() {
+ it('can write an feature\'s stroke style without fill', function() {
const style = new Style({
stroke: new Stroke({
color: '#112233',
@@ -2500,13 +2655,16 @@ describe('ol.format.KML', function() {
' ff332211' +
' 2' +
' ' +
+ ' ' +
+ ' 0' +
+ ' ' +
' ' +
' ' +
'';
expect(node).to.xmleql(parse(text));
});
- it('can write an feature\'s fill style', function() {
+ it('can write an feature\'s fill style without outline', function() {
const style = new Style({
fill: new Fill({
color: 'rgba(12, 34, 223, 0.7)'
@@ -2525,6 +2683,41 @@ describe('ol.format.KML', function() {
' ' +
+ ' ' +
+ '';
+ expect(node).to.xmleql(parse(text));
+ });
+
+ it('can write an feature\'s fill style and outline', function() {
+ const style = new Style({
+ fill: new Fill({
+ color: 'rgba(12, 34, 223, 0.7)'
+ }),
+ stroke: new Stroke({
+ color: '#112233',
+ width: 2
+ })
+ });
+ const feature = new Feature();
+ feature.setStyle([style]);
+ const node = format.writeFeaturesNode([feature]);
+ const text =
+ '' +
+ ' ' +
+ ' ' +
' ' +
@@ -2554,6 +2747,7 @@ describe('ol.format.KML', function() {
' ' +
' ' +
@@ -2561,6 +2755,7 @@ describe('ol.format.KML', function() {
' ' +
' ' +