diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 8566f748d0..d4ac12b859 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -617,6 +617,20 @@
* @property {ol.source.State|undefined} state State.
*/
+/**
+ * @typedef {Object} ol.source.VectorFileOptions
+ * @property {Array.
|undefined} attributions Attributions.
+ * @property {Document|undefined} doc Document.
+ * @property {ol.Extent|undefined} extent Extent.
+ * @property {ol.format.Format} format Format.
+ * @property {string|undefined} logo Logo.
+ * @property {Node|undefined} node Node.
+ * @property {Object|undefined} object Object.
+ * @property {ol.proj.ProjectionLike} projection Projection.
+ * @property {string|undefined} text Text.
+ * @property {string|undefined} url URL.
+ */
+
/**
* @typedef {Object} ol.source.WMTSOptions
* @property {Array.|undefined} attributions Attributions.
diff --git a/src/ol/source/vectorfilesource.exports b/src/ol/source/vectorfilesource.exports
new file mode 100644
index 0000000000..285db94e72
--- /dev/null
+++ b/src/ol/source/vectorfilesource.exports
@@ -0,0 +1 @@
+@exportClass ol.source.VectorFile ol.source.VectorFileOptions
diff --git a/src/ol/source/vectorfilesource.js b/src/ol/source/vectorfilesource.js
new file mode 100644
index 0000000000..7d4493dec6
--- /dev/null
+++ b/src/ol/source/vectorfilesource.js
@@ -0,0 +1,125 @@
+// FIXME remove reprojectTo
+
+goog.provide('ol.source.VectorFile');
+
+goog.require('goog.asserts');
+goog.require('goog.net.XhrIo');
+goog.require('ol.format.FormatType');
+goog.require('ol.proj');
+goog.require('ol.source.State');
+goog.require('ol.source.Vector');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.source.Vector}
+ * @param {ol.source.VectorFileOptions=} opt_options Options.
+ */
+ol.source.VectorFile = function(opt_options) {
+
+ var options = goog.isDef(opt_options) ? opt_options : {};
+
+ goog.base(this, {
+ attributions: options.attributions,
+ extent: options.extent,
+ logo: options.logo,
+ projection: options.projection
+ });
+
+ /**
+ * @type {ol.format.Format}
+ * @protected
+ */
+ this.format = options.format;
+
+ /**
+ * @type {ol.proj.Projection}
+ * @private
+ */
+ this.reprojectTo_ = goog.isDef(options.reprojectTo) ?
+ ol.proj.get(options.reprojectTo) : ol.proj.get('EPSG:3857');
+
+ if (goog.isDef(options.doc)) {
+ this.readFeatures_(options.doc);
+ }
+
+ if (goog.isDef(options.node)) {
+ this.readFeatures_(options.node);
+ }
+
+ if (goog.isDef(options.object)) {
+ this.readFeatures_(options.object);
+ }
+
+ if (goog.isDef(options.text)) {
+ this.readFeatures_(options.text);
+ }
+
+ if (goog.isDef(options.url)) {
+ this.setState(ol.source.State.LOADING);
+ goog.net.XhrIo.send(options.url, goog.bind(this.handleXhrIo_, this));
+ }
+
+};
+goog.inherits(ol.source.VectorFile, ol.source.Vector);
+
+
+/**
+ * @param {Event} event Event.
+ * @private
+ */
+ol.source.VectorFile.prototype.handleXhrIo_ = function(event) {
+ var xhrIo = /** @type {goog.net.XhrIo} */ (event.target);
+ if (xhrIo.isSuccess()) {
+ var type = this.format.getType();
+ /** @type {Document|Node|Object|string|undefined} */
+ var source;
+ if (type == ol.format.FormatType.BINARY) {
+ // FIXME
+ } else if (type == ol.format.FormatType.JSON) {
+ source = xhrIo.getResponseJson();
+ } else if (type == ol.format.FormatType.TEXT) {
+ source = xhrIo.getResponseText();
+ } else if (type == ol.format.FormatType.XML) {
+ source = xhrIo.getResponseXml();
+ } else {
+ goog.asserts.fail();
+ }
+ if (goog.isDef(source)) {
+ this.readFeatures_(source);
+ } else {
+ goog.asserts.fail();
+ this.setState(ol.source.State.ERROR);
+ }
+ } else {
+ this.setState(ol.source.State.ERROR);
+ }
+};
+
+
+/**
+ * @param {Document|Node|Object|string} source Source.
+ * @private
+ */
+ol.source.VectorFile.prototype.readFeatures_ = function(source) {
+ var format = this.format;
+ var features = format.readFeatures(source);
+ var featureProjection = format.readProjection(source);
+ var transform;
+ if (!ol.proj.equivalent(featureProjection, this.reprojectTo_)) {
+ transform = ol.proj.getTransform(featureProjection, this.reprojectTo_);
+ } else {
+ transform = null;
+ }
+ var i, ii;
+ for (i = 0, ii = features.length; i < ii; ++i) {
+ var feature = features[i];
+ var geometry = feature.getGeometry();
+ if (!goog.isNull(geometry) && !goog.isNull(transform)) {
+ geometry.transform(transform);
+ }
+ this.addFeature(feature);
+ }
+ this.setState(ol.source.State.READY);
+};