diff --git a/examples/mapguide-untiled.html b/examples/mapguide-untiled.html
new file mode 100644
index 0000000000..0d80284c1b
--- /dev/null
+++ b/examples/mapguide-untiled.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+ MapGuide untiled example
+
+
+
+
+
+
+
+
+
+
+
+
+
MapGuide untiled example
+
Example of a untiled MapGuide map.
+
+
mapguide
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/mapguide-untiled.js b/examples/mapguide-untiled.js
new file mode 100644
index 0000000000..b4d3358351
--- /dev/null
+++ b/examples/mapguide-untiled.js
@@ -0,0 +1,39 @@
+goog.require('ol.Map');
+goog.require('ol.RendererHint');
+goog.require('ol.View2D');
+goog.require('ol.layer.Image');
+goog.require('ol.source.MapGuide');
+
+var mdf = 'Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition';
+var agentUrl =
+ 'http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous';
+var bounds = [
+ -87.865114442365922,
+ 43.665065564837931,
+ -87.595394059497067,
+ 43.823852564430069
+];
+var map = new ol.Map({
+ layers: [
+ new ol.layer.Image({
+ source: new ol.source.MapGuide({
+ projection: 'EPSG:4326',
+ url: agentUrl,
+ useOverlay: false,
+ metersPerUnit: 111319.4908, //value returned from mapguide
+ params: {
+ MAPDEFINITION: mdf,
+ FORMAT: 'PNG'
+ },
+ extent: bounds
+ })
+ })
+ ],
+ renderer: ol.RendererHint.CANVAS,
+ target: 'map',
+ view: new ol.View2D({
+ center: [-87.7302542509315, 43.744459064634],
+ projection: 'EPSG:4326',
+ zoom: 12
+ })
+});
diff --git a/src/objectliterals.jsdoc b/src/objectliterals.jsdoc
index 83e0e9a6e1..eea253f924 100644
--- a/src/objectliterals.jsdoc
+++ b/src/objectliterals.jsdoc
@@ -606,6 +606,18 @@
* @todo stability experimental
*/
+/**
+ * @typedef {Object} ol.source.MapGuideOptions
+ * @property {string} url The mapagent url
+ * @property {number} metersPerUnit The meters-per-unit value
+ * @property {ol.Extent|undefined} extent Extent.
+ * @property {boolean} useOverlay If true, will use GETDYNAMICMAPOVERLAYIMAGE
+ * @property {ol.proj.ProjectionLike} projection Projection.
+ * @property {Array.|undefined} resolutions Resolutions. If specified,
+ * requests will be made for these resolutions only.
+ * @property {Object} params additional parameters
+ */
+
/**
* @typedef {Object} ol.source.MapQuestOptions
* @property {ol.TileLoadFunctionType|undefined} tileLoadFunction Optional
diff --git a/src/ol/source/mapguidesource.exports b/src/ol/source/mapguidesource.exports
new file mode 100644
index 0000000000..c386f721e0
--- /dev/null
+++ b/src/ol/source/mapguidesource.exports
@@ -0,0 +1 @@
+@exportSymbol ol.source.MapGuide
diff --git a/src/ol/source/mapguidesource.js b/src/ol/source/mapguidesource.js
new file mode 100644
index 0000000000..7fa5a742ed
--- /dev/null
+++ b/src/ol/source/mapguidesource.js
@@ -0,0 +1,125 @@
+goog.provide('ol.source.MapGuide');
+
+goog.require('goog.object');
+goog.require('goog.uri.utils');
+goog.require('ol.ImageUrlFunction');
+goog.require('ol.extent');
+goog.require('ol.source.Image');
+
+
+
+/**
+ * @constructor
+ * @extends {ol.source.Image}
+ * @param {ol.source.MapGuideOptions} options Options.
+ */
+ol.source.MapGuide = function(options) {
+ var imageUrlFunction = goog.isDef(options.url) ?
+ ol.ImageUrlFunction.createFromParamsFunction(
+ options.url,
+ options.params,
+ goog.bind(this.getUrl, this)) :
+ ol.ImageUrlFunction.nullImageUrlFunction;
+
+ /**
+ * @private
+ * @type {number}
+ */
+ this.metersPerUnit_ = options.metersPerUnit;
+
+ /**
+ * @private
+ * @type {boolean}
+ */
+ this.useOverlay_ = options.useOverlay;
+
+ /**
+ * @private
+ * @type {ol.Image}
+ */
+ this.image_ = null;
+
+ goog.base(this, {
+ extent: options.extent,
+ projection: options.projection,
+ resolutions: options.resolutions,
+ imageUrlFunction: imageUrlFunction
+ });
+};
+goog.inherits(ol.source.MapGuide, ol.source.Image);
+
+
+/**
+ * @inheritDoc
+ */
+ol.source.MapGuide.prototype.getImage =
+ function(extent, resolution, projection) {
+ resolution = this.findNearestResolution(resolution);
+
+ var image = this.image_;
+ if (!goog.isNull(image) &&
+ image.getResolution() == resolution &&
+ ol.extent.containsExtent(image.getExtent(), extent)) {
+ return image;
+ }
+
+ extent = extent.slice();
+ ol.extent.scaleFromCenter(extent, 1.0); //this.ratio_);
+ var width = (extent[2] - extent[0]) / resolution;
+ var height = (extent[3] - extent[1]) / resolution;
+ var size = [width, height];
+
+ this.image_ = this.createImage(extent, resolution, size, projection);
+ return this.image_;
+};
+
+
+/**
+ * @param {ol.Extent} extent The map extents
+ * @param {ol.Size} size the viewport size
+ * @return {number} The computed map scale
+ */
+ol.source.MapGuide.prototype.getScale = function(extent, size) {
+ var mcsW = extent[2] - extent[0];
+ var mcsH = extent[3] - extent[1];
+ var devW = size[0];
+ var devH = size[1];
+ var dpi = 96;
+ var mpu = this.metersPerUnit_;
+ var mpp = 0.0254 / dpi;
+ var scale = 0.0;
+ if (devH * mcsW > devW * mcsH)
+ scale = mcsW * mpu / (devW * mpp); //width-limited
+ else
+ scale = mcsH * mpu / (devH * mpp); //height-limited
+ return scale;
+};
+
+
+/**
+ * @param {string} baseUrl The mapagent url.
+ * @param {Object.} params Request parameters.
+ * @param {ol.Extent} extent Extent.
+ * @param {ol.Size} size Size.
+ * @param {ol.proj.Projection} projection Projection.
+ * @return {string} The mapagent map image request URL.
+ */
+ol.source.MapGuide.prototype.getUrl =
+ function(baseUrl, params, extent, size, projection) {
+ var scale = this.getScale(extent, size);
+ var baseParams = {
+ 'OPERATION': this.useOverlay_ ? 'GETDYNAMICMAPOVERLAYIMAGE' : 'GETMAPIMAGE',
+ 'VERSION': '2.0.0',
+ 'LOCALE': 'en',
+ 'CLIENTAGENT': 'ol.source.MapGuide source',
+ 'CLIP': '1',
+ 'SETDISPLAYDPI': 96,
+ 'SETDISPLAYWIDTH': Math.round(size[0]),
+ 'SETDISPLAYHEIGHT': Math.round(size[1]),
+ 'SETVIEWSCALE': scale,
+ 'SETVIEWCENTERX': (extent[0] + extent[2]) / 2,
+ 'SETVIEWCENTERY': (extent[1] + extent[3]) / 2
+ };
+ goog.object.extend(baseParams, params);
+ return goog.uri.utils.appendParamsFromMap(baseUrl, baseParams);
+};