--- a/labs/openlayers/lib/OpenLayers/Layer/ArcIMS.js +++ b/labs/openlayers/lib/OpenLayers/Layer/ArcIMS.js @@ -1,1 +1,467 @@ - +/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + * @requires OpenLayers/Tile/Image.js + * @requires OpenLayers/Format/ArcXML.js + * @requires OpenLayers/Request.js + */ + +/** + * Class: OpenLayers.Layer.ArcIMS + * Instances of OpenLayers.Layer.ArcIMS are used to display data from ESRI ArcIMS + * Mapping Services. Create a new ArcIMS layer with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Default query string parameters. + */ + DEFAULT_PARAMS: { + ClientVersion: "9.2", + ServiceName: '' + }, + + /** + * APIProperty: tileSize + * {} Size for tiles. Default is 512x512. + */ + tileSize: null, + + /** + * APIProperty: featureCoordSys + * {String} Code for feature coordinate system. Default is "4326". + */ + featureCoordSys: "4326", + + /** + * APIProperty: filterCoordSys + * {String} Code for filter coordinate system. Default is "4326". + */ + filterCoordSys: "4326", + + /** + * APIProperty: layers + * {Array} An array of objects with layer properties. + */ + layers: null, + + /** + * APIProperty: async + * {Boolean} Request images asynchronously. Default is true. + */ + async: true, + + /** + * APIProperty: name + * {String} Layer name. Default is "ArcIMS". + */ + name: "ArcIMS", + + /** + * APIProperty: isBaseLayer + * {Boolean} The layer is a base layer. Default is true. + */ + isBaseLayer: true, + + /** + * Constant: DEFAULT_OPTIONS + * {Object} Default layers properties. + */ + DEFAULT_OPTIONS: { + tileSize: new OpenLayers.Size(512, 512), + featureCoordSys: "4326", + filterCoordSys: "4326", + layers: null, + isBaseLayer: true, + async: true, + name: "ArcIMS" + }, + + /** + * Constructor: OpenLayers.Layer.ArcIMS + * Create a new ArcIMS layer object. + * + * Example: + * (code) + * var arcims = new OpenLayers.Layer.ArcIMS( + * "Global Sample", + * "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap", + * { + * service: "OpenLayers_Sample", + * layers: [ + * // layers to manipulate + * {id: "1", visible: true} + * ] + * } + * ); + * (end) + * + * Parameters: + * name - {String} A name for the layer + * url - {String} Base url for the ArcIMS server + * options - {Object} Optional object with properties to be set on the + * layer. + */ + initialize: function(name, url, options) { + + this.tileSize = new OpenLayers.Size(512, 512); + + // parameters + this.params = OpenLayers.Util.applyDefaults( + {ServiceName: options.serviceName}, + this.DEFAULT_PARAMS + ); + this.options = OpenLayers.Util.applyDefaults( + options, this.DEFAULT_OPTIONS + ); + + OpenLayers.Layer.Grid.prototype.initialize.apply( + this, [name, url, this.params, options] + ); + + //layer is transparent + if (this.transparent) { + + // unless explicitly set in options, make layer an overlay + if (!this.isBaseLayer) { + this.isBaseLayer = false; + } + + // jpegs can never be transparent, so intelligently switch the + // format, depending on the browser's capabilities + if (this.format == "image/jpeg") { + this.format = OpenLayers.Util.alphaHack() ? "image/gif" : "image/png"; + } + } + + // create an empty layer list if no layers specified in the options + if (this.options.layers === null) { + this.options.layers = []; + } + }, + + + /** + * Method: destroy + * Destroy this layer + */ + destroy: function() { + // for now, nothing special to do here. + OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments); + }, + + + /** + * Method: getURL + * Return an image url this layer. + * + * Parameters: + * bounds - {} A bounds representing the bbox for the + * request. + * + * Returns: + * {String} A string with the map image's url. + */ + getURL: function(bounds) { + var url = ""; + bounds = this.adjustBounds(bounds); + + // create an arcxml request to generate the image + var axlReq = new OpenLayers.Format.ArcXML( + OpenLayers.Util.extend(this.options, { + requesttype: "image", + envelope: bounds.toArray(), + tileSize: this.tileSize + }) + ); + + // create a synchronous ajax request to get an arcims image + var req = new OpenLayers.Request.POST({ + url: this.getFullRequestString(), + data: axlReq.write(), + async: false + }); + + // if the response exists + if (req != null) { + var doc = req.responseXML; + + if (!doc || !doc.documentElement) { + doc = req.responseText; + } + + // create a new arcxml format to read the response + var axlResp = new OpenLayers.Format.ArcXML(); + var arcxml = axlResp.read(doc); + url = this.getUrlOrImage(arcxml.image.output); + } + + return url; + }, + + + /** + * Method: getURLasync + * Get an image url this layer asynchronously, and execute a callback + * when the image url is generated. + * + * Parameters: + * bounds - {} A bounds representing the bbox for the + * request. + * scope - {Object} The scope of the callback method. + * prop - {String} The name of the property in the scoped object to + * recieve the image url. + * callback - {Function} Function to call when image url is retrieved. + */ + getURLasync: function(bounds, scope, prop, callback) { + bounds = this.adjustBounds(bounds); + + // create an arcxml request to generate the image + var axlReq = new OpenLayers.Format.ArcXML( + OpenLayers.Util.extend(this.options, { + requesttype: "image", + envelope: bounds.toArray(), + tileSize: this.tileSize + }) + ); + + // create an asynchronous ajax request to get an arcims image + OpenLayers.Request.POST({ + url: this.getFullRequestString(), + async: true, + data: axlReq.write(), + callback: function(req) { + // process the response from ArcIMS, and call the callback function + // to set the image URL + var doc = req.responseXML; + if (!doc || !doc.documentElement) { + doc = req.responseText; + } + + // create a new arcxml format to read the response + var axlResp = new OpenLayers.Format.ArcXML(); + var arcxml = axlResp.read(doc); + + scope[prop] = this.getUrlOrImage(arcxml.image.output); + + // call the callback function to recieve the updated property on the + // scoped object + callback.apply(scope); + }, + scope: this + }); + }, + + /** + * Method: getUrlOrImage + * Extract a url or image from the ArcXML image output. + * + * Parameters: + * output - {Object} The image.output property of the object returned from + * the ArcXML format read method. + * + * Returns: + * {String} A URL for an image (potentially with the data protocol). + */ + getUrlOrImage: function(output) { + var ret = ""; + if(output.url) { + // If the image response output url is a string, then the image + // data is not inline. + ret = output.url; + } else if(output.data) { + // The image data is inline and base64 encoded, create a data + // url for the image. This will only work for small images, + // due to browser url length limits. + ret = "data:image/" + output.type + + ";base64," + output.data; + } + return ret; + }, + + /** + * Method: setLayerQuery + * Set the query definition on this layer. Query definitions are used to + * render parts of the spatial data in an image, and can be used to + * filter features or layers in the ArcIMS service. + * + * Parameters: + * id - {String} The ArcIMS layer ID. + * queryDef - {Object} The query definition to apply to this layer. + */ + setLayerQuery: function(id, querydef) { + // find the matching layer, if it exists + for (var lyr = 0; lyr < this.options.layers.length; lyr++) { + if (id == this.options.layers[lyr].id) { + // replace this layer definition + this.options.layers[lyr].query = querydef; + return; + } + } + + // no layer found, create a new definition + this.options.layers.push({id: id, visible: true, query: querydef}); + }, + + /** + * Method: getFeatureInfo + * Get feature information from ArcIMS. Using the applied geometry, apply + * the options to the query (buffer, area/envelope intersection), and + * query the ArcIMS service. + * + * A note about accuracy: + * ArcIMS interprets the accuracy attribute in feature requests to be + * something like the 'modulus' operator on feature coordinates, + * applied to the database geometry of the feature. It doesn't round, + * so your feature coordinates may be up to (1 x accuracy) offset from + * the actual feature coordinates. If the accuracy of the layer is not + * specified, the accuracy will be computed to be approximately 1 + * feature coordinate per screen pixel. + * + * Parameters: + * geometry - {} or {} The + * geometry to use when making the query. This should be a closed + * polygon for behavior approximating a free selection. + * layer - {Object} The ArcIMS layer definition. This is an anonymous object + * that looks like: + * (code) + * { + * id: "ArcXML layer ID", // the ArcXML layer ID + * query: { + * where: "STATE = 'PA'", // the where clause of the query + * accuracy: 100 // the accuracy of the returned feature + * } + * } + * (end) + * options - {Object} Object with non-default properties to set on the layer. + * Supported properties are buffer, callback, scope, and any other + * properties applicable to the ArcXML format. Set the 'callback' and + * 'scope' for an object and function to recieve the parsed features + * from ArcIMS. + */ + getFeatureInfo: function(geometry, layer, options) { + // set the buffer to 1 unit (dd/m/ft?) by default + var buffer = options.buffer || 1; + // empty callback by default + var callback = options.callback || function() {}; + // default scope is window (global) + var scope = options.scope || window; + + // apply these option to the request options + var requestOptions = {}; + OpenLayers.Util.extend(requestOptions, this.options); + + // this is a feature request + requestOptions.requesttype = "feature"; + + if (geometry instanceof OpenLayers.LonLat) { + // create an envelope if the geometry is really a lon/lat + requestOptions.polygon = null; + requestOptions.envelope = [ + geometry.lon - buffer, + geometry.lat - buffer, + geometry.lon + buffer, + geometry.lat + buffer + ]; + } else if (geometry instanceof OpenLayers.Geometry.Polygon) { + // use the polygon assigned, and empty the envelope + requestOptions.envelope = null; + requestOptions.polygon = geometry; + } + + // create an arcxml request to get feature requests + var arcxml = new OpenLayers.Format.ArcXML(requestOptions); + + // apply any get feature options to the arcxml request + OpenLayers.Util.extend(arcxml.request.get_feature, options); + + arcxml.request.get_feature.layer = layer.id; + if (typeof layer.query.accuracy == "number") { + // set the accuracy if it was specified + arcxml.request.get_feature.query.accuracy = layer.query.accuracy; + } else { + // guess that the accuracy is 1 per screen pixel + var mapCenter = this.map.getCenter(); + var viewPx = this.map.getViewPortPxFromLonLat(mapCenter); + viewPx.x++; + var mapOffCenter = this.map.getLonLatFromPixel(viewPx); + arcxml.request.get_feature.query.accuracy = mapOffCenter.lon - mapCenter.lon; + } + + // set the get_feature query to be the same as the layer passed in + arcxml.request.get_feature.query.where = layer.query.where; + + // use area_intersection + arcxml.request.get_feature.query.spatialfilter.relation = "area_intersection"; + + // create a new asynchronous request to get the feature info + OpenLayers.Request.POST({ + url: this.getFullRequestString({'CustomService': 'Query'}), + data: arcxml.write(), + callback: function(request) { + // parse the arcxml response + var response = arcxml.parseResponse(request.responseText); + + if (!arcxml.iserror()) { + // if the arcxml is not an error, call the callback with the features parsed + callback.call(scope, response.features); + } else { + // if the arcxml is an error, return null features selected + callback.call(scope, null); + } + } + }); + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.ArcIMS(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + return obj; + }, + + /** + * Method: addTile + * addTile creates a tile, initializes it, and adds it to the layer div. + * + * Parameters: + * bounds - {} + * position - {} + * + * Returns: + * {} The added image tile. + */ + addTile:function(bounds,position) { + return new OpenLayers.Tile.Image( + this, position, bounds, null, this.tileSize + ); + }, + + CLASS_NAME: "OpenLayers.Layer.ArcIMS" +}); +