Files
openlayers/src/ol/parser/kml.js
Tim Schaub 5d0b4563a8 Optionally stroke and fill polygons (closes #475)
This also removes the unsupported width property from PolyStyle (closes #891).
2013-08-05 11:16:49 -06:00

1102 lines
39 KiB
JavaScript

goog.provide('ol.parser.KML');
goog.require('goog.array');
goog.require('goog.async.Deferred');
goog.require('goog.async.DeferredList');
goog.require('goog.date');
goog.require('goog.dispose');
goog.require('goog.dom.xml');
goog.require('goog.events');
goog.require('goog.net.EventType');
goog.require('goog.net.XhrIo');
goog.require('goog.object');
goog.require('goog.string');
goog.require('ol.Feature');
goog.require('ol.geom.AbstractCollection');
goog.require('ol.geom.GeometryCollection');
goog.require('ol.geom.GeometryType');
goog.require('ol.geom.LineString');
goog.require('ol.geom.MultiLineString');
goog.require('ol.geom.MultiPoint');
goog.require('ol.geom.MultiPolygon');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.geom.SharedVertices');
goog.require('ol.parser.AsyncObjectFeatureParser');
goog.require('ol.parser.AsyncStringFeatureParser');
goog.require('ol.parser.DomFeatureParser');
goog.require('ol.parser.ReadFeaturesOptions');
goog.require('ol.parser.StringFeatureParser');
goog.require('ol.parser.XML');
goog.require('ol.style.Icon');
goog.require('ol.style.Line');
goog.require('ol.style.LineLiteral');
goog.require('ol.style.Polygon');
goog.require('ol.style.PolygonLiteral');
/**
* @constructor
* @implements {ol.parser.DomFeatureParser}
* @implements {ol.parser.StringFeatureParser}
* @implements {ol.parser.AsyncObjectFeatureParser}
* @implements {ol.parser.AsyncStringFeatureParser}
* @param {ol.parser.KMLOptions=} opt_options Optional configuration object.
* @extends {ol.parser.XML}
*/
ol.parser.KML = function(opt_options) {
var options = /** @type {ol.parser.KMLOptions} */
(goog.isDef(opt_options) ? opt_options : {});
this.extractAttributes = goog.isDef(options.extractAttributes) ?
options.extractAttributes : true;
this.extractStyles = goog.isDef(options.extractStyles) ?
options.extractStyles : false;
this.schemaLocation = 'http://www.opengis.net/kml/2.2 ' +
'http://schemas.opengis.net/kml/2.2.0/ogckml22.xsd';
// TODO re-evaluate once shared structures support 3D
this.dimension = goog.isDef(options.dimension) ? options.dimension : 3;
this.maxDepth = goog.isDef(options.maxDepth) ? options.maxDepth : 0;
this.trackAttributes = goog.isDef(options.trackAttributes) ?
options.trackAttributes : null;
this.defaultNamespaceURI = 'http://www.opengis.net/kml/2.2';
this.readers = {
'http://www.opengis.net/kml/2.2': {
'kml': function(node, obj) {
if (!goog.isDef(obj.features)) {
obj.features = [];
}
if (!goog.isDef(obj.links)) {
obj.links = [];
}
this.readChildNodes(node, obj);
},
'Document': function(node, obj) {
this.readChildNodes(node, obj);
},
'*': function(node, obj) {
if (this.extractAttributes === true) {
var len = node.childNodes.length;
if ((len === 1 || len === 2) && (node.firstChild.nodeType === 3 ||
node.firstChild.nodeType === 4)) {
var readers = this.readers[this.defaultNamespaceURI];
readers['_attribute'].apply(this, arguments);
}
}
},
'NetworkLink': function(node, obj) {
var link = {};
this.readChildNodes(node, link);
obj.links.push(link);
},
'Link': function(node, obj) {
this.readChildNodes(node, obj);
},
'_attribute': function(node, obj) {
var local = node.localName || node.nodeName.split(':').pop();
var value = this.getChildValue(node);
if (obj.properties) {
obj.properties[local] = value.replace(this.regExes.trimSpace, '');
} else {
obj[local] = value.replace(this.regExes.trimSpace, '');
}
},
'Placemark': function(node, obj) {
var container = {properties: {}};
var sharedVertices, callback;
var id = node.getAttribute('id');
this.readChildNodes(node, container);
if (goog.isDef(container.track)) {
var track = container.track, j, jj;
delete container.track;
for (var i = 0, ii = track.whens.length; i < ii; ++i) {
if (this.trackAttributes) {
for (j = 0, jj = this.trackAttributes.length; j < jj; ++j) {
var name = this.trackAttributes[j];
container.properties[name] = track.attributes[name][i];
}
}
container.properties['when'] = track.whens[i];
if (goog.isDef(track.angles[i])) {
container.properties['heading'] = parseFloat(track.angles[i][0]);
container.properties['tilt'] = parseFloat(track.angles[i][1]);
container.properties['roll'] = parseFloat(track.angles[i][2]);
}
if (track.points[i].coordinates.length === 3) {
container.properties['altitude'] = track.points[i].coordinates[2];
}
var feature = new ol.Feature(container.properties);
if (!goog.isNull(id)) {
feature.setFeatureId(id);
}
var geom = track.points[i];
if (geom) {
sharedVertices = undefined;
if (this.readFeaturesOptions_) {
callback = this.readFeaturesOptions_.callback;
if (callback) {
sharedVertices = callback(feature, geom.type);
}
}
var geometry = this.createGeometry_({geometry: geom},
sharedVertices);
if (goog.isDef(geometry)) {
feature.setGeometry(geometry);
}
}
obj.features.push(feature);
}
} else if (goog.isDef(container.geometry)) {
var styleUrl = container.properties['styleUrl'];
if (goog.isDef(styleUrl)) {
if (!goog.string.startsWith(styleUrl, '#')) {
obj.links.push({href: styleUrl});
}
}
feature = new ol.Feature(container.properties);
if (!goog.isNull(id)) {
feature.setFeatureId(id);
}
if (container.geometry) {
sharedVertices = undefined;
if (this.readFeaturesOptions_) {
callback = this.readFeaturesOptions_.callback;
if (callback) {
sharedVertices = callback(feature, container.geometry.type);
}
}
geometry = this.createGeometry_(container, sharedVertices);
if (goog.isDef(geometry)) {
feature.setGeometry(geometry);
}
}
var symbolizers = undefined;
if (goog.isDef(container.styles)) {
symbolizers = container.styles[0].symbolizers;
}
this.applyStyle_(feature, obj['styles'], symbolizers);
obj.features.push(feature);
}
},
'MultiGeometry': function(node, container) {
var parts = [];
this.readChildNodes(node, parts);
var buckets = goog.array.bucket(parts, function(val) {
return val.type;
});
var obj = {};
if (goog.object.getCount(buckets) === 1) {
// homogeneous collection
var type = goog.object.getAnyKey(buckets);
switch (type) {
case ol.geom.GeometryType.POINT:
obj.geometry = {
type: ol.geom.GeometryType.MULTIPOINT,
parts: parts
};
break;
case ol.geom.GeometryType.LINESTRING:
obj.geometry = {
type: ol.geom.GeometryType.MULTILINESTRING,
parts: parts
};
break;
case ol.geom.GeometryType.POLYGON:
obj.geometry = {
type: ol.geom.GeometryType.MULTIPOLYGON,
parts: parts
};
break;
default:
break;
}
} else {
// mixed collection
obj.geometry = {
type: ol.geom.GeometryType.GEOMETRYCOLLECTION,
parts: parts
};
}
if (goog.isArray(container)) {
// MultiGeometry nested inside another
container.push(obj.geometry);
} else {
container.geometry = obj.geometry;
}
},
'Point': function(node, container) {
var coordinates = [];
this.readChildNodes(node, coordinates);
var point = {
type: ol.geom.GeometryType.POINT,
coordinates: coordinates[0][0]
};
// in the case of a multi geometry this is parts
if (goog.isArray(container)) {
container.push(point);
} else {
container.geometry = point;
}
},
'Polygon': function(node, container) {
var coordinates = [];
this.readChildNodes(node, coordinates);
var polygon = {
type: ol.geom.GeometryType.POLYGON,
coordinates: coordinates
};
// in the case of a multi geometry this is parts
if (goog.isArray(container)) {
container.push(polygon);
} else {
container.geometry = polygon;
}
},
'LineString': function(node, container) {
var coordinates = [];
this.readChildNodes(node, coordinates);
var linestring = {
type: ol.geom.GeometryType.LINESTRING,
coordinates: coordinates[0]
};
// in the case of a multi geometry this is parts
if (goog.isArray(container)) {
container.push(linestring);
} else {
container.geometry = linestring;
}
},
'outerBoundaryIs': function(node, coordinates) {
this.readChildNodes(node, coordinates);
},
'LinearRing': function(node, coordinates) {
this.readChildNodes(node, coordinates);
},
'coordinates': function(node, coordinates) {
var coordstr = this.getChildValue(node);
var reg = this.regExes;
var coords = coordstr.replace(reg.trimSpace, '').split(reg.splitSpace);
var coordArray = [];
for (var i = 0, ii = coords.length; i < ii; i++) {
var array = coords[i].replace(reg.removeSpace, '').split(',');
var pair = [];
var jj = Math.min(array.length, this.dimension);
for (var j = 0; j < jj; j++) {
pair.push(parseFloat(array[j]));
}
coordArray.push(pair);
}
coordinates.push(coordArray);
},
'innerBoundaryIs': function(node, coordinates) {
this.readChildNodes(node, coordinates);
},
'Folder': function(node, obj) {
this.readChildNodes(node, obj);
},
'ExtendedData': function(node, container) {
this.readChildNodes(node, container.properties);
},
'SchemaData': function(node, attributes) {
this.readChildNodes(node, attributes);
},
'SimpleData': function(node, attributes) {
attributes[node.getAttribute('name')] = this.getChildValue(node);
},
'Data': function(node, attributes) {
var data = {};
this.readChildNodes(node, data);
attributes[node.getAttribute('name')] = data['value'];
},
'when': function(node, container) {
var value = this.getChildValue(node);
var split1 = value.split('T');
if (split1.length === 2) {
var split2 = split1[1].split('-');
if (split2.length === 2) {
value += ':00';
}
}
container.whens.push(goog.date.fromIsoString(value).date_);
},
'_trackPointAttribute': function(node, container) {
var name = node.nodeName.split(':').pop();
container.attributes[name].push(this.getChildValue(node));
},
'Style': function(node, obj) {
if (this.extractStyles === true) {
if (!obj['styles']) {
obj['styles'] = [];
}
var style = {'symbolizers': [], 'ids': []};
var id = node.getAttribute('id');
if (!goog.isNull(id)) {
style['id'] = id;
}
this.readChildNodes(node, style);
obj['styles'].push(style);
}
},
'LineStyle': function(node, obj) {
var symbolizer = {};
this.readChildNodes(node, symbolizer);
if (symbolizer.color) {
symbolizer.strokeColor = symbolizer.color.color;
symbolizer.strokeOpacity = symbolizer.color.opacity;
}
if (symbolizer.width) {
symbolizer.strokeWidth = parseFloat(symbolizer.width);
}
delete symbolizer.color;
delete symbolizer.width;
obj['ids'].push(node.getAttribute('id'));
obj['symbolizers'].push(new ol.style.Line(symbolizer));
},
'PolyStyle': function(node, obj) {
var style = {}; // from KML
var symbolizer = {}; // for ol.style.Polygon
this.readChildNodes(node, style);
// check if poly has fill
if (!(style.fill === '0' || style.fill === 'false')) {
if (style.color) {
symbolizer.fillColor = style.color.color;
symbolizer.fillOpacity = style.color.opacity;
} else {
// KML defaults
symbolizer.fillColor = '#ffffff';
symbolizer.fillOpacity = 1;
}
}
// check if poly has stroke
if (!(style.outline === '0' || style.outline === 'false')) {
if (style.color) {
symbolizer.strokeColor = style.color.color;
symbolizer.strokeOpacity = style.color.opacity;
} else {
// KML defaults
symbolizer.strokeColor = '#ffffff';
symbolizer.strokeOpacity = 1;
}
}
obj['ids'].push(node.getAttribute('id'));
obj['symbolizers'].push(new ol.style.Polygon(symbolizer));
},
'fill': function(node, obj) {
obj.fill = this.getChildValue(node);
},
'outline': function(node, obj) {
obj.outline = this.getChildValue(node);
},
'scale': function(node, obj) {
obj.scale = parseFloat(this.getChildValue(node));
},
'Icon': function(node, obj) {
obj.icon = {};
this.readChildNodes(node, obj.icon);
},
'href': function(node, obj) {
obj.href = this.getChildValue(node);
},
'w': function(node, obj) {
obj.w = this.getChildValue(node);
},
'h': function(node, obj) {
obj.h = this.getChildValue(node);
},
'x': function(node, obj) {
obj.x = this.getChildValue(node);
},
'y': function(node, obj) {
obj.y = this.getChildValue(node);
},
'hotSpot': function(node, obj) {
obj.hotSpot = {
x: parseFloat(node.getAttribute('x')),
y: parseFloat(node.getAttribute('y')),
xunits: node.getAttribute('xunits'),
yunits: node.getAttribute('yunits')
};
},
'IconStyle': function(node, obj) {
var symbolizer = {};
this.readChildNodes(node, symbolizer);
var scale = symbolizer.scale || 1;
// set default width and height of icon
var width = 32 * scale;
var height = 32 * scale;
var x, y;
delete symbolizer.scale;
if (goog.isDef(symbolizer.icon)) {
var href = symbolizer.icon.href;
if (goog.isDef(href)) {
var w = symbolizer.icon.w;
var h = symbolizer.icon.h;
// Settings for Google specific icons that are 64x64
// We set the width and height to 64 and halve the
// scale to prevent icons from being too big
var google = 'http://maps.google.com/mapfiles/kml';
if (goog.string.startsWith(href, google) && !goog.isDef(w) &&
!goog.isDef(h)) {
w = 64;
h = 64;
scale = scale / 2;
}
// if only dimension is defined, make sure the
// other one has the same value
w = w || h;
h = h || w;
if (w) {
width = parseInt(w, 10) * scale;
}
if (h) {
height = parseInt(h, 10) * scale;
}
// support for internal icons
// (/root://icons/palette-x.png)
// x and y tell the position on the palette:
// - in pixels
// - starting from the left bottom
// We translate that to a position in the list
// and request the appropriate icon from the
// google maps website
var matches = href.match(this.regExes.kmlIconPalette);
if (matches) {
var palette = matches[1];
var file_extension = matches[2];
x = symbolizer.icon.x;
y = symbolizer.icon.y;
var posX = x ? x / 32 : 0;
var posY = y ? (7 - y / 32) : 7;
var pos = posY * 8 + posX;
href = 'http://maps.google.com/mapfiles/kml/pal' +
palette + '/icon' + pos + file_extension;
}
symbolizer.opacity = 1;
symbolizer.url = href;
}
}
if (goog.isDef(symbolizer.hotSpot)) {
x = symbolizer.hotSpot.x;
y = symbolizer.hotSpot.y;
var xUnits = symbolizer.hotSpot.xunits,
yUnits = symbolizer.hotSpot.yunits;
if (xUnits === 'pixels') {
symbolizer.graphicXOffset = -x * scale;
} else if (xUnits === 'insetPixels') {
symbolizer.graphicXOffset = -width + (x * scale);
} else if (xUnits === 'fraction') {
symbolizer.graphicXOffset = -width * x;
}
if (yUnits == 'pixels') {
symbolizer.graphicYOffset = -height + (y * scale) + 1;
} else if (yUnits == 'insetPixels') {
symbolizer.graphicYOffset = -(y * scale) + 1;
} else if (yUnits == 'fraction') {
symbolizer.graphicYOffset = -height * (1 - y) + 1;
}
}
symbolizer.width = width;
symbolizer.height = height;
delete symbolizer.scale;
delete symbolizer.icon;
delete symbolizer.hotSpot;
obj['ids'].push(node.getAttribute('id'));
obj['symbolizers'].push(new ol.style.Icon(symbolizer));
},
'color': function(node, obj) {
var kmlColor = this.getChildValue(node);
if (kmlColor) {
var matches = kmlColor.match(this.regExes.kmlColor);
if (matches) {
obj.color = {
color: '#' + matches[4] + matches[3] + matches[2],
opacity: parseInt(matches[1], 16) / 255
};
}
}
},
'width': function(node, obj) {
obj.width = this.getChildValue(node);
}
},
'http://www.google.com/kml/ext/2.2': {
'Track': function(node, container) {
container.track = {
whens: [],
points: [],
angles: []
};
if (this.trackAttributes) {
var name;
container.track.attributes = {};
for (var i = 0, ii = this.trackAttributes.length; i < ii; ++i) {
name = this.trackAttributes[i];
container.track.attributes[name] = [];
var readers = this.readers[this.defaultNamespaceURI];
if (!(name in readers)) {
readers[name] = readers['_trackPointAttribute'];
}
}
}
this.readChildNodes(node, container.track);
if (container.track.whens.length !== container.track.points.length) {
throw new Error('gx:Track with unequal number of when (' +
container.track.whens.length + ') and gx:coord (' +
container.track.points.length + ') elements.');
}
var hasAngles = container.track.angles.length > 0;
if (hasAngles && container.track.whens.length !==
container.track.angles.length) {
throw new Error('gx:Track with unequal number of when (' +
container.track.whens.length + ') and gx:angles (' +
container.track.angles.length + ') elements.');
}
},
'coord': function(node, container) {
var str = this.getChildValue(node);
var coords = str.replace(this.regExes.trimSpace, '').split(/\s+/);
for (var i = 0, ii = this.dimension; i < ii; ++i) {
coords[i] = parseFloat(coords[i]);
}
var point = {
type: ol.geom.GeometryType.POINT,
coordinates: coords
};
container.points.push(point);
},
'angles': function(node, container) {
var str = this.getChildValue(node);
var parts = str.replace(this.regExes.trimSpace, '').split(/\s+/);
container.angles.push(parts);
}
}
};
this.writers = {
'http://www.opengis.net/kml/2.2': {
'kml': function(options) {
var node = this.createElementNS('kml');
this.writeNode('Document', options, null, node);
return node;
},
'Document': function(options) {
var node = this.createElementNS('Document');
for (var key in options) {
if (options.hasOwnProperty(key) && goog.isString(options[key])) {
var child = this.createElementNS(key);
child.appendChild(this.createTextNode(options[key]));
node.appendChild(child);
}
}
var i, ii;
if (goog.isDef(options.styles)) {
for (i = 0, ii = options.styles.length; i < ii; ++i) {
this.writeNode('_style', options.styles[i], null, node);
}
}
for (i = 0, ii = options.features.length; i < ii; ++i) {
this.writeNode('_feature', options.features[i], null, node);
}
return node;
},
'_style': function(style) {
var node = this.createElementNS('Style');
if (goog.isDef(style.id)) {
this.setAttributeNS(node, null, 'id', style.id);
}
for (var i = 0, ii = style.symbolizers.length; i < ii; ++i) {
this.writeNode('_symbolizer', {
symbolizer: style.symbolizers[i],
id: style.ids ? style.ids[i] : undefined
}, null, node);
}
return node;
},
'_symbolizer': function(symbolizerObj) {
var symbolizer = symbolizerObj.symbolizer;
if (symbolizer instanceof ol.style.Icon) {
return this.writeNode('IconStyle', symbolizerObj);
} else if (symbolizer instanceof ol.style.Line ||
symbolizer instanceof ol.style.LineLiteral) {
return this.writeNode('LineStyle', symbolizerObj);
} else if (symbolizer instanceof ol.style.Polygon ||
symbolizer instanceof ol.style.PolygonLiteral) {
return this.writeNode('PolyStyle', symbolizerObj);
}
},
'PolyStyle': function(symbolizerObj) {
/**
* There is not a 1:1 mapping between KML PolyStyle and
* ol.style.Polygon. In KML, if a PolyStyle has <outline>1</outline>
* then the "current" LineStyle is used to stroke the polygon.
*/
var node = this.createElementNS('PolyStyle');
if (symbolizerObj.id) {
this.setAttributeNS(node, null, 'id', symbolizerObj.id);
}
var symbolizer = symbolizerObj.symbolizer;
var literal = symbolizer instanceof ol.style.PolygonLiteral ?
symbolizer : symbolizer.createLiteral();
var color, opacity;
if (literal.fillOpacity !== 0) {
this.writeNode('fill', '1', null, node);
color = literal.fillColor;
opacity = literal.fillOpacity;
} else {
this.writeNode('fill', '0', null, node);
}
if (literal.strokeOpacity) {
this.writeNode('outline', '1', null, node);
color = color || literal.strokeColor;
opacity = opacity || literal.strokeOpacity;
} else {
this.writeNode('outline', '0', null, node);
}
if (color && opacity) {
this.writeNode('color', {
color: color.substring(1),
opacity: opacity
}, null, node);
}
return node;
},
'fill': function(fill) {
var node = this.createElementNS('fill');
node.appendChild(this.createTextNode(fill));
return node;
},
'outline': function(outline) {
var node = this.createElementNS('outline');
node.appendChild(this.createTextNode(outline));
return node;
},
'LineStyle': function(symbolizerObj) {
var node = this.createElementNS('LineStyle');
if (symbolizerObj.id) {
this.setAttributeNS(node, null, 'id', symbolizerObj.id);
}
var symbolizer = symbolizerObj.symbolizer;
var literal = symbolizer instanceof ol.style.LineLiteral ?
symbolizer : symbolizer.createLiteral();
this.writeNode('color', {
color: literal.strokeColor.substring(1),
opacity: literal.strokeOpacity
}, null, node);
this.writeNode('width', literal.strokeWidth, null, node);
return node;
},
'color': function(colorObj) {
var color = colorObj.color;
var text = (colorObj.opacity * 255).toString(16) +
color.substring(4, 6) + color.substring(2, 4) +
color.substring(0, 2);
var node = this.createElementNS('color');
node.appendChild(this.createTextNode(text));
return node;
},
'width': function(width) {
var node = this.createElementNS('width');
node.appendChild(this.createTextNode(width));
return node;
},
'IconStyle': function(symbolizerObj) {
var node = this.createElementNS('IconStyle');
this.setAttributeNS(node, null, 'id', symbolizerObj.id);
this.writeNode('Icon', symbolizerObj.symbolizer.createLiteral().url,
null, node);
return node;
},
'Icon': function(url) {
var node = this.createElementNS('Icon');
this.writeNode('href', url, null, node);
return node;
},
'href': function(url) {
var node = this.createElementNS('href');
node.appendChild(this.createTextNode(url));
return node;
},
'_feature': function(feature) {
var node = this.createElementNS('Placemark');
var fid = feature.getFeatureId();
if (goog.isDef(fid)) {
node.setAttribute('id', fid);
}
this.writeNode('name', feature, null, node);
this.writeNode('description', feature, null, node);
var literals = feature.getSymbolizerLiterals();
if (goog.isDef(feature.get('styleUrl'))) {
this.writeNode('styleUrl', feature, null, node);
} else if (goog.isDefAndNotNull(literals)) {
// inline style
this.writeNode('_style', {symbolizers: literals}, null, node);
}
this.writeNode('_geometry', feature.getGeometry(), null, node);
return node;
},
'name': function(feature) {
var name = feature.get('name');
if (goog.isDef(name)) {
var node = this.createElementNS('name');
node.appendChild(this.createTextNode(name));
return node;
}
},
'description': function(feature) {
var description = feature.get('description');
if (goog.isDef(description)) {
var node = this.createElementNS('description');
node.appendChild(this.createTextNode(description));
return node;
}
},
'styleUrl': function(feature) {
var styleUrl = feature.get('styleUrl');
var node = this.createElementNS('styleUrl');
node.appendChild(this.createTextNode(styleUrl));
return node;
},
'_geometry': function(geometry) {
if (geometry instanceof ol.geom.Point) {
return this.writeNode('Point', geometry);
} else if (geometry instanceof ol.geom.LineString) {
return this.writeNode('LineString', geometry);
} else if (geometry instanceof ol.geom.Polygon) {
return this.writeNode('Polygon', geometry);
} else if (geometry instanceof ol.geom.AbstractCollection) {
return this.writeNode('MultiGeometry', geometry);
}
},
'MultiGeometry': function(geometry) {
var node = this.createElementNS('MultiGeometry');
for (var i = 0, ii = geometry.components.length; i < ii; ++i) {
this.writeNode('_geometry', geometry.components[i], null, node);
}
return node;
},
'Point': function(geometry) {
var node = this.createElementNS('Point');
var coords = geometry.getCoordinates();
this.writeNode('coordinates', [coords], null, node);
return node;
},
'LineString': function(geometry) {
var node = this.createElementNS('LineString');
this.writeNode('coordinates', geometry.getCoordinates(), null, node);
return node;
},
'Polygon': function(geometry) {
/**
* KML doesn't specify the winding order of coordinates in linear
* rings. So we keep them as they are in the geometries.
*/
var node = this.createElementNS('Polygon');
var coordinates = geometry.getCoordinates();
this.writeNode('outerBoundaryIs', coordinates[0], null, node);
for (var i = 1, ii = coordinates.length; i < ii; ++i) {
this.writeNode('innerBoundaryIs', coordinates[i], null, node);
}
return node;
},
'outerBoundaryIs': function(vertexArray) {
var node = this.createElementNS('outerBoundaryIs');
this.writeNode('LinearRing', vertexArray, null, node);
return node;
},
'innerBoundaryIs': function(vertexArray) {
var node = this.createElementNS('innerBoundaryIs');
this.writeNode('LinearRing', vertexArray, null, node);
return node;
},
'LinearRing': function(vertexArray) {
var node = this.createElementNS('LinearRing');
this.writeNode('coordinates', vertexArray, null, node);
return node;
},
'coordinates': function(vertexArray) {
var node = this.createElementNS('coordinates');
var coordstr = '';
for (var i = 0, ii = vertexArray.length; i < ii; ++i) {
for (var j = 0, jj = vertexArray[i].length; j < jj; ++j) {
coordstr += vertexArray[i][j];
if (j < jj - 1) {
coordstr += ',';
}
}
if (i < ii - 1) {
coordstr += ' ';
}
}
node.appendChild(this.createTextNode(coordstr));
return node;
}
}
};
goog.base(this);
goog.object.extend(this.regExes, {
kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
straightBracket: (/\$\[(.*?)\]/g)
});
};
goog.inherits(ol.parser.KML, ol.parser.XML);
/**
* @param {Object} obj Object representing features.
* @param {function(ol.parser.ReadFeaturesResult)} callback Callback which is
* called after parsing.
* @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
*/
ol.parser.KML.prototype.readFeaturesFromObjectAsync =
function(obj, callback, opt_options) {
this.readFeaturesOptions_ = opt_options;
this.read(obj, callback);
};
/**
* @param {string} str String data.
* @param {function(ol.parser.ReadFeaturesResult)}
* callback Callback which is called after parsing.
* @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
*/
ol.parser.KML.prototype.readFeaturesFromStringAsync =
function(str, callback, opt_options) {
this.readFeaturesOptions_ = opt_options;
this.read(str, callback);
};
/**
* Parse a KML document provided as a string.
* @param {string} str KML document.
* @param {ol.parser.ReadFeaturesOptions=} opt_options Reader options.
* @return {ol.parser.ReadFeaturesResult} Features and metadata.
*/
ol.parser.KML.prototype.readFeaturesFromString =
function(str, opt_options) {
this.readFeaturesOptions_ = opt_options;
return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(str));
};
/**
* Parse a KML document provided as a DOM structure.
* @param {Element|Document} node Document or element node.
* @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
* @return {ol.parser.ReadFeaturesResult} Features and metadata.
*/
ol.parser.KML.prototype.readFeaturesFromNode =
function(node, opt_options) {
this.readFeaturesOptions_ = opt_options;
return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(node));
};
/**
* @param {Object} obj Object representing features.
* @param {ol.parser.ReadFeaturesOptions=} opt_options Feature reading options.
* @return {ol.parser.ReadFeaturesResult} Features and metadata.
*/
ol.parser.KML.prototype.readFeaturesFromObject =
function(obj, opt_options) {
this.readFeaturesOptions_ = opt_options;
return /** @type {ol.parser.ReadFeaturesResult} */ (this.read(obj));
};
/**
* @param {Array} deferreds List of deferred instances.
* @param {Object} obj The returned object from the parser.
* @param {Function} done A callback for when all links have been retrieved.
*/
ol.parser.KML.prototype.parseLinks = function(deferreds, obj, done) {
var unvisited;
if (this.depth_ < this.maxDepth) {
this.depth_++;
for (var i = 0, ii = obj.links.length; i < ii; ++i) {
var link = obj.links[i];
if (link.visited !== true) {
unvisited = true;
var deferred = new goog.async.Deferred();
var xhr = new goog.net.XhrIo();
var me = this;
goog.events.listen(xhr, goog.net.EventType.COMPLETE, function(e) {
if (e.target.isSuccess()) {
var data = e.target.getResponseXml() || e.target.getResponseText();
if (goog.isString(data)) {
data = goog.dom.xml.loadXml(data);
}
goog.dispose(e.target);
if (data) {
if (data.nodeType == 9) {
data = data.documentElement;
}
me.readNode(data, obj);
}
me.parseLinks(deferreds, obj, done);
this.callback(data);
}
}, false, deferred);
deferreds.push(deferred);
xhr.send(link.href);
link.visited = true;
}
}
}
if (unvisited !== true && this.callbackCalled_ !== true) {
done.call(this);
}
};
/**
* @param {string|Document|Element|Object} data Data to read.
* @param {function(ol.parser.ReadFeaturesResult)=} opt_callback Optional
* callback to call when reading is done. If provided, this method will
* return undefined.
* @return {ol.parser.ReadFeaturesResult|undefined} An object representing the
* document if `opt_callback` was not provided.
*/
ol.parser.KML.prototype.read = function(data, opt_callback) {
if (goog.isString(data)) {
data = goog.dom.xml.loadXml(data);
}
if (data && data.nodeType == 9) {
data = data.documentElement;
}
var obj = /** @type {ol.parser.ReadFeaturesResult} */
({metadata: {projection: 'EPSG:4326'}});
this.readNode(data, obj);
if (goog.isDef(opt_callback)) {
var deferreds = [];
this.depth_ = 0;
this.callbackCalled_ = false;
this.parseLinks(deferreds, obj, function() {
this.callbackCalled_ = true;
goog.async.DeferredList.gatherResults(deferreds).addCallbacks(
function(datas) {
for (var i = 0, ii = obj.features.length; i < ii; ++i) {
var feature = obj.features[i];
this.applyStyle_(feature, obj['styles']);
}
opt_callback.call(null, obj);
}, function() {
throw new Error('KML: parsing of NetworkLinks failed');
}, this);
});
} else {
return obj;
}
};
/**
* @private
* @param {ol.Feature} feature The feature to apply the style to.
* @param {Array} styles The style list to search in.
* @param {Array=} opt_symbolizers Optional symbolizers.
*/
ol.parser.KML.prototype.applyStyle_ = function(feature, styles,
opt_symbolizers) {
var symbolizers = opt_symbolizers;
var i, ii;
if (feature.get('styleUrl') &&
feature.getSymbolizerLiterals() === null) {
var styleUrl = feature.get('styleUrl');
styleUrl = styleUrl.substring(styleUrl.indexOf('#') + 1);
// look for the style and set in the feature
if (goog.isDef(styles)) {
for (i = 0, ii = styles.length; i < ii; ++i) {
if (styles[i]['id'] === styleUrl) {
symbolizers = styles[i]['symbolizers'];
break;
}
}
}
}
if (goog.isDef(symbolizers)) {
var geom = feature.getGeometry();
if (geom && geom instanceof ol.geom.LineString) {
for (i = 0, ii = symbolizers.length; i < ii; i++) {
if (symbolizers[i] instanceof ol.style.Polygon) {
symbolizers.splice(i, 1);
}
}
}
feature.setSymbolizers(symbolizers);
}
};
/**
* @private
* @param {Object} container Geometry container.
* @param {ol.geom.SharedVertices=} opt_vertices Shared vertices.
* @return {ol.geom.Geometry} The geometry created.
*/
ol.parser.KML.prototype.createGeometry_ = function(container,
opt_vertices) {
var geometry = null, coordinates, i, ii;
switch (container.geometry.type) {
case ol.geom.GeometryType.POINT:
geometry = new ol.geom.Point(container.geometry.coordinates,
opt_vertices);
break;
case ol.geom.GeometryType.LINESTRING:
geometry = new ol.geom.LineString(container.geometry.coordinates,
opt_vertices);
break;
case ol.geom.GeometryType.POLYGON:
geometry = new ol.geom.Polygon(container.geometry.coordinates,
opt_vertices);
break;
case ol.geom.GeometryType.MULTIPOINT:
coordinates = [];
for (i = 0, ii = container.geometry.parts.length; i < ii; i++) {
coordinates.push(container.geometry.parts[i].coordinates);
}
geometry = new ol.geom.MultiPoint(coordinates, opt_vertices);
break;
case ol.geom.GeometryType.MULTILINESTRING:
coordinates = [];
for (i = 0, ii = container.geometry.parts.length; i < ii; i++) {
coordinates.push(container.geometry.parts[i].coordinates);
}
geometry = new ol.geom.MultiLineString(coordinates, opt_vertices);
break;
case ol.geom.GeometryType.MULTIPOLYGON:
coordinates = [];
for (i = 0, ii = container.geometry.parts.length; i < ii; i++) {
coordinates.push(container.geometry.parts[i].coordinates);
}
geometry = new ol.geom.MultiPolygon(coordinates, opt_vertices);
break;
case ol.geom.GeometryType.GEOMETRYCOLLECTION:
var geometries = [];
for (i = 0, ii = container.geometry.parts.length; i < ii; i++) {
geometries.push(this.createGeometry_({
geometry: container.geometry.parts[i]
}, opt_vertices));
}
geometry = new ol.geom.GeometryCollection(geometries);
break;
default:
break;
}
return geometry;
};
/**
* @param {Object} obj Object structure to write out as XML.
* @return {string} An string representing the XML document.
*/
ol.parser.KML.prototype.write = function(obj) {
var root = this.writeNode('kml', obj);
this.setAttributeNS(
root, 'http://www.w3.org/2001/XMLSchema-instance',
'xsi:schemaLocation', this.schemaLocation);
return this.serialize(root);
};