Add ol.source.ImageArcGISRest (#3880)

* Add ol.source.ImageArcGISRest

ol.source.ImageArcGISRest is added so a single image can be retrieved
which is required when labelling is applied to the underlying map
service.
it's based on the approach used in ol.source.ImageWMS

* updating to remove as many good references as possible
changing so js passes lint checks

* fixing up so build and tests run

* putting back what was lost in the merge

* adding in an example
tidying up
This commit is contained in:
Anna Lambrechtsen
2016-04-20 21:19:53 +12:00
committed by Andreas Hocevar
parent 0e4692e69e
commit 18a29ea6d3
5 changed files with 595 additions and 2 deletions

View File

@@ -0,0 +1,11 @@
---
layout: example.html
title: Image ArcGIS MapServer
shortdesc: Example of an image ArcGIS layer.
docs: >
This example shows how to use a dynamic ArcGIS REST MapService.
This source type supports Map and Image Services. For dyamic ArcGIS
services.
tags: arcgis, image, dynamiclayer"
---
<div id="map" class="map"></div>

29
examples/arcgis-image.js Normal file
View File

@@ -0,0 +1,29 @@
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Image');
goog.require('ol.source.MapQuest');
goog.require('ol.source.ImageArcGISRest');
var url = 'http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/' +
'Specialty/ESRI_StateCityHighway_USA/MapServer';
var layers = [
new ol.layer.Tile({
source: new ol.source.MapQuest({layer: 'sat'})
}),
new ol.layer.Image({
source: new ol.source.ImageArcGISRest({
params: {},
url: url
})
})
];
var map = new ol.Map({
layers: layers,
target: 'map',
view: new ol.View({
center: [-10997148, 4569099],
zoom: 4
})
});

View File

@@ -3094,7 +3094,6 @@ olx.interaction.SelectOptions.prototype.toggleCondition;
*/ */
olx.interaction.SelectOptions.prototype.multi; olx.interaction.SelectOptions.prototype.multi;
/** /**
* Collection where the interaction will place selected features. Optional. If * Collection where the interaction will place selected features. Optional. If
* not set the interaction will create a collection. In any case the collection * not set the interaction will create a collection. In any case the collection
@@ -3105,7 +3104,6 @@ olx.interaction.SelectOptions.prototype.multi;
*/ */
olx.interaction.SelectOptions.prototype.features; olx.interaction.SelectOptions.prototype.features;
/** /**
* A function that takes an {@link ol.Feature} and an {@link ol.layer.Layer} and * A function that takes an {@link ol.Feature} and an {@link ol.layer.Layer} and
* returns `true` if the feature may be selected or `false` otherwise. * returns `true` if the feature may be selected or `false` otherwise.
@@ -4808,6 +4806,105 @@ olx.source.OSMOptions.prototype.url;
olx.source.OSMOptions.prototype.wrapX; olx.source.OSMOptions.prototype.wrapX;
/**
* @typedef {{attributions: (Array.<ol.Attribution>|undefined),
* crossOrigin: (null|string|undefined),
* logo: (string|olx.LogoOptions|undefined),
* imageLoadFunction: (ol.ImageLoadFunctionType|undefined),
* params: Object.<string,*>,
* projection: ol.proj.ProjectionLike,
* ratio: (number|undefined),
* resolutions: (Array.<number>|undefined),
* url: (string|undefined)}}
* @api
*/
olx.source.ImageArcGISRestOptions;
/**
* Attributions.
* @type {Array.<ol.Attribution>|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.attributions;
/**
* The `crossOrigin` attribute for loaded images. Note that you must provide a
* `crossOrigin` value if you are using the WebGL renderer or if you want to
* access pixel data with the Canvas renderer. See
* {@link https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image}
* for more detail.
* @type {null|string|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.crossOrigin;
/**
* Logo.
* @type {string|olx.LogoOptions|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.logo;
/**
* Optional function to load an image given a URL.
* @type {ol.ImageLoadFunctionType|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.imageLoadFunction;
/**
* ArcGIS Rest parameters. This field is optional. Service defaults will be
* used for any fields not specified. `FORMAT` is `PNG32` by default. `F` is `IMAGE` by
* default. `TRANSPARENT` is `true` by default. `BBOX, `SIZE`, `BBOXSR`,
* and `IMAGESR` will be set dynamically. Set `LAYERS` to
* override the default service layer visibility. See
* {@link http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#/Export_Map/02r3000000v7000000/}
* for further reference.
* @type {Object.<string,*>|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.params;
/**
* Projection.
* @type {ol.proj.ProjectionLike}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.projection;
/**
* Ratio. `1` means image requests are the size of the map viewport, `2` means
* twice the size of the map viewport, and so on. Default is `1.5`.
* @type {number|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.ratio;
/**
* Resolutions. If specified, requests will be made for these resolutions only.
* @type {Array.<number>|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.resolutions;
/**
* ArcGIS Rest service URL for a Map Service or Image Service. The
* url should include /MapServer or /ImageServer.
* @type {string|undefined}
* @api
*/
olx.source.ImageArcGISRestOptions.prototype.url;
/** /**
* @typedef {{attributions: (ol.AttributionLike|undefined), * @typedef {{attributions: (ol.AttributionLike|undefined),
* canvasFunction: ol.CanvasFunctionType, * canvasFunction: ol.CanvasFunctionType,

View File

@@ -0,0 +1,268 @@
goog.provide('ol.source.ImageArcGISRest');
goog.require('goog.asserts');
goog.require('goog.uri.utils');
goog.require('ol');
goog.require('ol.Image');
goog.require('ol.ImageLoadFunctionType');
goog.require('ol.events');
goog.require('ol.events.EventType');
goog.require('ol.extent');
goog.require('ol.object');
goog.require('ol.proj');
goog.require('ol.source.Image');
/**
* @classdesc
* Source for data from ArcGIS Rest services providing single, untiled images.
* Useful when underlying map service has labels.
*
* If underlying map service is not using labels,
* take advantage of ol image caching and use
* {@link ol.source.TileArcGISRest} data source.
*
* @constructor
* @fires ol.source.ImageEvent
* @extends {ol.source.Image}
* @param {olx.source.ImageArcGISRestOptions=} opt_options Image ArcGIS Rest Options.
* @api
*/
ol.source.ImageArcGISRest = function(opt_options) {
var options = opt_options || {};
goog.base(this, {
attributions: options.attributions,
logo: options.logo,
projection: options.projection,
resolutions: options.resolutions
});
/**
* @private
* @type {?string}
*/
this.crossOrigin_ =
options.crossOrigin !== undefined ? options.crossOrigin : null;
/**
* @private
* @type {string|undefined}
*/
this.url_ = options.url;
/**
* @private
* @type {ol.ImageLoadFunctionType}
*/
this.imageLoadFunction_ = options.imageLoadFunction !== undefined ?
options.imageLoadFunction : ol.source.Image.defaultImageLoadFunction;
/**
* @private
* @type {!Object}
*/
this.params_ = options.params || {};
/**
* @private
* @type {ol.Image}
*/
this.image_ = null;
/**
* @private
* @type {ol.Size}
*/
this.imageSize_ = [0, 0];
/**
* @private
* @type {number}
*/
this.renderedRevision_ = 0;
/**
* @private
* @type {number}
*/
this.ratio_ = options.ratio !== undefined ? options.ratio : 1.5;
};
goog.inherits(ol.source.ImageArcGISRest, ol.source.Image);
/**
* Get the user-provided params, i.e. those passed to the constructor through
* the "params" option, and possibly updated using the updateParams method.
* @return {Object} Params.
* @api stable
*/
ol.source.ImageArcGISRest.prototype.getParams = function() {
return this.params_;
};
/**
* @inheritDoc
*/
ol.source.ImageArcGISRest.prototype.getImageInternal = function(extent, resolution, pixelRatio, projection) {
if (this.url_ === undefined) {
return null;
}
resolution = this.findNearestResolution(resolution);
var image = this.image_;
if (image &&
this.renderedRevision_ == this.getRevision() &&
image.getResolution() == resolution &&
image.getPixelRatio() == pixelRatio &&
ol.extent.containsExtent(image.getExtent(), extent)) {
return image;
}
var params = {
'F': 'image',
'FORMAT': 'PNG32',
'TRANSPARENT': true
};
ol.object.assign(params, this.params_);
extent = extent.slice();
var centerX = (extent[0] + extent[2]) / 2;
var centerY = (extent[1] + extent[3]) / 2;
if (this.ratio_ != 1) {
var halfWidth = this.ratio_ * ol.extent.getWidth(extent) / 2;
var halfHeight = this.ratio_ * ol.extent.getHeight(extent) / 2;
extent[0] = centerX - halfWidth;
extent[1] = centerY - halfHeight;
extent[2] = centerX + halfWidth;
extent[3] = centerY + halfHeight;
}
var imageResolution = resolution / pixelRatio;
// Compute an integer width and height.
var width = Math.ceil(ol.extent.getWidth(extent) / imageResolution);
var height = Math.ceil(ol.extent.getHeight(extent) / imageResolution);
// Modify the extent to match the integer width and height.
extent[0] = centerX - imageResolution * width / 2;
extent[2] = centerX + imageResolution * width / 2;
extent[1] = centerY - imageResolution * height / 2;
extent[3] = centerY + imageResolution * height / 2;
this.imageSize_[0] = width;
this.imageSize_[1] = height;
var url = this.getRequestUrl_(extent, this.imageSize_, pixelRatio,
projection, params);
this.image_ = new ol.Image(extent, resolution, pixelRatio,
this.getAttributions(), url, this.crossOrigin_, this.imageLoadFunction_);
this.renderedRevision_ = this.getRevision();
ol.events.listen(this.image_, ol.events.EventType.CHANGE,
this.handleImageChange, this);
return this.image_;
};
/**
* Return the image load function of the source.
* @return {ol.ImageLoadFunctionType} The image load function.
* @api
*/
ol.source.ImageArcGISRest.prototype.getImageLoadFunction = function() {
return this.imageLoadFunction_;
};
/**
* @param {ol.Extent} extent Extent.
* @param {ol.Size} size Size.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.proj.Projection} projection Projection.
* @param {Object} params Params.
* @return {string} Request URL.
* @private
*/
ol.source.ImageArcGISRest.prototype.getRequestUrl_ = function(extent, size, pixelRatio, projection, params) {
goog.asserts.assert(this.url_ !== undefined, 'url is defined');
// ArcGIS Server only wants the numeric portion of the projection ID.
var srid = projection.getCode().split(':').pop();
params['SIZE'] = size[0] + ',' + size[1];
params['BBOX'] = extent.join(',');
params['BBOXSR'] = srid;
params['IMAGESR'] = srid;
params['DPI'] = 90 * pixelRatio;
var url = this.url_;
var modifiedUrl = url
.replace(/MapServer\/?$/, 'MapServer/export')
.replace(/ImageServer\/?$/, 'ImageServer/exportImage');
if (modifiedUrl == url) {
goog.asserts.fail('Unknown Rest Service', url);
}
return goog.uri.utils.appendParamsFromMap(modifiedUrl, params);
};
/**
* Return the URL used for this ArcGIS source.
* @return {string|undefined} URL.
* @api stable
*/
ol.source.ImageArcGISRest.prototype.getUrl = function() {
return this.url_;
};
/**
* Set the image load function of the source.
* @param {ol.ImageLoadFunctionType} imageLoadFunction Image load function.
* @api
*/
ol.source.ImageArcGISRest.prototype.setImageLoadFunction = function(imageLoadFunction) {
this.image_ = null;
this.imageLoadFunction_ = imageLoadFunction;
this.changed();
};
/**
* Set the URL to use for requests.
* @param {string|undefined} url URL.
* @api stable
*/
ol.source.ImageArcGISRest.prototype.setUrl = function(url) {
if (url != this.url_) {
this.url_ = url;
this.image_ = null;
this.changed();
}
};
/**
* Update the user-provided params.
* @param {Object} params Params.
* @api stable
*/
ol.source.ImageArcGISRest.prototype.updateParams = function(params) {
ol.object.assign(this.params_, params);
this.image_ = null;
this.changed();
};

View File

@@ -0,0 +1,188 @@
goog.provide('ol.test.source.ImageArcGISRest');
describe('ol.source.ImageArcGISRest', function() {
var pixelRatio, options, projection, proj3857, resolution;
beforeEach(function() {
pixelRatio = 1;
projection = ol.proj.get('EPSG:4326');
proj3857 = ol.proj.get('EPSG:3857');
resolution = 0.1;
options = {
params: {},
url: 'http://example.com/MapServer'
};
});
describe('#getImage', function() {
it('returns a image with the expected URL', function() {
var source = new ol.source.ImageArcGISRest(options);
var image = source.getImage([3, 2, -7, 1], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
expect(uri.getScheme()).to.be('http');
expect(uri.getDomain()).to.be('example.com');
expect(uri.getPath()).to.be('/MapServer/export');
var queryData = uri.getQueryData();
expect(queryData.get('BBOX')).to.be('5.5,2.25,-9.5,0.75');
expect(queryData.get('FORMAT')).to.be('PNG32');
expect(queryData.get('IMAGESR')).to.be('3857');
expect(queryData.get('BBOXSR')).to.be('3857');
expect(queryData.get('TRANSPARENT')).to.be('true');
});
it('returns a non floating point DPI value', function() {
var source = new ol.source.ImageArcGISRest(options);
var image = source.getImage([3, 2, -7, 1.12], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
var queryData = uri.getQueryData();
expect(queryData.get('DPI')).to.be('90');
});
it('returns a image with the expected URL for ImageServer', function() {
options.url = 'http://example.com/ImageServer';
var source = new ol.source.ImageArcGISRest(options);
var image = source.getImage([3, 2, -7, 1], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
expect(uri.getScheme()).to.be('http');
expect(uri.getDomain()).to.be('example.com');
expect(uri.getPath()).to.be('/ImageServer/exportImage');
var queryData = uri.getQueryData();
expect(queryData.get('BBOX')).to.be('5.5,2.25,-9.5,0.75');
expect(queryData.get('FORMAT')).to.be('PNG32');
expect(queryData.get('IMAGESR')).to.be('3857');
expect(queryData.get('BBOXSR')).to.be('3857');
expect(queryData.get('TRANSPARENT')).to.be('true');
});
it('allows various parameters to be overridden', function() {
options.params.FORMAT = 'png';
options.params.TRANSPARENT = false;
var source = new ol.source.ImageArcGISRest(options);
var image = source.getImage([3, 2, -3, 1], resolution, pixelRatio, projection);
var uri = new goog.Uri(image.src_);
var queryData = uri.getQueryData();
expect(queryData.get('FORMAT')).to.be('png');
expect(queryData.get('TRANSPARENT')).to.be('false');
});
it('allows adding rest option', function() {
options.params.LAYERS = 'show:1,3,4';
var source = new ol.source.ImageArcGISRest(options);
var image = source.getImage([3, 2, -3, 1], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
var queryData = uri.getQueryData();
expect(queryData.get('LAYERS')).to.be('show:1,3,4');
});
});
describe('#updateParams', function() {
it('add a new param', function() {
var source = new ol.source.ImageArcGISRest(options);
source.updateParams({'TEST': 'value'});
var image = source.getImage([3, 2, -7, 1], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
var queryData = uri.getQueryData();
expect(queryData.get('TEST')).to.be('value');
});
it('updates an existing param', function() {
options.params.TEST = 'value';
var source = new ol.source.ImageArcGISRest(options);
source.updateParams({'TEST':'newValue'});
var image = source.getImage([3, 2, -7, 1], resolution, pixelRatio, proj3857);
var uri = new goog.Uri(image.src_);
var queryData = uri.getQueryData();
expect(queryData.get('TEST')).to.be('newValue');
});
});
describe('#getParams', function() {
it('verify getting a param', function() {
options.params.TEST = 'value';
var source = new ol.source.ImageArcGISRest(options);
var setParams = source.getParams();
expect(setParams).to.eql({TEST: 'value'});
});
it('verify on adding a param', function() {
options.params.TEST = 'value';
var source = new ol.source.ImageArcGISRest(options);
source.updateParams({'TEST2':'newValue'});
var setParams = source.getParams();
expect(setParams).to.eql({TEST:'value', TEST2:'newValue'});
});
it('verify on update a param', function() {
options.params.TEST = 'value';
var source = new ol.source.ImageArcGISRest(options);
source.updateParams({'TEST':'newValue'});
var setParams = source.getParams();
expect(setParams).to.eql({TEST:'newValue'});
});
});
describe('#getUrl', function() {
it('verify getting url', function() {
options.url = 'http://test.com/MapServer';
var source = new ol.source.ImageArcGISRest(options);
var url = source.getUrl();
expect(url).to.eql('http://test.com/MapServer');
});
});
describe('#setUrl', function() {
it('verify setting url when not set yet', function() {
var source = new ol.source.ImageArcGISRest(options);
source.setUrl('http://test.com/MapServer');
var url = source.getUrl();
expect(url).to.eql('http://test.com/MapServer');
});
it('verify setting url with existing url', function() {
options.url = 'http://test.com/MapServer';
var source = new ol.source.ImageArcGISRest(options);
source.setUrl('http://test2.com/MapServer');
var url = source.getUrl();
expect(url).to.eql('http://test2.com/MapServer');
});
});
});
goog.require('goog.Uri');
goog.require('ol.source.ImageArcGISRest');
goog.require('ol.proj');