1 /* Copyright (c) 2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Layer/Grid.js
7 * @requires OpenLayers/Tile/Image.js
8 * @requires OpenLayers/Format/ArcXML.js
9 * @requires OpenLayers/Request.js
13 * Class: OpenLayers.Layer.ArcIMS
14 * Instances of OpenLayers.Layer.ArcIMS are used to display data from ESRI ArcIMS
15 * Mapping Services. Create a new ArcIMS layer with the <OpenLayers.Layer.ArcIMS>
19 * - <OpenLayers.Layer.Grid>
21 OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
24 * Constant: DEFAULT_PARAMS
25 * {Object} Default query string parameters.
33 * APIProperty: tileSize
34 * {<OpenLayers.Size>} Size for tiles. Default is 512x512.
39 * APIProperty: featureCoordSys
40 * {String} Code for feature coordinate system. Default is "4326".
42 featureCoordSys: "4326",
45 * APIProperty: filterCoordSys
46 * {String} Code for filter coordinate system. Default is "4326".
48 filterCoordSys: "4326",
52 * {Array} An array of objects with layer properties.
58 * {Boolean} Request images asynchronously. Default is true.
64 * {String} Layer name. Default is "ArcIMS".
69 * APIProperty: isBaseLayer
70 * {Boolean} The layer is a base layer. Default is true.
75 * Constant: DEFAULT_OPTIONS
76 * {Object} Default layers properties.
79 tileSize: new OpenLayers.Size(512, 512),
80 featureCoordSys: "4326",
81 filterCoordSys: "4326",
89 * Constructor: OpenLayers.Layer.ArcIMS
90 * Create a new ArcIMS layer object.
94 * var arcims = new OpenLayers.Layer.ArcIMS(
96 * "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap",
98 * service: "OpenLayers_Sample",
100 * // layers to manipulate
101 * {id: "1", visible: true}
108 * name - {String} A name for the layer
109 * url - {String} Base url for the ArcIMS server
110 * options - {Object} Optional object with properties to be set on the
113 initialize: function(name, url, options) {
115 this.tileSize = new OpenLayers.Size(512, 512);
118 this.params = OpenLayers.Util.applyDefaults(
119 {ServiceName: options.serviceName},
122 this.options = OpenLayers.Util.applyDefaults(
123 options, this.DEFAULT_OPTIONS
126 OpenLayers.Layer.Grid.prototype.initialize.apply(
127 this, [name, url, this.params, options]
130 //layer is transparent
131 if (this.transparent) {
133 // unless explicitly set in options, make layer an overlay
134 if (!this.isBaseLayer) {
135 this.isBaseLayer = false;
138 // jpegs can never be transparent, so intelligently switch the
139 // format, depending on the browser's capabilities
140 if (this.format == "image/jpeg") {
141 this.format = OpenLayers.Util.alphaHack() ? "image/gif" : "image/png";
145 // create an empty layer list if no layers specified in the options
146 if (this.options.layers === null) {
147 this.options.layers = [];
156 destroy: function() {
157 // for now, nothing special to do here.
158 OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);
164 * Return an image url this layer.
167 * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
171 * {String} A string with the map image's url.
173 getURL: function(bounds) {
175 bounds = this.adjustBounds(bounds);
177 // create an arcxml request to generate the image
178 var axlReq = new OpenLayers.Format.ArcXML(
179 OpenLayers.Util.extend(this.options, {
180 requesttype: "image",
181 envelope: bounds.toArray(),
182 tileSize: this.tileSize
186 // create a synchronous ajax request to get an arcims image
187 var req = new OpenLayers.Request.POST({
188 url: this.getFullRequestString(),
189 data: axlReq.write(),
193 // if the response exists
195 var doc = req.responseXML;
197 if (!doc || !doc.documentElement) {
198 doc = req.responseText;
201 // create a new arcxml format to read the response
202 var axlResp = new OpenLayers.Format.ArcXML();
203 var arcxml = axlResp.read(doc);
204 url = this.getUrlOrImage(arcxml.image.output);
212 * Method: getURLasync
213 * Get an image url this layer asynchronously, and execute a callback
214 * when the image url is generated.
217 * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
219 * scope - {Object} The scope of the callback method.
220 * prop - {String} The name of the property in the scoped object to
221 * recieve the image url.
222 * callback - {Function} Function to call when image url is retrieved.
224 getURLasync: function(bounds, scope, prop, callback) {
225 bounds = this.adjustBounds(bounds);
227 // create an arcxml request to generate the image
228 var axlReq = new OpenLayers.Format.ArcXML(
229 OpenLayers.Util.extend(this.options, {
230 requesttype: "image",
231 envelope: bounds.toArray(),
232 tileSize: this.tileSize
236 // create an asynchronous ajax request to get an arcims image
237 OpenLayers.Request.POST({
238 url: this.getFullRequestString(),
240 data: axlReq.write(),
241 callback: function(req) {
242 // process the response from ArcIMS, and call the callback function
243 // to set the image URL
244 var doc = req.responseXML;
245 if (!doc || !doc.documentElement) {
246 doc = req.responseText;
249 // create a new arcxml format to read the response
250 var axlResp = new OpenLayers.Format.ArcXML();
251 var arcxml = axlResp.read(doc);
253 scope[prop] = this.getUrlOrImage(arcxml.image.output);
255 // call the callback function to recieve the updated property on the
257 callback.apply(scope);
264 * Method: getUrlOrImage
265 * Extract a url or image from the ArcXML image output.
268 * output - {Object} The image.output property of the object returned from
269 * the ArcXML format read method.
272 * {String} A URL for an image (potentially with the data protocol).
274 getUrlOrImage: function(output) {
277 // If the image response output url is a string, then the image
278 // data is not inline.
280 } else if(output.data) {
281 // The image data is inline and base64 encoded, create a data
282 // url for the image. This will only work for small images,
283 // due to browser url length limits.
284 ret = "data:image/" + output.type +
285 ";base64," + output.data;
291 * Method: setLayerQuery
292 * Set the query definition on this layer. Query definitions are used to
293 * render parts of the spatial data in an image, and can be used to
294 * filter features or layers in the ArcIMS service.
297 * id - {String} The ArcIMS layer ID.
298 * queryDef - {Object} The query definition to apply to this layer.
300 setLayerQuery: function(id, querydef) {
301 // find the matching layer, if it exists
302 for (var lyr = 0; lyr < this.options.layers.length; lyr++) {
303 if (id == this.options.layers[lyr].id) {
304 // replace this layer definition
305 this.options.layers[lyr].query = querydef;
310 // no layer found, create a new definition
311 this.options.layers.push({id: id, visible: true, query: querydef});
315 * Method: getFeatureInfo
316 * Get feature information from ArcIMS. Using the applied geometry, apply
317 * the options to the query (buffer, area/envelope intersection), and
318 * query the ArcIMS service.
320 * A note about accuracy:
321 * ArcIMS interprets the accuracy attribute in feature requests to be
322 * something like the 'modulus' operator on feature coordinates,
323 * applied to the database geometry of the feature. It doesn't round,
324 * so your feature coordinates may be up to (1 x accuracy) offset from
325 * the actual feature coordinates. If the accuracy of the layer is not
326 * specified, the accuracy will be computed to be approximately 1
327 * feature coordinate per screen pixel.
330 * geometry - {<OpenLayers.LonLat>} or {<OpenLayers.Geometry.Polygon>} The
331 * geometry to use when making the query. This should be a closed
332 * polygon for behavior approximating a free selection.
333 * layer - {Object} The ArcIMS layer definition. This is an anonymous object
337 * id: "ArcXML layer ID", // the ArcXML layer ID
339 * where: "STATE = 'PA'", // the where clause of the query
340 * accuracy: 100 // the accuracy of the returned feature
344 * options - {Object} Object with non-default properties to set on the layer.
345 * Supported properties are buffer, callback, scope, and any other
346 * properties applicable to the ArcXML format. Set the 'callback' and
347 * 'scope' for an object and function to recieve the parsed features
350 getFeatureInfo: function(geometry, layer, options) {
351 // set the buffer to 1 unit (dd/m/ft?) by default
352 var buffer = options.buffer || 1;
353 // empty callback by default
354 var callback = options.callback || function() {};
355 // default scope is window (global)
356 var scope = options.scope || window;
358 // apply these option to the request options
359 var requestOptions = {};
360 OpenLayers.Util.extend(requestOptions, this.options);
362 // this is a feature request
363 requestOptions.requesttype = "feature";
365 if (geometry instanceof OpenLayers.LonLat) {
366 // create an envelope if the geometry is really a lon/lat
367 requestOptions.polygon = null;
368 requestOptions.envelope = [
369 geometry.lon - buffer,
370 geometry.lat - buffer,
371 geometry.lon + buffer,
372 geometry.lat + buffer
374 } else if (geometry instanceof OpenLayers.Geometry.Polygon) {
375 // use the polygon assigned, and empty the envelope
376 requestOptions.envelope = null;
377 requestOptions.polygon = geometry;
380 // create an arcxml request to get feature requests
381 var arcxml = new OpenLayers.Format.ArcXML(requestOptions);
383 // apply any get feature options to the arcxml request
384 OpenLayers.Util.extend(arcxml.request.get_feature, options);
386 arcxml.request.get_feature.layer = layer.id;
387 if (typeof layer.query.accuracy == "number") {
388 // set the accuracy if it was specified
389 arcxml.request.get_feature.query.accuracy = layer.query.accuracy;
391 // guess that the accuracy is 1 per screen pixel
392 var mapCenter = this.map.getCenter();
393 var viewPx = this.map.getViewPortPxFromLonLat(mapCenter);
395 var mapOffCenter = this.map.getLonLatFromPixel(viewPx);
396 arcxml.request.get_feature.query.accuracy = mapOffCenter.lon - mapCenter.lon;
399 // set the get_feature query to be the same as the layer passed in
400 arcxml.request.get_feature.query.where = layer.query.where;
402 // use area_intersection
403 arcxml.request.get_feature.query.spatialfilter.relation = "area_intersection";
405 // create a new asynchronous request to get the feature info
406 OpenLayers.Request.POST({
407 url: this.getFullRequestString({'CustomService': 'Query'}),
408 data: arcxml.write(),
409 callback: function(request) {
410 // parse the arcxml response
411 var response = arcxml.parseResponse(request.responseText);
413 if (!arcxml.iserror()) {
414 // if the arcxml is not an error, call the callback with the features parsed
415 callback.call(scope, response.features);
417 // if the arcxml is an error, return null features selected
418 callback.call(scope, null);
426 * addTile creates a tile, initializes it, and adds it to the layer div.
429 * bounds - {<OpenLayers.Bounds>}
430 * position - {<OpenLayers.Pixel>}
433 * {<OpenLayers.Tile.Image>} The added image tile.
435 addTile:function(bounds,position) {
436 return new OpenLayers.Tile.Image(
437 this, position, bounds, null, this.tileSize
441 CLASS_NAME: "OpenLayers.Layer.ArcIMS"