diff --git a/examples/data/osm/map.osm b/examples/data/osm/map.osm
new file mode 100644
index 0000000000..135e798e1d
--- /dev/null
+++ b/examples/data/osm/map.osm
@@ -0,0 +1,2223 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/vector-osm.html b/examples/vector-osm.html
new file mode 100644
index 0000000000..6a0e5ea7ef
--- /dev/null
+++ b/examples/vector-osm.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+ OSM XML example
+
+
+
+
+
+
+
+
+
+
+
+
+
OSM XML example
+
Example of using the OSM XML source.
+
+
vector, osm, xml
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/vector-osm.js b/examples/vector-osm.js
new file mode 100644
index 0000000000..25f757bae4
--- /dev/null
+++ b/examples/vector-osm.js
@@ -0,0 +1,123 @@
+goog.require('ol.Map');
+goog.require('ol.View2D');
+goog.require('ol.layer.Tile');
+goog.require('ol.layer.Vector');
+goog.require('ol.source.BingMaps');
+goog.require('ol.source.OSMXML');
+goog.require('ol.style.Circle');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+
+var styles = {
+ 'amenity': {
+ 'parking': [
+ new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: 'rgba(170, 170, 170, 1.0)',
+ width: 1
+ }),
+ fill: new ol.style.Fill({
+ color: 'rgba(170, 170, 170, 0.3)'
+ })
+ })
+ ]
+ },
+ 'building': {
+ '.*': [
+ new ol.style.Style({
+ zIndex: 100,
+ stroke: new ol.style.Stroke({
+ color: 'rgba(246, 99, 79, 1.0)',
+ width: 1
+ }),
+ fill: new ol.style.Fill({
+ color: 'rgba(246, 99, 79, 0.3)'
+ })
+ })
+ ]
+ },
+ 'highway': {
+ 'service': [
+ new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: 'rgba(255, 255, 255, 1.0)',
+ width: 2
+ })
+ })
+ ],
+ '.*': [
+ new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: 'rgba(255, 255, 255, 1.0)',
+ width: 3
+ })
+ })
+ ]
+ },
+ 'landuse': {
+ 'forest|grass|allotments': [
+ new ol.style.Style({
+ stroke: new ol.style.Stroke({
+ color: 'rgba(140, 208, 95, 1.0)',
+ width: 1
+ }),
+ fill: new ol.style.Fill({
+ color: 'rgba(140, 208, 95, 0.3)'
+ })
+ })
+ ]
+ },
+ 'natural': {
+ 'tree': [
+ new ol.style.Style({
+ image: new ol.style.Circle({
+ radius: 2,
+ fill: new ol.style.Fill({
+ color: 'rgba(140, 208, 95, 1.0)'
+ }),
+ stroke: null
+ })
+ })
+ ]
+ }
+};
+
+var vectorSource = new ol.source.OSMXML({
+ projection: 'EPSG:3857',
+ url: 'data/osm/map.osm'
+});
+
+var vector = new ol.layer.Vector({
+ source: vectorSource,
+ style: function(feature, resolution) {
+ for (var key in styles) {
+ var value = feature.get(key);
+ if (value !== undefined) {
+ for (var regexp in styles[key]) {
+ if (new RegExp(regexp).test(value)) {
+ return styles[key][regexp];
+ }
+ }
+ }
+ }
+ return null;
+ }
+});
+
+var raster = new ol.layer.Tile({
+ source: new ol.source.BingMaps({
+ imagerySet: 'Aerial',
+ key: 'Ak-dzM4wZjSqTlzveKz5u0d4IQ4bRzVI309GxmkgSVr1ewS6iPSrOvOKhA-CJlm3'
+ })
+});
+
+var map = new ol.Map({
+ layers: [raster, vector],
+ renderer: 'canvas',
+ target: document.getElementById('map'),
+ view: new ol.View2D({
+ center: [739218, 5906096],
+ zoom: 17
+ })
+});
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 74622c6b14..55fbe40143 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -700,6 +700,21 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} olx.source.OSMXMLOptions
+ * @property {Array.|undefined} attributions Attributions.
+ * @property {Array.|undefined} defaultStyle Default style.
+ * @property {Document|undefined} doc Document.
+ * @property {ol.Extent|undefined} extent Extent.
+ * @property {string|undefined} logo Logo.
+ * @property {Node|undefined| node Node.
+ * @property {ol.proj.ProjectionLike} projection Projection.
+ * @property {ol.proj.ProjectionLike} reprojectTo Re-project to.
+ * @property {string|undefined} text Text.
+ * @property {string|undefined} url URL.
+ * @property {Array.|undefined} urls URLs.
+ */
+
/**
* @typedef {Object} olx.source.ImageCanvasOptions
* @property {Array.|undefined} attributions Attributions.
diff --git a/src/ol/format/osmxmlformat.js b/src/ol/format/osmxmlformat.js
new file mode 100644
index 0000000000..65f8f2badb
--- /dev/null
+++ b/src/ol/format/osmxmlformat.js
@@ -0,0 +1,210 @@
+// FIXME add typedef for stack state objects
+goog.provide('ol.format.OSMXML');
+
+goog.require('goog.array');
+goog.require('goog.asserts');
+goog.require('goog.dom.NodeType');
+goog.require('goog.object');
+goog.require('ol.Feature');
+goog.require('ol.format.XMLFeature');
+goog.require('ol.geom.LineString');
+goog.require('ol.geom.Point');
+goog.require('ol.geom.Polygon');
+goog.require('ol.proj');
+goog.require('ol.xml');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.format.XMLFeature}
+ */
+ol.format.OSMXML = function() {
+ goog.base(this);
+};
+goog.inherits(ol.format.OSMXML, ol.format.XMLFeature);
+
+
+/**
+ * @const
+ * @type {Array.}
+ * @private
+ */
+ol.format.OSMXML.EXTENSIONS_ = ['.osm'];
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.getExtensions = function() {
+ return ol.format.OSMXML.EXTENSIONS_;
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readNode_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+ goog.asserts.assert(node.localName == 'node');
+ var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ var id = node.getAttribute('id');
+ var coordinates = /** @type {Array.} */ ([
+ parseFloat(node.getAttribute('lon')),
+ parseFloat(node.getAttribute('lat'))
+ ]);
+ goog.object.set(state.nodes, id, coordinates);
+
+ var values = ol.xml.pushParseAndPop({
+ tags: {}
+ }, ol.format.OSMXML.NODE_PARSERS_, node, objectStack);
+ if (!goog.object.isEmpty(values.tags)) {
+ var geometry = new ol.geom.Point(coordinates);
+ var feature = new ol.Feature(geometry);
+ feature.setId(id);
+ feature.setValues(values.tags);
+ state.features.push(feature);
+ }
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ */
+ol.format.OSMXML.readWay_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+ goog.asserts.assert(node.localName == 'way');
+ var values = ol.xml.pushParseAndPop({
+ ndrefs: [],
+ tags: {}
+ }, ol.format.OSMXML.WAY_PARSERS_, node, objectStack);
+ var state = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ var flatCoordinates = /** @type {Array.} */ ([]);
+ for (var i = 0, ii = values.ndrefs.length; i < ii; i++) {
+ var point = goog.object.get(state.nodes, values.ndrefs[i]);
+ goog.array.extend(flatCoordinates, point);
+ }
+ if (values.ndrefs[0] == values.ndrefs[values.ndrefs.length - 1]) {
+ // closed way
+ var geometry = new ol.geom.Polygon(null);
+ geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates,
+ [flatCoordinates.length]);
+ } else {
+ var geometry = new ol.geom.LineString(null);
+ geometry.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates);
+ }
+ var feature = new ol.Feature(geometry);
+ feature.setValues(values.tags);
+ state.features.push(feature);
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.OSMXML.readNd_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+ goog.asserts.assert(node.localName == 'nd');
+ var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ values.ndrefs.push(node.getAttribute('ref'));
+};
+
+
+/**
+ * @param {Node} node Node.
+ * @param {Array.<*>} objectStack Object stack.
+ * @private
+ * @return {ol.Feature|undefined} Track.
+ */
+ol.format.OSMXML.readTag_ = function(node, objectStack) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+ goog.asserts.assert(node.localName == 'tag');
+ var values = /** @type {Object} */ (objectStack[objectStack.length - 1]);
+ goog.object.set(values.tags, node.getAttribute('k'), node.getAttribute('v'));
+};
+
+
+/**
+ * @const
+ * @private
+ * @type {Array.}
+ */
+ol.format.OSMXML.NAMESPACE_URIS_ = [
+ null
+];
+
+
+/**
+ * @const
+ * @type {Object.>}
+ * @private
+ */
+ol.format.OSMXML.WAY_PARSERS_ = ol.xml.makeParsersNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'nd': ol.format.OSMXML.readNd_,
+ 'tag': ol.format.OSMXML.readTag_
+ });
+
+
+/**
+ * @const
+ * @type {Object.>}
+ * @private
+ */
+ol.format.OSMXML.PARSERS_ = ol.xml.makeParsersNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'node': ol.format.OSMXML.readNode_,
+ 'way': ol.format.OSMXML.readWay_
+ });
+
+
+/**
+ * @const
+ * @type {Object.>}
+ * @private
+ */
+ol.format.OSMXML.NODE_PARSERS_ = ol.xml.makeParsersNS(
+ ol.format.OSMXML.NAMESPACE_URIS_, {
+ 'tag': ol.format.OSMXML.readTag_
+ });
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readFeaturesFromNode = function(node) {
+ goog.asserts.assert(node.nodeType == goog.dom.NodeType.ELEMENT);
+ if (node.localName == 'osm') {
+ var state = ol.xml.pushParseAndPop({
+ nodes: {},
+ features: []
+ }, ol.format.OSMXML.PARSERS_, node, []);
+ if (goog.isDef(state.features)) {
+ return state.features;
+ }
+ }
+ return [];
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readProjectionFromDocument = function(doc) {
+ return ol.proj.get('EPSG:4326');
+};
+
+
+/**
+ * @inheritDoc
+ */
+ol.format.OSMXML.prototype.readProjectionFromNode = function(node) {
+ return ol.proj.get('EPSG:4326');
+};
diff --git a/src/ol/source/osmxmlsource.exports b/src/ol/source/osmxmlsource.exports
new file mode 100644
index 0000000000..880127d254
--- /dev/null
+++ b/src/ol/source/osmxmlsource.exports
@@ -0,0 +1 @@
+@exportSymbol ol.source.OSMXML
diff --git a/src/ol/source/osmxmlsource.js b/src/ol/source/osmxmlsource.js
new file mode 100644
index 0000000000..48353beda5
--- /dev/null
+++ b/src/ol/source/osmxmlsource.js
@@ -0,0 +1,31 @@
+goog.provide('ol.source.OSMXML');
+
+goog.require('ol.format.OSMXML');
+goog.require('ol.source.VectorFile');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.source.VectorFile}
+ * @param {olx.source.OSMXMLOptions=} opt_options Options.
+ */
+ol.source.OSMXML = function(opt_options) {
+
+ var options = goog.isDef(opt_options) ? opt_options : {};
+
+ goog.base(this, {
+ attributions: options.attributions,
+ doc: options.doc,
+ extent: options.extent,
+ format: new ol.format.OSMXML(),
+ logo: options.logo,
+ node: options.node,
+ projection: options.projection,
+ reprojectTo: options.reprojectTo,
+ text: options.text,
+ url: options.url
+ });
+
+};
+goog.inherits(ol.source.OSMXML, ol.source.VectorFile);