--- a/labs/openlayers/lib/OpenLayers/Layer/MapGuide.js +++ b/labs/openlayers/lib/OpenLayers/Layer/MapGuide.js @@ -1,1 +1,490 @@ - +/* 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/Request/XMLHttpRequest.js + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.MapGuide + * Instances of OpenLayers.Layer.MapGuide are used to display + * data from a MapGuide OS instance. + * + * Inherits from: + * - + */ +OpenLayers.Layer.MapGuide = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * {Boolean} Treat this layer as a base layer. Default is true. + **/ + isBaseLayer: true, + + /** + * APIProperty: useHttpTile + * {Boolean} use a tile cache exposed directly via a webserver rather than the + * via mapguide server. This does require extra configuration on the Mapguide Server, + * and will only work when singleTile is false. The url for the layer must be set to the + * webserver path rather than the Mapguide mapagent. + * See http://trac.osgeo.org/mapguide/wiki/CodeSamples/Tiles/ServingTilesViaHttp + **/ + useHttpTile: false, + + /** + * APIProperty: singleTile + * {Boolean} use tile server or request single tile image. + **/ + singleTile: false, + + /** + * APIProperty: useOverlay + * {Boolean} flag to indicate if the layer should be retrieved using + * GETMAPIMAGE (default) or using GETDYNAMICOVERLAY requests. + **/ + useOverlay: false, + + /** + * APIProperty: useAsyncOverlay + * {Boolean} indicates if the MapGuide site supports the asynchronous + * GETDYNAMICOVERLAY requests which is available in MapGuide Enterprise 2010 + * and MapGuide Open Source v2.0.3 or higher. The newer versions of MG + * is called asynchronously, allows selections to be drawn separately from + * the map and offers styling options. + * + * With older versions of MapGuide, set useAsyncOverlay=false. Note that in + * this case a synchronous AJAX call is issued and the mapname and session + * parameters must be used to initialize the layer, not the mapdefinition + * parameter. Also note that this will issue a synchronous AJAX request + * before the image request can be issued so the users browser may lock + * up if the MG Web tier does not respond in a timely fashion. + **/ + useAsyncOverlay: true, + + /** + * Constant: TILE_PARAMS + * {Object} Hashtable of default parameter key/value pairs for tiled layer + */ + TILE_PARAMS: { + operation: 'GETTILEIMAGE', + version: '1.2.0' + }, + + /** + * Constant: SINGLE_TILE_PARAMS + * {Object} Hashtable of default parameter key/value pairs for untiled layer + */ + SINGLE_TILE_PARAMS: { + operation: 'GETMAPIMAGE', + format: 'PNG', + locale: 'en', + clip: '1', + version: '1.0.0' + }, + + /** + * Constant: OVERLAY_PARAMS + * {Object} Hashtable of default parameter key/value pairs for untiled layer + */ + OVERLAY_PARAMS: { + operation: 'GETDYNAMICMAPOVERLAYIMAGE', + format: 'PNG', + locale: 'en', + clip: '1', + version: '2.0.0' + }, + + /** + * Constant: FOLDER_PARAMS + * {Object} Hashtable of parameter key/value pairs which describe + * the folder structure for tiles as configured in the mapguide + * serverconfig.ini section [TileServiceProperties] + */ + FOLDER_PARAMS: { + tileColumnsPerFolder: 30, + tileRowsPerFolder: 30, + format: 'png', + querystring: null + }, + + /** + * Property: defaultSize + * {} Tile size as produced by MapGuide server + **/ + defaultSize: new OpenLayers.Size(300,300), + + /** + * Constructor: OpenLayers.Layer.MapGuide + * Create a new Mapguide layer, either tiled or untiled. + * + * For tiled layers, the 'groupName' and 'mapDefinition' values + * must be specified as parameters in the constructor. + * + * For untiled base layers, specify either combination of 'mapName' and + * 'session', or 'mapDefinition' and 'locale'. + * + * For older versions of MapGuide and overlay layers, set useAsyncOverlay + * to false and in this case mapName and session are required parameters + * for the constructor. + * + * NOTE: MapGuide OS uses a DPI value and degrees to meters conversion + * factor that are different than the defaults used in OpenLayers, + * so these must be adjusted accordingly in your application. + * See the MapGuide example for how to set these values for MGOS. + * + * Parameters: + * name - {String} Name of the layer displayed in the interface + * url - {String} Location of the MapGuide mapagent executable + * (e.g. http://localhost:8008/mapguide/mapagent/mapagent.fcgi) + * params - {Object} hashtable of additional parameters to use. Some + * parameters may require additional code on the server. The ones that + * you may want to use are: + * - mapDefinition - {String} The MapGuide resource definition + * (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition) + * - locale - Locale setting + * (for untiled overlays layers only) + * - mapName - {String} Name of the map as stored in the MapGuide session. + * (for untiled layers with a session parameter only) + * - session - { String} MapGuide session ID + * (for untiled overlays layers only) + * - basemaplayergroupname - {String} GroupName for tiled MapGuide layers only + * - format - Image format to be returned (for untiled overlay layers only) + * - showLayers - {String} A comma separated list of GUID's for the + * layers to display eg: 'cvc-xcv34,453-345-345sdf'. + * - hideLayers - {String} A comma separated list of GUID's for the + * layers to hide eg: 'cvc-xcv34,453-345-345sdf'. + * - showGroups - {String} A comma separated list of GUID's for the + * groups to display eg: 'cvc-xcv34,453-345-345sdf'. + * - hideGroups - {String} A comma separated list of GUID's for the + * groups to hide eg: 'cvc-xcv34,453-345-345sdf' + * - selectionXml - {String} A selection xml string Some server plumbing + * is required to read such a value. + * options - {Ojbect} Hashtable of extra options to tag onto the layer; + * will vary depending if tiled or untiled maps are being requested + */ + initialize: function(name, url, params, options) { + + OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments); + + // unless explicitly set in options, if the layer is transparent, + // it will be an overlay + if (options == null || options.isBaseLayer == null) { + this.isBaseLayer = ((this.transparent != "true") && + (this.transparent != true)); + } + + if (options && options.useOverlay!=null) { + this.useOverlay = options.useOverlay; + } + + //initialize for untiled layers + if (this.singleTile) { + if (this.useOverlay) { + OpenLayers.Util.applyDefaults( + this.params, + this.OVERLAY_PARAMS + ); + if (!this.useAsyncOverlay) { + this.params.version = "1.0.0"; + } + } else { + OpenLayers.Util.applyDefaults( + this.params, + this.SINGLE_TILE_PARAMS + ); + } + } else { + //initialize for tiled layers + if (this.useHttpTile) { + OpenLayers.Util.applyDefaults( + this.params, + this.FOLDER_PARAMS + ); + } else { + OpenLayers.Util.applyDefaults( + this.params, + this.TILE_PARAMS + ); + } + this.setTileSize(this.defaultSize); + } + }, + + /** + * 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.MapGuide(this.name, + this.url, + this.params, + this.getOptions()); + } + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + return obj; + }, + + /** + * Method: addTile + * Creates a tile, initializes it, and adds it to the layer div. + * + * Parameters: + * bounds - {} + * position - {} + * + * Returns: + * {} The added OpenLayers.Tile.Image + */ + addTile:function(bounds,position) { + return new OpenLayers.Tile.Image(this, position, bounds, + null, this.tileSize); + }, + + /** + * Method: getURL + * Return a query string for this layer + * + * Parameters: + * bounds - {} A bounds representing the bbox + * for the request + * + * Returns: + * {String} A string with the layer's url and parameters and also + * the passed-in bounds and appropriate tile size specified + * as parameters. + */ + getURL: function (bounds) { + var url; + var center = bounds.getCenterLonLat(); + var mapSize = this.map.getSize(); + + if (this.singleTile) { + //set up the call for GETMAPIMAGE or GETDYNAMICMAPOVERLAY with + //dynamic map parameters + var params = { + setdisplaydpi: OpenLayers.DOTS_PER_INCH, + setdisplayheight: mapSize.h*this.ratio, + setdisplaywidth: mapSize.w*this.ratio, + setviewcenterx: center.lon, + setviewcentery: center.lat, + setviewscale: this.map.getScale() + }; + + if (this.useOverlay && !this.useAsyncOverlay) { + //first we need to call GETVISIBLEMAPEXTENT to set the extent + var getVisParams = {}; + getVisParams = OpenLayers.Util.extend(getVisParams, params); + getVisParams.operation = "GETVISIBLEMAPEXTENT"; + getVisParams.version = "1.0.0"; + getVisParams.session = this.params.session; + getVisParams.mapName = this.params.mapName; + getVisParams.format = 'text/xml'; + url = this.getFullRequestString( getVisParams ); + + OpenLayers.Request.GET({url: url, async: false}); + } + //construct the full URL + url = this.getFullRequestString( params ); + } else { + + //tiled version + var currentRes = this.map.getResolution(); + var colidx = Math.floor((bounds.left-this.maxExtent.left)/currentRes); + colidx = Math.round(colidx/this.tileSize.w); + var rowidx = Math.floor((this.maxExtent.top-bounds.top)/currentRes); + rowidx = Math.round(rowidx/this.tileSize.h); + + if (this.useHttpTile){ + url = this.getImageFilePath( + { + tilecol: colidx, + tilerow: rowidx, + scaleindex: this.resolutions.length - this.map.zoom - 1 + }); + + } else { + url = this.getFullRequestString( + { + tilecol: colidx, + tilerow: rowidx, + scaleindex: this.resolutions.length - this.map.zoom - 1 + }); + } + } + return url; + }, + + /** + * Method: getFullRequestString + * getFullRequestString on MapGuide layers is special, because we + * do a regular expression replace on ',' in parameters to '+'. + * This is why it is subclassed here. + * + * Parameters: + * altUrl - {String} Alternative base URL to use. + * + * Returns: + * {String} A string with the layer's url appropriately encoded for MapGuide + */ + getFullRequestString:function(newParams, altUrl) { + // use layer's url unless altUrl passed in + var url = (altUrl == null) ? this.url : altUrl; + + // if url is not a string, it should be an array of strings, + // in which case we will randomly select one of them in order + // to evenly distribute requests to different urls. + if (typeof url == "object") { + url = url[Math.floor(Math.random()*url.length)]; + } + // requestString always starts with url + var requestString = url; + + // create a new params hashtable with all the layer params and the + // new params together. then convert to string + var allParams = OpenLayers.Util.extend({}, this.params); + allParams = OpenLayers.Util.extend(allParams, newParams); + // ignore parameters that are already in the url search string + var urlParams = OpenLayers.Util.upperCaseObject( + OpenLayers.Util.getParameters(url)); + for(var key in allParams) { + if(key.toUpperCase() in urlParams) { + delete allParams[key]; + } + } + var paramsString = OpenLayers.Util.getParameterString(allParams); + + /* MapGuide needs '+' seperating things like bounds/height/width. + Since typically this is URL encoded, we use a slight hack: we + depend on the list-like functionality of getParameterString to + leave ',' only in the case of list items (since otherwise it is + encoded) then do a regular expression replace on the , characters + to '+' */ + paramsString = paramsString.replace(/,/g, "+"); + + if (paramsString != "") { + var lastServerChar = url.charAt(url.length - 1); + if ((lastServerChar == "&") || (lastServerChar == "?")) { + requestString += paramsString; + } else { + if (url.indexOf('?') == -1) { + //serverPath has no ? -- add one + requestString += '?' + paramsString; + } else { + //serverPath contains ?, so must already have paramsString at the end + requestString += '&' + paramsString; + } + } + } + return requestString; + }, + + /** + * Method: getImageFilePath + * special handler to request mapguide tiles from an http exposed tilecache + * + * Parameters: + * altUrl - {String} Alternative base URL to use. + * + * Returns: + * {String} A string with the url for the tile image + */ + getImageFilePath:function(newParams, altUrl) { + // use layer's url unless altUrl passed in + var url = (altUrl == null) ? this.url : altUrl; + + // if url is not a string, it should be an array of strings, + // in which case we will randomly select one of them in order + // to evenly distribute requests to different urls. + if (typeof url == "object") { + url = url[Math.floor(Math.random()*url.length)]; + } + // requestString always starts with url + var requestString = url; + + var tileRowGroup = ""; + var tileColGroup = ""; + + if (newParams.tilerow < 0) { + tileRowGroup = '-'; + } + + if (newParams.tilerow == 0 ) { + tileRowGroup += '0'; + } else { + tileRowGroup += Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder)) * this.params.tileRowsPerFolder; + } + + if (newParams.tilecol < 0) { + tileColGroup = '-'; + } + + if (newParams.tilecol == 0) { + tileColGroup += '0'; + } else { + tileColGroup += Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder)) * this.params.tileColumnsPerFolder; + } + + var tilePath = '/S' + Math.floor(newParams.scaleindex) + + '/' + this.params.basemaplayergroupname + + '/R' + tileRowGroup + + '/C' + tileColGroup + + '/' + (newParams.tilerow % this.params.tileRowsPerFolder) + + '_' + (newParams.tilecol % this.params.tileColumnsPerFolder) + + '.' + this.params.format; + + if (this.params.querystring) { + tilePath += "?" + this.params.querystring; + } + + requestString += tilePath; + return requestString; + }, + + /** + * Method: calculateGridLayout + * Generate parameters for the grid layout. This + * + * Parameters: + * bounds - {} + * extent - {} + * resolution - {Number} + * + * Returns: + * Object containing properties tilelon, tilelat, tileoffsetlat, + * tileoffsetlat, tileoffsetx, tileoffsety + */ + calculateGridLayout: function(bounds, extent, resolution) { + var tilelon = resolution * this.tileSize.w; + var tilelat = resolution * this.tileSize.h; + + var offsetlon = bounds.left - extent.left; + var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; + var tilecolremain = offsetlon/tilelon - tilecol; + var tileoffsetx = -tilecolremain * this.tileSize.w; + var tileoffsetlon = extent.left + tilecol * tilelon; + + var offsetlat = extent.top - bounds.top + tilelat; + var tilerow = Math.floor(offsetlat/tilelat) - this.buffer; + var tilerowremain = tilerow - offsetlat/tilelat; + var tileoffsety = tilerowremain * this.tileSize.h; + var tileoffsetlat = extent.top - tilelat*tilerow; + + return { + tilelon: tilelon, tilelat: tilelat, + tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat, + tileoffsetx: tileoffsetx, tileoffsety: tileoffsety + }; + }, + + CLASS_NAME: "OpenLayers.Layer.MapGuide" +}); +