Add support for ArcGIS Rest Services

Adding a data source to support ArcGIS Map Server and Image Server.
Functionality is similar to the ArcGIS93Rest Layer in OpenLayers 2.
This commit is contained in:
cwgrant
2015-02-16 18:37:13 -05:00
parent 3fbc6fa361
commit f63a856741
5 changed files with 626 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="../css/ol.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
<link rel="stylesheet" href="../resources/layout.css" type="text/css">
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
<title>Tiled ArcGIS MapServer example</title>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="./"><img src="../resources/logo.png"> OpenLayers 3 Examples</a>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div id="map" class="map"></div>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<h4 id="title">Tiled ArcGIS MapServer example</h4>
<p id="shortdesc">Example of a tiled ArcGIS layer.</p>
<div id="docs">
<p>See the <a href="arcgis-tiled.js" target="_blank">arcgis-tiled.js source</a> to see how this is done.</p>
</div>
<div id="tags">arcgis, tile, tilelayer</div>
</div>
</div>
</div>
<script src="../resources/jquery.min.js" type="text/javascript"></script>
<script src="../resources/example-behaviour.js" type="text/javascript"></script>
<script src="loader.js?id=arcgis-tiled" type="text/javascript"></script>
</body>
</html>

28
examples/arcgis-tiled.js Normal file
View File

@@ -0,0 +1,28 @@
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.layer.Tile');
goog.require('ol.source.MapQuest');
goog.require('ol.source.TileArcGISRest');
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.Tile({
extent: [-13884991, 2870341, -7455066, 6338219],
source: new ol.source.TileArcGISRest({
url: url
})
})
];
var map = new ol.Map({
layers: layers,
target: 'map',
view: new ol.View({
center: [-10997148, 4569099],
zoom: 4
})
});

View File

@@ -4978,6 +4978,93 @@ olx.source.ServerVectorOptions.prototype.logo;
*/ */
olx.source.ServerVectorOptions.prototype.projection; olx.source.ServerVectorOptions.prototype.projection;
/**
* @typedef {{attributions: (Array.<ol.Attribution>|undefined),
* params: (Object.<string, *>|undefined),
* logo: (string|olx.LogoOptions|undefined),
* tileGrid: (ol.tilegrid.TileGrid|undefined),
* projection: ol.proj.ProjectionLike,
* tileLoadFunction: (ol.TileLoadFunctionType|undefined),
* url: (string|undefined),
* urls: (Array.<string>|undefined)}}
* @api
*/
olx.source.TileArcGISRestOptions;
/**
* Attributions.
* @type {Array.<ol.Attribution>|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.attributions;
/**
* 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.TileArcGISRestOptions.prototype.params;
/**
* Logo.
* @type {string|olx.LogoOptions|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.logo;
/**
* Tile grid. Base this on the resolutions, tilesize and extent supported by the
* server.
* If this is not defined, a default grid will be used: if there is a projection
* extent, the grid will be based on that; if not, a grid based on a global
* extent with origin at 0,0 will be used.
* @type {ol.tilegrid.TileGrid|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.tileGrid;
/**
* Projection.
* @type {ol.proj.ProjectionLike}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.projection;
/**
* Optional function to load a tile given a URL.
* @type {ol.TileLoadFunctionType|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.tileLoadFunction;
/**
* ArcGIS Rest service URL for a Map Service or Image Service. The
* url should include /MapServer or /ImageServer.
* @type {string|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.url;
/**
* ArcGIS Rest service urls. Use this instead of `url` when the ArcGIS Service supports multiple
* urls for export requests.
* @type {Array.<string>|undefined}
* @api
*/
olx.source.TileArcGISRestOptions.prototype.urls;
/** /**
* @typedef {{attributions: (Array.<ol.Attribution>|undefined), * @typedef {{attributions: (Array.<ol.Attribution>|undefined),

View File

@@ -0,0 +1,218 @@
goog.provide('ol.source.TileArcGISRest');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.math');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.uri.utils');
goog.require('ol');
goog.require('ol.TileCoord');
goog.require('ol.TileUrlFunction');
goog.require('ol.extent');
goog.require('ol.proj');
goog.require('ol.source.TileImage');
goog.require('ol.tilecoord');
/**
* @classdesc
* Layer source for tile data from ArcGIS Rest services. Map and Image
* Services are supported.
*
* For cached ArcGIS services, better performance is available using the
* {@link ol.source.XYZ} data source.
*
* @constructor
* @extends {ol.source.TileImage}
* @param {olx.source.TileArcGISRestOptions=} opt_options Tile ArcGIS Rest
* options.
* @api
*/
ol.source.TileArcGISRest = function(opt_options) {
var options = goog.isDef(opt_options) ? opt_options : {};
var params = goog.isDef(options.params) ? options.params : {};
goog.base(this, {
attributions: options.attributions,
logo: options.logo,
projection: options.projection,
tileGrid: options.tileGrid,
tileLoadFunction: options.tileLoadFunction,
tileUrlFunction: goog.bind(this.tileUrlFunction_, this)
});
var urls = options.urls;
if (!goog.isDef(urls) && goog.isDef(options.url)) {
urls = ol.TileUrlFunction.expandUrl(options.url);
}
/**
* @private
* @type {!Array.<string>}
*/
this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
/**
* @private
* @type {Object}
*/
this.params_ = params;
/**
* @private
* @type {ol.Extent}
*/
this.tmpExtent_ = ol.extent.createEmpty();
};
goog.inherits(ol.source.TileArcGISRest, ol.source.TileImage);
/**
* 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
*/
ol.source.TileArcGISRest.prototype.getParams = function() {
return this.params_;
};
/**
* @param {ol.TileCoord} tileCoord Tile coordinate.
* @param {number} tileSize Tile size.
* @param {ol.Extent} tileExtent Tile extent.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.proj.Projection} projection Projection.
* @param {Object} params Params.
* @return {string|undefined} Request URL.
* @private
*/
ol.source.TileArcGISRest.prototype.getRequestUrl_ =
function(tileCoord, tileSize, tileExtent,
pixelRatio, projection, params) {
var urls = this.urls_;
if (goog.array.isEmpty(urls)) {
return undefined;
}
// ArcGIS Server only wants the numeric portion of the projection ID.
var srid = projection.getCode().split(':').pop();
params['SIZE'] = tileSize + ',' + tileSize;
params['BBOX'] = tileExtent.join(',');
params['BBOXSR'] = srid;
params['IMAGESR'] = srid;
var url;
if (urls.length == 1) {
url = urls[0];
} else {
var index = goog.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
url = urls[index];
}
if (!goog.string.endsWith(url, '/')) {
url = url + '/';
}
// If a MapServer, use export. If an ImageServer, use exportImage.
if (goog.string.endsWith(url, 'MapServer/')) {
url = url + 'export';
}
else if (goog.string.endsWith(url, 'ImageServer/')) {
url = url + 'exportImage';
}
else {
goog.asserts.fail('Unknown Rest Service', url);
}
return goog.uri.utils.appendParamsFromMap(url, params);
};
/**
* Return the URLs used for this ArcGIS source.
* @return {!Array.<string>} URLs.
* @api stable
*/
ol.source.TileArcGISRest.prototype.getUrls = function() {
return this.urls_;
};
/**
* @param {string|undefined} url URL.
* @api stable
*/
ol.source.TileArcGISRest.prototype.setUrl = function(url) {
var urls = goog.isDef(url) ? ol.TileUrlFunction.expandUrl(url) : null;
this.setUrls(urls);
};
/**
* @param {Array.<string>|undefined} urls URLs.
* @api stable
*/
ol.source.TileArcGISRest.prototype.setUrls = function(urls) {
this.urls_ = goog.isDefAndNotNull(urls) ? urls : [];
this.changed();
};
/**
* @param {ol.TileCoord} tileCoord Tile coordinate.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.proj.Projection} projection Projection.
* @return {string|undefined} Tile URL.
* @private
*/
ol.source.TileArcGISRest.prototype.tileUrlFunction_ =
function(tileCoord, pixelRatio, projection) {
var tileGrid = this.getTileGrid();
if (goog.isNull(tileGrid)) {
tileGrid = this.getTileGridForProjection(projection);
}
if (tileGrid.getResolutions().length <= tileCoord[0]) {
return undefined;
}
var tileExtent = tileGrid.getTileCoordExtent(
tileCoord, this.tmpExtent_);
var tileSize = tileGrid.getTileSize(tileCoord[0]);
if (pixelRatio != 1) {
tileSize = (tileSize * pixelRatio + 0.5) | 0;
}
// Apply default params and override with user specified values.
var baseParams = {
'F': 'image',
'FORMAT': 'PNG32',
'TRANSPARENT': true
};
goog.object.extend(baseParams, this.params_);
return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
pixelRatio, projection, baseParams);
};
/**
* Update the user-provided params.
* @param {Object} params Params.
* @api stable
*/
ol.source.TileArcGISRest.prototype.updateParams = function(params) {
goog.object.extend(this.params_, params);
this.changed();
};

View File

@@ -0,0 +1,242 @@
goog.provide('ol.test.source.TileArcGISRest');
describe('ol.source.TileArcGISRest', function() {
var options;
beforeEach(function() {
options = {
params: {},
url: 'http://example.com/MapServer'
};
});
describe('#getTile', function() {
it('returns a tile with the expected URL', function() {
var source = new ol.source.TileArcGISRest(options);
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:3857'));
expect(tile).to.be.an(ol.ImageTile);
var uri = new goog.Uri(tile.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(
'-10018754.171394622,-15028131.257091932,' +
'-5009377.085697311,-10018754.17139462');
expect(queryData.get('FORMAT')).to.be('PNG32');
expect(queryData.get('SIZE')).to.be('256,256');
expect(queryData.get('IMAGESR')).to.be('3857');
expect(queryData.get('BBOXSR')).to.be('3857');
expect(queryData.get('TRANSPARENT')).to.be('true');
});
it('returns a tile with the expected URL with url list', function() {
options.urls = ['http://test1.com/MapServer',
'http://test2.com/MapServer'];
var source = new ol.source.TileArcGISRest(options);
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:3857'));
expect(tile).to.be.an(ol.ImageTile);
var uri = new goog.Uri(tile.src_);
expect(uri.getScheme()).to.be('http');
expect(uri.getDomain()).to.match(/test[12]\.com/);
expect(uri.getPath()).to.be('/MapServer/export');
var queryData = uri.getQueryData();
expect(queryData.get('BBOX')).to.be(
'-10018754.171394622,-15028131.257091932,' +
'-5009377.085697311,-10018754.17139462');
expect(queryData.get('FORMAT')).to.be('PNG32');
expect(queryData.get('SIZE')).to.be('256,256');
expect(queryData.get('IMAGESR')).to.be('3857');
expect(queryData.get('BBOXSR')).to.be('3857');
expect(queryData.get('TRANSPARENT')).to.be('true');
});
it('returns a tile with the expected URL for ImageServer', function() {
options.url = 'http://example.com/ImageServer';
var source = new ol.source.TileArcGISRest(options);
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:3857'));
expect(tile).to.be.an(ol.ImageTile);
var uri = new goog.Uri(tile.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(
'-10018754.171394622,-15028131.257091932,' +
'-5009377.085697311,-10018754.17139462');
expect(queryData.get('FORMAT')).to.be('PNG32');
expect(queryData.get('SIZE')).to.be('256,256');
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.TileArcGISRest(options);
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:4326'));
var uri = new goog.Uri(tile.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.TileArcGISRest(options);
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:4326'));
var uri = new goog.Uri(tile.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.TileArcGISRest(options);
source.updateParams({ 'TEST': 'value' });
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:3857'));
var uri = new goog.Uri(tile.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.TileArcGISRest(options);
source.updateParams({ 'TEST': 'newValue' });
var tile = source.getTile(3, 2, 1, 1, ol.proj.get('EPSG:3857'));
var uri = new goog.Uri(tile.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.TileArcGISRest(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.TileArcGISRest(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.TileArcGISRest(options);
source.updateParams({ 'TEST': 'newValue' });
var setParams = source.getParams();
expect(setParams).to.eql({ TEST: 'newValue' });
});
});
describe('#getUrls', function() {
it('verify getting array of urls', function() {
options.urls = ['http://test.com/MapServer',
'http://test2.com/MapServer'];
var source = new ol.source.TileArcGISRest(options);
var urls = source.getUrls();
expect(urls).to.eql(['http://test.com/MapServer',
'http://test2.com/MapServer']);
});
});
describe('#setUrls', function() {
it('verify setting urls when not set yet', function() {
var source = new ol.source.TileArcGISRest(options);
source.setUrls(['http://test.com/MapServer',
'http://test2.com/MapServer']);
var urls = source.getUrls();
expect(urls).to.eql(['http://test.com/MapServer',
'http://test2.com/MapServer']);
});
it('verify setting urls with existing list', function() {
options.urls = ['http://test.com/MapServer',
'http://test2.com/MapServer'];
var source = new ol.source.TileArcGISRest(options);
source.setUrls(['http://test3.com/MapServer',
'http://test4.com/MapServer']);
var urls = source.getUrls();
expect(urls).to.eql(['http://test3.com/MapServer',
'http://test4.com/MapServer']);
});
});
describe('#setUrl', function() {
it('verify setting url with no urls', function() {
var source = new ol.source.TileArcGISRest(options);
source.setUrl('http://test.com/MapServer');
var urls = source.getUrls();
expect(urls).to.eql(['http://test.com/MapServer']);
});
it('verify setting url with list of urls', function() {
options.urls = ['http://test.com/MapServer',
'http://test2.com/MapServer'];
var source = new ol.source.TileArcGISRest(options);
source.setUrl('http://test3.com/MapServer');
var urls = source.getUrls();
expect(urls).to.eql(['http://test3.com/MapServer']);
});
});
});
goog.require('goog.Uri');
goog.require('ol.ImageTile');
goog.require('ol.source.TileArcGISRest');
goog.require('ol.proj');