diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Layer')
33 files changed, 10663 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js b/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js new file mode 100644 index 0000000..c5bac36 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js @@ -0,0 +1,225 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.ArcGIS93Rest + * Instances of OpenLayers.Layer.ArcGIS93Rest are used to display data from + * ESRI ArcGIS Server 9.3 (and up?) Mapping Services using the REST API. + * Create a new ArcGIS93Rest layer with the <OpenLayers.Layer.ArcGIS93Rest> + * constructor. More detail on the REST API is available at + * http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/index.html ; + * specifically, the URL provided to this layer should be an export service + * URL: http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.ArcGIS93Rest = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Hashtable of default parameter key/value pairs + */ + DEFAULT_PARAMS: { + format: "png" + }, + + /** + * APIProperty: isBaseLayer + * {Boolean} Default is true for ArcGIS93Rest layer + */ + isBaseLayer: true, + + + /** + * Constructor: OpenLayers.Layer.ArcGIS93Rest + * Create a new ArcGIS93Rest layer object. + * + * Example: + * (code) + * var arcims = new OpenLayers.Layer.ArcGIS93Rest("MyName", + * "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/export", + * { + * layers: "0,1,2" + * }); + * (end) + * + * Parameters: + * name - {String} A name for the layer + * url - {String} Base url for the ArcGIS server REST service + * options - {Object} An object with key/value pairs representing the + * options and option values. + * + * Valid Options: + * format - {String} MIME type of desired image type. + * layers - {String} Comma-separated list of layers to display. + * srs - {String} Projection ID. + */ + initialize: function(name, url, params, options) { + var newArguments = []; + //uppercase params + params = OpenLayers.Util.upperCaseObject(params); + newArguments.push(name, url, params, options); + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + OpenLayers.Util.applyDefaults( + this.params, + OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS) + ); + + //layer is transparent + if (this.params.TRANSPARENT && + this.params.TRANSPARENT.toString().toLowerCase() == "true") { + + // unless explicitly set in options, make layer an overlay + if ( (options == null) || (!options.isBaseLayer) ) { + this.isBaseLayer = false; + } + + // jpegs can never be transparent, so intelligently switch the + // format, depending on the browser's capabilities + if (this.params.FORMAT == "jpg") { + this.params.FORMAT = OpenLayers.Util.alphaHack() ? "gif" + : "png"; + } + } + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {<OpenLayers.Layer.ArcGIS93Rest>} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.ArcGIS93Rest(this.name, + this.url, + this.params, + 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: getURL + * Return an image url this layer. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the + * request. + * + * Returns: + * {String} A string with the map image's url. + */ + getURL: function (bounds) { + bounds = this.adjustBounds(bounds); + + // ArcGIS Server only wants the numeric portion of the projection ID. + var projWords = this.projection.getCode().split(":"); + var srid = projWords[projWords.length - 1]; + + var imageSize = this.getImageSize(); + var newParams = { + 'BBOX': bounds.toBBOX(), + 'SIZE': imageSize.w + "," + imageSize.h, + // We always want image, the other options were json, image with a whole lotta html around it, etc. + 'F': "image", + 'BBOXSR': srid, + 'IMAGESR': srid + }; + + // Now add the filter parameters. + if (this.layerDefs) { + var layerDefStrList = []; + var layerID; + for(layerID in this.layerDefs) { + if (this.layerDefs.hasOwnProperty(layerID)) { + if (this.layerDefs[layerID]) { + layerDefStrList.push(layerID); + layerDefStrList.push(":"); + layerDefStrList.push(this.layerDefs[layerID]); + layerDefStrList.push(";"); + } + } + } + if (layerDefStrList.length > 0) { + newParams['LAYERDEFS'] = layerDefStrList.join(""); + } + } + var requestString = this.getFullRequestString(newParams); + return requestString; + }, + + /** + * Method: setLayerFilter + * addTile creates a tile, initializes it, and adds it to the layer div. + * + * Parameters: + * id - {String} The id of the layer to which the filter applies. + * queryDef - {String} A sql-ish query filter, for more detail see the ESRI + * documentation at http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html + */ + setLayerFilter: function ( id, queryDef ) { + if (!this.layerDefs) { + this.layerDefs = {}; + } + if (queryDef) { + this.layerDefs[id] = queryDef; + } else { + delete this.layerDefs[id]; + } + }, + + /** + * Method: clearLayerFilter + * Clears layer filters, either from a specific layer, + * or all of them. + * + * Parameters: + * id - {String} The id of the layer from which to remove any + * filter. If unspecified/blank, all filters + * will be removed. + */ + clearLayerFilter: function ( id ) { + if (id) { + delete this.layerDefs[id]; + } else { + delete this.layerDefs; + } + }, + + /** + * APIMethod: mergeNewParams + * Catch changeParams and uppercase the new params to be merged in + * before calling changeParams on the super class. + * + * Once params have been changed, the tiles will be reloaded with + * the new parameters. + * + * Parameters: + * newParams - {Object} Hashtable of new params to use + */ + mergeNewParams:function(newParams) { + var upperParams = OpenLayers.Util.upperCaseObject(newParams); + var newArguments = [upperParams]; + return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this, + newArguments); + }, + + CLASS_NAME: "OpenLayers.Layer.ArcGIS93Rest" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js b/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js new file mode 100644 index 0000000..99f7dda --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js @@ -0,0 +1,480 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * Class: OpenLayers.Layer.ArcGISCache + * Layer for accessing cached map tiles from an ArcGIS Server style mapcache. + * Tile must already be cached for this layer to access it. This does not require + * ArcGIS Server itself. + * + * A few attempts have been made at this kind of layer before. See + * http://trac.osgeo.org/openlayers/ticket/1967 + * and + * http://trac.osgeo.org/openlayers/browser/sandbox/tschaub/arcgiscache/lib/OpenLayers/Layer/ArcGISCache.js + * + * Typically the problem encountered is that the tiles seem to "jump around". + * This is due to the fact that the actual max extent for the tiles on AGS layers + * changes at each zoom level due to the way these caches are constructed. + * We have attempted to use the resolutions, tile size, and tile origin + * from the cache meta data to make the appropriate changes to the max extent + * of the tile to compensate for this behavior. This must be done as zoom levels change + * and before tiles are requested, which is why methods from base classes are overridden. + * + * For reference, you can access mapcache meta data in two ways. For accessing a + * mapcache through ArcGIS Server, you can simply go to the landing page for the + * layer. (ie. http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer) + * For accessing it directly through HTTP, there should always be a conf.xml file + * in the root directory. + * (ie. http://serverx.esri.com/arcgiscache/DG_County_roads_yesA_backgroundDark/Layers/conf.xml) + * + *Inherits from: + * - <OpenLayers.Layer.XYZ> + */ +OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * APIProperty: url + * {String | Array} The base URL for the layer cache. You can also + * provide a list of URL strings for the layer if your cache is + * available from multiple origins. This must be set before the layer + * is drawn. + */ + url: null, + + /** + * APIProperty: tileOrigin + * {<OpenLayers.LonLat>} The location of the tile origin for the cache. + * An ArcGIS cache has it's origin at the upper-left (lowest x value + * and highest y value of the coordinate system). The units for the + * tile origin should be the same as the units for the cached data. + */ + tileOrigin: null, + + /** + * APIProperty: tileSize + * {<OpenLayers.Size>} This size of each tile. Defaults to 256 by 256 pixels. + */ + tileSize: new OpenLayers.Size(256, 256), + + /** + * APIProperty: useAGS + * {Boolean} Indicates if we are going to be accessing the ArcGIS Server (AGS) + * cache via an AGS MapServer or directly through HTTP. When accessing via + * AGS the path structure uses a standard z/y/x structure. But AGS actually + * stores the tile images on disk using a hex based folder structure that looks + * like "http://example.com/mylayer/L00/R00000000/C00000000.png". Learn more + * about this here: + * http://blogs.esri.com/Support/blogs/mappingcenter/archive/2010/08/20/Checking-Your-Local-Cache-Folders.aspx + * Defaults to true; + */ + useArcGISServer: true, + + /** + * APIProperty: type + * {String} Image type for the layer. This becomes the filename extension + * in tile requests. Default is "png" (generating a url like + * "http://example.com/mylayer/L00/R00000000/C00000000.png"). + */ + type: 'png', + + /** + * APIProperty: useScales + * {Boolean} Optional override to indicate that the layer should use 'scale' information + * returned from the server capabilities object instead of 'resolution' information. + * This can be important if your tile server uses an unusual DPI for the tiles. + */ + useScales: false, + + /** + * APIProperty: overrideDPI + * {Boolean} Optional override to change the OpenLayers.DOTS_PER_INCH setting based + * on the tile information in the server capabilities object. This can be useful + * if your server has a non-standard DPI setting on its tiles, and you're only using + * tiles with that DPI. This value is used while OpenLayers is calculating resolution + * using scales, and is not necessary if you have resolution information. (This is + * typically the case) Regardless, this setting can be useful, but is dangerous + * because it will impact other layers while calculating resolution. Only use this + * if you know what you are doing. (See OpenLayers.Util.getResolutionFromScale) + */ + overrideDPI: false, + + /** + * Constructor: OpenLayers.Layer.ArcGISCache + * Creates a new instance of this class + * + * Parameters: + * name - {String} + * url - {String} + * options - {Object} extra layer options + */ + initialize: function(name, url, options) { + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments); + + if (this.resolutions) { + this.serverResolutions = this.resolutions; + this.maxExtent = this.getMaxExtentForResolution(this.resolutions[0]); + } + + // this block steps through translating the values from the server layer JSON + // capabilities object into values that we can use. This is also a helpful + // reference when configuring this layer directly. + if (this.layerInfo) { + // alias the object + var info = this.layerInfo; + + // build our extents + var startingTileExtent = new OpenLayers.Bounds( + info.fullExtent.xmin, + info.fullExtent.ymin, + info.fullExtent.xmax, + info.fullExtent.ymax + ); + + // set our projection based on the given spatial reference. + // esri uses slightly different IDs, so this may not be comprehensive + this.projection = 'EPSG:' + info.spatialReference.wkid; + this.sphericalMercator = (info.spatialReference.wkid == 102100); + + // convert esri units into openlayers units (basic feet or meters only) + this.units = (info.units == "esriFeet") ? 'ft' : 'm'; + + // optional extended section based on whether or not the server returned + // specific tile information + if (!!info.tileInfo) { + // either set the tiles based on rows/columns, or specific width/height + this.tileSize = new OpenLayers.Size( + info.tileInfo.width || info.tileInfo.cols, + info.tileInfo.height || info.tileInfo.rows + ); + + // this must be set when manually configuring this layer + this.tileOrigin = new OpenLayers.LonLat( + info.tileInfo.origin.x, + info.tileInfo.origin.y + ); + + var upperLeft = new OpenLayers.Geometry.Point( + startingTileExtent.left, + startingTileExtent.top + ); + + var bottomRight = new OpenLayers.Geometry.Point( + startingTileExtent.right, + startingTileExtent.bottom + ); + + if (this.useScales) { + this.scales = []; + } else { + this.resolutions = []; + } + + this.lods = []; + for(var key in info.tileInfo.lods) { + if (info.tileInfo.lods.hasOwnProperty(key)) { + var lod = info.tileInfo.lods[key]; + if (this.useScales) { + this.scales.push(lod.scale); + } else { + this.resolutions.push(lod.resolution); + } + + var start = this.getContainingTileCoords(upperLeft, lod.resolution); + lod.startTileCol = start.x; + lod.startTileRow = start.y; + + var end = this.getContainingTileCoords(bottomRight, lod.resolution); + lod.endTileCol = end.x; + lod.endTileRow = end.y; + this.lods.push(lod); + } + } + + this.maxExtent = this.calculateMaxExtentWithLOD(this.lods[0]); + this.serverResolutions = this.resolutions; + if (this.overrideDPI && info.tileInfo.dpi) { + // see comment above for 'overrideDPI' + OpenLayers.DOTS_PER_INCH = info.tileInfo.dpi; + } + } + } + }, + + /** + * Method: getContainingTileCoords + * Calculates the x/y pixel corresponding to the position of the tile + * that contains the given point and for the for the given resolution. + * + * Parameters: + * point - {<OpenLayers.Geometry.Point>} + * res - {Float} The resolution for which to compute the extent. + * + * Returns: + * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position + * of the upper left tile for the given resolution. + */ + getContainingTileCoords: function(point, res) { + return new OpenLayers.Pixel( + Math.max(Math.floor((point.x - this.tileOrigin.lon) / (this.tileSize.w * res)),0), + Math.max(Math.floor((this.tileOrigin.lat - point.y) / (this.tileSize.h * res)),0) + ); + }, + + /** + * Method: calculateMaxExtentWithLOD + * Given a Level of Detail object from the server, this function + * calculates the actual max extent + * + * Parameters: + * lod - {Object} a Level of Detail Object from the server capabilities object + representing a particular zoom level + * + * Returns: + * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level + */ + calculateMaxExtentWithLOD: function(lod) { + // the max extent we're provided with just overlaps some tiles + // our real extent is the bounds of all the tiles we touch + + var numTileCols = (lod.endTileCol - lod.startTileCol) + 1; + var numTileRows = (lod.endTileRow - lod.startTileRow) + 1; + + var minX = this.tileOrigin.lon + (lod.startTileCol * this.tileSize.w * lod.resolution); + var maxX = minX + (numTileCols * this.tileSize.w * lod.resolution); + + var maxY = this.tileOrigin.lat - (lod.startTileRow * this.tileSize.h * lod.resolution); + var minY = maxY - (numTileRows * this.tileSize.h * lod.resolution); + return new OpenLayers.Bounds(minX, minY, maxX, maxY); + }, + + /** + * Method: calculateMaxExtentWithExtent + * Given a 'suggested' max extent from the server, this function uses + * information about the actual tile sizes to determine the actual + * extent of the layer. + * + * Parameters: + * extent - {<OpenLayers.Bounds>} The 'suggested' extent for the layer + * res - {Float} The resolution for which to compute the extent. + * + * Returns: + * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level + */ + calculateMaxExtentWithExtent: function(extent, res) { + var upperLeft = new OpenLayers.Geometry.Point(extent.left, extent.top); + var bottomRight = new OpenLayers.Geometry.Point(extent.right, extent.bottom); + var start = this.getContainingTileCoords(upperLeft, res); + var end = this.getContainingTileCoords(bottomRight, res); + var lod = { + resolution: res, + startTileCol: start.x, + startTileRow: start.y, + endTileCol: end.x, + endTileRow: end.y + }; + return this.calculateMaxExtentWithLOD(lod); + }, + + /** + * Method: getUpperLeftTileCoord + * Calculates the x/y pixel corresponding to the position + * of the upper left tile for the given resolution. + * + * Parameters: + * res - {Float} The resolution for which to compute the extent. + * + * Returns: + * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position + * of the upper left tile for the given resolution. + */ + getUpperLeftTileCoord: function(res) { + var upperLeft = new OpenLayers.Geometry.Point( + this.maxExtent.left, + this.maxExtent.top); + return this.getContainingTileCoords(upperLeft, res); + }, + + /** + * Method: getLowerRightTileCoord + * Calculates the x/y pixel corresponding to the position + * of the lower right tile for the given resolution. + * + * Parameters: + * res - {Float} The resolution for which to compute the extent. + * + * Returns: + * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position + * of the lower right tile for the given resolution. + */ + getLowerRightTileCoord: function(res) { + var bottomRight = new OpenLayers.Geometry.Point( + this.maxExtent.right, + this.maxExtent.bottom); + return this.getContainingTileCoords(bottomRight, res); + }, + + /** + * Method: getMaxExtentForResolution + * Since the max extent of a set of tiles can change from zoom level + * to zoom level, we need to be able to calculate that max extent + * for a given resolution. + * + * Parameters: + * res - {Float} The resolution for which to compute the extent. + * + * Returns: + * {<OpenLayers.Bounds>} The extent for this resolution + */ + getMaxExtentForResolution: function(res) { + var start = this.getUpperLeftTileCoord(res); + var end = this.getLowerRightTileCoord(res); + + var numTileCols = (end.x - start.x) + 1; + var numTileRows = (end.y - start.y) + 1; + + var minX = this.tileOrigin.lon + (start.x * this.tileSize.w * res); + var maxX = minX + (numTileCols * this.tileSize.w * res); + + var maxY = this.tileOrigin.lat - (start.y * this.tileSize.h * res); + var minY = maxY - (numTileRows * this.tileSize.h * res); + return new OpenLayers.Bounds(minX, minY, maxX, maxY); + }, + + /** + * APIMethod: clone + * Returns an exact clone of this OpenLayers.Layer.ArcGISCache + * + * Parameters: + * [obj] - {Object} optional object to assign the cloned instance to. + * + * Returns: + * {<OpenLayers.Layer.ArcGISCache>} clone of this instance + */ + clone: function (obj) { + if (obj == null) { + obj = new OpenLayers.Layer.ArcGISCache(this.name, this.url, this.options); + } + return OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + }, + + /** + * Method: initGriddedTiles + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + */ + initGriddedTiles: function(bounds) { + delete this._tileOrigin; + OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this, arguments); + }, + + /** + * Method: getMaxExtent + * Get this layer's maximum extent. + * + * Returns: + * {<OpenLayers.Bounds>} + */ + getMaxExtent: function() { + var resolution = this.map.getResolution(); + return this.maxExtent = this.getMaxExtentForResolution(resolution); + }, + + /** + * Method: getTileOrigin + * Determine the origin for aligning the grid of tiles. + * The origin will be derived from the layer's <maxExtent> property. + * + * Returns: + * {<OpenLayers.LonLat>} The tile origin. + */ + getTileOrigin: function() { + if (!this._tileOrigin) { + var extent = this.getMaxExtent(); + this._tileOrigin = new OpenLayers.LonLat(extent.left, extent.bottom); + } + return this._tileOrigin; + }, + + /** + * Method: getURL + * Determine the URL for a tile given the tile bounds. This is should support + * urls that access tiles through an ArcGIS Server MapServer or directly through + * the hex folder structure using HTTP. Just be sure to set the useArcGISServer + * property appropriately! This is basically the same as + * 'OpenLayers.Layer.TMS.getURL', but with the addition of hex addressing, + * and tile rounding. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * Returns: + * {String} The URL for a tile based on given bounds. + */ + getURL: function (bounds) { + var res = this.getResolution(); + + // tile center + var originTileX = (this.tileOrigin.lon + (res * this.tileSize.w/2)); + var originTileY = (this.tileOrigin.lat - (res * this.tileSize.h/2)); + + var center = bounds.getCenterLonLat(); + var point = { x: center.lon, y: center.lat }; + var x = (Math.round(Math.abs((center.lon - originTileX) / (res * this.tileSize.w)))); + var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h)))); + var z = this.map.getZoom(); + + // this prevents us from getting pink tiles (non-existant tiles) + if (this.lods) { + var lod = this.lods[this.map.getZoom()]; + if ((x < lod.startTileCol || x > lod.endTileCol) + || (y < lod.startTileRow || y > lod.endTileRow)) { + return null; + } + } + else { + var start = this.getUpperLeftTileCoord(res); + var end = this.getLowerRightTileCoord(res); + if ((x < start.x || x >= end.x) + || (y < start.y || y >= end.y)) { + return null; + } + } + + // Construct the url string + var url = this.url; + var s = '' + x + y + z; + + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(s, url); + } + + // Accessing tiles through ArcGIS Server uses a different path + // structure than direct access via the folder structure. + if (this.useArcGISServer) { + // AGS MapServers have pretty url access to tiles + url = url + '/tile/${z}/${y}/${x}'; + } else { + // The tile images are stored using hex values on disk. + x = 'C' + OpenLayers.Number.zeroPad(x, 8, 16); + y = 'R' + OpenLayers.Number.zeroPad(y, 8, 16); + z = 'L' + OpenLayers.Number.zeroPad(z, 2, 10); + url = url + '/${z}/${y}/${x}.' + this.type; + } + + // Write the values into our formatted url + url = OpenLayers.String.format(url, {'x': x, 'y': y, 'z': z}); + + return OpenLayers.Util.urlAppend( + url, OpenLayers.Util.getParameterString(this.params) + ); + }, + + CLASS_NAME: 'OpenLayers.Layer.ArcGISCache' +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js b/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js new file mode 100644 index 0000000..e19584c --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js @@ -0,0 +1,425 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.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 <OpenLayers.Layer.ArcIMS> + * constructor. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Default query string parameters. + */ + DEFAULT_PARAMS: { + ClientVersion: "9.2", + ServiceName: '' + }, + + /** + * 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: getURL + * Return an image url this layer. + * + * Parameters: + * bounds - {<OpenLayers.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 - {<OpenLayers.Bounds>} A bounds representing the bbox for the + * request. + * callback - {Function} Function to call when image url is retrieved. + * scope - {Object} The scope of the callback method. + */ + getURLasync: function(bounds, callback, scope) { + 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); + + callback.call(scope, this.getUrlOrImage(arcxml.image.output)); + }, + 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 - {<OpenLayers.LonLat>} or {<OpenLayers.Geometry.Polygon>} 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: + * {<OpenLayers.Layer.ArcIMS>} 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; + }, + + CLASS_NAME: "OpenLayers.Layer.ArcIMS" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Bing.js b/misc/openlayers/lib/OpenLayers/Layer/Bing.js new file mode 100644 index 0000000..0615285 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Bing.js @@ -0,0 +1,333 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * Class: OpenLayers.Layer.Bing + * Bing layer using direct tile access as provided by Bing Maps REST Services. + * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more + * information. Note: Terms of Service compliant use requires the map to be + * configured with an <OpenLayers.Control.Attribution> control and the + * attribution placed on or near the map. + * + * Inherits from: + * - <OpenLayers.Layer.XYZ> + */ +OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * Property: key + * {String} API key for Bing maps, get your own key + * at http://bingmapsportal.com/ . + */ + key: null, + + /** + * Property: serverResolutions + * {Array} the resolutions provided by the Bing servers. + */ + serverResolutions: [ + 156543.03390625, 78271.516953125, 39135.7584765625, + 19567.87923828125, 9783.939619140625, 4891.9698095703125, + 2445.9849047851562, 1222.9924523925781, 611.4962261962891, + 305.74811309814453, 152.87405654907226, 76.43702827453613, + 38.218514137268066, 19.109257068634033, 9.554628534317017, + 4.777314267158508, 2.388657133579254, 1.194328566789627, + 0.5971642833948135, 0.29858214169740677, 0.14929107084870338, + 0.07464553542435169 + ], + + /** + * Property: attributionTemplate + * {String} + */ + attributionTemplate: '<span class="olBingAttribution ${type}">' + + '<div><a target="_blank" href="http://www.bing.com/maps/">' + + '<img src="${logo}" /></a></div>${copyrights}' + + '<a style="white-space: nowrap" target="_blank" '+ + 'href="http://www.microsoft.com/maps/product/terms.html">' + + 'Terms of Use</a></span>', + + /** + * Property: metadata + * {Object} Metadata for this layer, as returned by the callback script + */ + metadata: null, + + /** + * Property: protocolRegex + * {RegExp} Regular expression to match and replace http: in bing urls + */ + protocolRegex: /^http:/i, + + /** + * APIProperty: type + * {String} The layer identifier. Any non-birdseye imageryType + * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be + * used. Default is "Road". + */ + type: "Road", + + /** + * APIProperty: culture + * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx + * for the definition and the possible values. Default is "en-US". + */ + culture: "en-US", + + /** + * APIProperty: metadataParams + * {Object} Optional url parameters for the Get Imagery Metadata request + * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx + */ + metadataParams: null, + + /** APIProperty: tileOptions + * {Object} optional configuration options for <OpenLayers.Tile> instances + * created by this Layer. Default is + * + * (code) + * {crossOriginKeyword: 'anonymous'} + * (end) + */ + tileOptions: null, + + /** APIProperty: protocol + * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo + * Can be 'http:' 'https:' or '' + * + * Warning: tiles may not be available under both HTTP and HTTPS protocols. + * Microsoft approved use of both HTTP and HTTPS urls for tiles. However + * this is undocumented and the Imagery Metadata API always returns HTTP + * urls. + * + * Default is '', unless when executed from a file:/// uri, in which case + * it is 'http:'. + */ + protocol: ~window.location.href.indexOf('http') ? '' : 'http:', + + /** + * Constructor: OpenLayers.Layer.Bing + * Create a new Bing layer. + * + * Example: + * (code) + * var road = new OpenLayers.Layer.Bing({ + * name: "My Bing Aerial Layer", + * type: "Aerial", + * key: "my-api-key-here", + * }); + * (end) + * + * Parameters: + * options - {Object} Configuration properties for the layer. + * + * Required configuration properties: + * key - {String} Bing Maps API key for your application. Get one at + * http://bingmapsportal.com/. + * type - {String} The layer identifier. Any non-birdseye imageryType + * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be + * used. + * + * Any other documented layer properties can be provided in the config object. + */ + initialize: function(options) { + options = OpenLayers.Util.applyDefaults({ + sphericalMercator: true + }, options); + var name = options.name || "Bing " + (options.type || this.type); + + var newArgs = [name, null, options]; + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs); + this.tileOptions = OpenLayers.Util.extend({ + crossOriginKeyword: 'anonymous' + }, this.options.tileOptions); + this.loadMetadata(); + }, + + /** + * Method: loadMetadata + */ + loadMetadata: function() { + this._callbackId = "_callback_" + this.id.replace(/\./g, "_"); + // link the processMetadata method to the global scope and bind it + // to this instance + window[this._callbackId] = OpenLayers.Function.bind( + OpenLayers.Layer.Bing.processMetadata, this + ); + var params = OpenLayers.Util.applyDefaults({ + key: this.key, + jsonp: this._callbackId, + include: "ImageryProviders" + }, this.metadataParams); + var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" + + this.type + "?" + OpenLayers.Util.getParameterString(params); + var script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + script.id = this._callbackId; + document.getElementsByTagName("head")[0].appendChild(script); + }, + + /** + * Method: initLayer + * + * Sets layer properties according to the metadata provided by the API + */ + initLayer: function() { + var res = this.metadata.resourceSets[0].resources[0]; + var url = res.imageUrl.replace("{quadkey}", "${quadkey}"); + url = url.replace("{culture}", this.culture); + url = url.replace(this.protocolRegex, this.protocol); + this.url = []; + for (var i=0; i<res.imageUrlSubdomains.length; ++i) { + this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i])); + } + this.addOptions({ + maxResolution: Math.min( + this.serverResolutions[res.zoomMin], + this.maxResolution || Number.POSITIVE_INFINITY + ), + numZoomLevels: Math.min( + res.zoomMax + 1 - res.zoomMin, this.numZoomLevels + ) + }, true); + if (!this.isBaseLayer) { + this.redraw(); + } + this.updateAttribution(); + }, + + /** + * Method: getURL + * + * Paramters: + * bounds - {<OpenLayers.Bounds>} + */ + getURL: function(bounds) { + if (!this.url) { + return; + } + var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z; + var quadDigits = []; + for (var i = z; i > 0; --i) { + var digit = '0'; + var mask = 1 << (i - 1); + if ((x & mask) != 0) { + digit++; + } + if ((y & mask) != 0) { + digit++; + digit++; + } + quadDigits.push(digit); + } + var quadKey = quadDigits.join(""); + var url = this.selectUrl('' + x + y + z, this.url); + + return OpenLayers.String.format(url, {'quadkey': quadKey}); + }, + + /** + * Method: updateAttribution + * Updates the attribution according to the requirements outlined in + * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html + */ + updateAttribution: function() { + var metadata = this.metadata; + if (!metadata.resourceSets || !this.map || !this.map.center) { + return; + } + var res = metadata.resourceSets[0].resources[0]; + var extent = this.map.getExtent().transform( + this.map.getProjectionObject(), + new OpenLayers.Projection("EPSG:4326") + ); + var providers = res.imageryProviders || [], + zoom = OpenLayers.Util.indexOf(this.serverResolutions, + this.getServerResolution()), + copyrights = "", provider, i, ii, j, jj, bbox, coverage; + for (i=0,ii=providers.length; i<ii; ++i) { + provider = providers[i]; + for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) { + coverage = provider.coverageAreas[j]; + // axis order provided is Y,X + bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true); + if (extent.intersectsBounds(bbox) && + zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) { + copyrights += provider.attribution + " "; + } + } + } + var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol); + this.attribution = OpenLayers.String.format(this.attributionTemplate, { + type: this.type.toLowerCase(), + logo: logo, + copyrights: copyrights + }); + this.map && this.map.events.triggerEvent("changelayer", { + layer: this, + property: "attribution" + }); + }, + + /** + * Method: setMap + */ + setMap: function() { + OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments); + this.map.events.register("moveend", this, this.updateAttribution); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing> + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.Bing(this.options); + } + //get all additions from superclasses + obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + // copy/set any non-init, non-simple values here + return obj; + }, + + /** + * Method: destroy + */ + destroy: function() { + this.map && + this.map.events.unregister("moveend", this, this.updateAttribution); + OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Layer.Bing" +}); + +/** + * Function: OpenLayers.Layer.Bing.processMetadata + * This function will be bound to an instance, linked to the global scope with + * an id, and called by the JSONP script returned by the API. + * + * Parameters: + * metadata - {Object} metadata as returned by the API + */ +OpenLayers.Layer.Bing.processMetadata = function(metadata) { + this.metadata = metadata; + this.initLayer(); + var script = document.getElementById(this._callbackId); + script.parentNode.removeChild(script); + window[this._callbackId] = undefined; // cannot delete from window in IE + delete this._callbackId; +}; diff --git a/misc/openlayers/lib/OpenLayers/Layer/Boxes.js b/misc/openlayers/lib/OpenLayers/Layer/Boxes.js new file mode 100644 index 0000000..7cd605a --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Boxes.js @@ -0,0 +1,76 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Layer/Markers.js + */ + +/** + * Class: OpenLayers.Layer.Boxes + * Draw divs as 'boxes' on the layer. + * + * Inherits from: + * - <OpenLayers.Layer.Markers> + */ +OpenLayers.Layer.Boxes = OpenLayers.Class(OpenLayers.Layer.Markers, { + + /** + * Constructor: OpenLayers.Layer.Boxes + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + + /** + * Method: drawMarker + * Calculate the pixel location for the marker, create it, and + * add it to the layer's div + * + * Parameters: + * marker - {<OpenLayers.Marker.Box>} + */ + drawMarker: function(marker) { + var topleft = this.map.getLayerPxFromLonLat({ + lon: marker.bounds.left, + lat: marker.bounds.top + }); + var botright = this.map.getLayerPxFromLonLat({ + lon: marker.bounds.right, + lat: marker.bounds.bottom + }); + if (botright == null || topleft == null) { + marker.display(false); + } else { + var markerDiv = marker.draw(topleft, { + w: Math.max(1, botright.x - topleft.x), + h: Math.max(1, botright.y - topleft.y) + }); + if (!marker.drawn) { + this.div.appendChild(markerDiv); + marker.drawn = true; + } + } + }, + + + /** + * APIMethod: removeMarker + * + * Parameters: + * marker - {<OpenLayers.Marker.Box>} + */ + removeMarker: function(marker) { + OpenLayers.Util.removeItem(this.markers, marker); + if ((marker.div != null) && + (marker.div.parentNode == this.div) ) { + this.div.removeChild(marker.div); + } + }, + + CLASS_NAME: "OpenLayers.Layer.Boxes" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/EventPane.js b/misc/openlayers/lib/OpenLayers/Layer/EventPane.js new file mode 100644 index 0000000..15a852f --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/EventPane.js @@ -0,0 +1,441 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Layer.EventPane + * Base class for 3rd party layers, providing a DOM element which isolates + * the 3rd-party layer from mouse events. + * Only used by Google layers. + * + * Automatically instantiated by the Google constructor, and not usually instantiated directly. + * + * Create a new event pane layer with the + * <OpenLayers.Layer.EventPane> constructor. + * + * Inherits from: + * - <OpenLayers.Layer> + */ +OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, { + + /** + * APIProperty: smoothDragPan + * {Boolean} smoothDragPan determines whether non-public/internal API + * methods are used for better performance while dragging EventPane + * layers. When not in sphericalMercator mode, the smoother dragging + * doesn't actually move north/south directly with the number of + * pixels moved, resulting in a slight offset when you drag your mouse + * north south with this option on. If this visual disparity bothers + * you, you should turn this option off, or use spherical mercator. + * Default is on. + */ + smoothDragPan: true, + + /** + * Property: isBaseLayer + * {Boolean} EventPaned layers are always base layers, by necessity. + */ + isBaseLayer: true, + + /** + * APIProperty: isFixed + * {Boolean} EventPaned layers are fixed by default. + */ + isFixed: true, + + /** + * Property: pane + * {DOMElement} A reference to the element that controls the events. + */ + pane: null, + + + /** + * Property: mapObject + * {Object} This is the object which will be used to load the 3rd party library + * in the case of the google layer, this will be of type GMap, + * in the case of the ve layer, this will be of type VEMap + */ + mapObject: null, + + + /** + * Constructor: OpenLayers.Layer.EventPane + * Create a new event pane layer + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + if (this.pane == null) { + this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane"); + } + }, + + /** + * APIMethod: destroy + * Deconstruct this layer. + */ + destroy: function() { + this.mapObject = null; + this.pane = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + + /** + * Method: setMap + * Set the map property for the layer. This is done through an accessor + * so that subclasses can override this and take special action once + * they have their map variable set. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + + this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; + this.pane.style.display = this.div.style.display; + this.pane.style.width="100%"; + this.pane.style.height="100%"; + if (OpenLayers.BROWSER_NAME == "msie") { + this.pane.style.background = + "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")"; + } + + if (this.isFixed) { + this.map.viewPortDiv.appendChild(this.pane); + } else { + this.map.layerContainerDiv.appendChild(this.pane); + } + + // once our layer has been added to the map, we can load it + this.loadMapObject(); + + // if map didn't load, display warning + if (this.mapObject == null) { + this.loadWarningMessage(); + } + }, + + /** + * APIMethod: removeMap + * On being removed from the map, we'll like to remove the invisible 'pane' + * div that we added to it on creation. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + if (this.pane && this.pane.parentNode) { + this.pane.parentNode.removeChild(this.pane); + } + OpenLayers.Layer.prototype.removeMap.apply(this, arguments); + }, + + /** + * Method: loadWarningMessage + * If we can't load the map lib, then display an error message to the + * user and tell them where to go for help. + * + * This function sets up the layout for the warning message. Each 3rd + * party layer must implement its own getWarningHTML() function to + * provide the actual warning message. + */ + loadWarningMessage:function() { + + this.div.style.backgroundColor = "darkblue"; + + var viewSize = this.map.getSize(); + + var msgW = Math.min(viewSize.w, 300); + var msgH = Math.min(viewSize.h, 200); + var size = new OpenLayers.Size(msgW, msgH); + + var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2); + + var topLeft = centerPx.add(-size.w/2, -size.h/2); + + var div = OpenLayers.Util.createDiv(this.name + "_warning", + topLeft, + size, + null, + null, + null, + "auto"); + + div.style.padding = "7px"; + div.style.backgroundColor = "yellow"; + + div.innerHTML = this.getWarningHTML(); + this.div.appendChild(div); + }, + + /** + * Method: getWarningHTML + * To be implemented by subclasses. + * + * Returns: + * {String} String with information on why layer is broken, how to get + * it working. + */ + getWarningHTML:function() { + //should be implemented by subclasses + return ""; + }, + + /** + * Method: display + * Set the display on the pane + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + OpenLayers.Layer.prototype.display.apply(this, arguments); + this.pane.style.display = this.div.style.display; + }, + + /** + * Method: setZIndex + * Set the z-index order for the pane. + * + * Parameters: + * zIndex - {int} + */ + setZIndex: function (zIndex) { + OpenLayers.Layer.prototype.setZIndex.apply(this, arguments); + this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1; + }, + + /** + * Method: moveByPx + * Move the layer based on pixel vector. To be implemented by subclasses. + * + * Parameters: + * dx - {Number} The x coord of the displacement vector. + * dy - {Number} The y coord of the displacement vector. + */ + moveByPx: function(dx, dy) { + OpenLayers.Layer.prototype.moveByPx.apply(this, arguments); + + if (this.dragPanMapObject) { + this.dragPanMapObject(dx, -dy); + } else { + this.moveTo(this.map.getCachedCenter()); + } + }, + + /** + * Method: moveTo + * Handle calls to move the layer. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + if (this.mapObject != null) { + + var newCenter = this.map.getCenter(); + var newZoom = this.map.getZoom(); + + if (newCenter != null) { + + var moOldCenter = this.getMapObjectCenter(); + var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter); + + var moOldZoom = this.getMapObjectZoom(); + var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom); + + if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) { + + if (!zoomChanged && oldCenter && this.dragPanMapObject && + this.smoothDragPan) { + var oldPx = this.map.getViewPortPxFromLonLat(oldCenter); + var newPx = this.map.getViewPortPxFromLonLat(newCenter); + this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y); + } else { + var center = this.getMapObjectLonLatFromOLLonLat(newCenter); + var zoom = this.getMapObjectZoomFromOLZoom(newZoom); + this.setMapObjectCenter(center, zoom, dragging); + } + } + } + } + }, + + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /********************************************************/ + + /** + * Method: getLonLatFromViewPortPx + * Get a map location from a pixel location + * + * Parameters: + * viewPortPx - {<OpenLayers.Pixel>} + * + * Returns: + * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view + * port OpenLayers.Pixel, translated into lon/lat by map lib + * If the map lib is not loaded or not centered, returns null + */ + getLonLatFromViewPortPx: function (viewPortPx) { + var lonlat = null; + if ( (this.mapObject != null) && + (this.getMapObjectCenter() != null) ) { + var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx); + var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel); + lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat); + } + return lonlat; + }, + + + /** + * Method: getViewPortPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {<OpenLayers.LonLat>} + * + * Returns: + * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in + * OpenLayers.LonLat, translated into view port pixels by map lib + * If map lib is not loaded or not centered, returns null + */ + getViewPortPxFromLonLat: function (lonlat) { + var viewPortPx = null; + if ( (this.mapObject != null) && + (this.getMapObjectCenter() != null) ) { + + var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat); + var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat); + + viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel); + } + return viewPortPx; + }, + + /********************************************************/ + /* */ + /* Translation Functions */ + /* */ + /* The following functions translate Map Object and */ + /* OL formats for Pixel, LonLat */ + /* */ + /********************************************************/ + + // + // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat + // + + /** + * Method: getOLLonLatFromMapObjectLonLat + * Get an OL style map location from a 3rd party style map location + * + * Parameters + * moLonLat - {Object} + * + * Returns: + * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in + * MapObject LonLat + * Returns null if null value is passed in + */ + getOLLonLatFromMapObjectLonLat: function(moLonLat) { + var olLonLat = null; + if (moLonLat != null) { + var lon = this.getLongitudeFromMapObjectLonLat(moLonLat); + var lat = this.getLatitudeFromMapObjectLonLat(moLonLat); + olLonLat = new OpenLayers.LonLat(lon, lat); + } + return olLonLat; + }, + + /** + * Method: getMapObjectLonLatFromOLLonLat + * Get a 3rd party map location from an OL map location. + * + * Parameters: + * olLonLat - {<OpenLayers.LonLat>} + * + * Returns: + * {Object} A MapObject LonLat, translated from the passed in + * OpenLayers.LonLat + * Returns null if null value is passed in + */ + getMapObjectLonLatFromOLLonLat: function(olLonLat) { + var moLatLng = null; + if (olLonLat != null) { + moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon, + olLonLat.lat); + } + return moLatLng; + }, + + + // + // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel + // + + /** + * Method: getOLPixelFromMapObjectPixel + * Get an OL pixel location from a 3rd party pixel location. + * + * Parameters: + * moPixel - {Object} + * + * Returns: + * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in + * MapObject Pixel + * Returns null if null value is passed in + */ + getOLPixelFromMapObjectPixel: function(moPixel) { + var olPixel = null; + if (moPixel != null) { + var x = this.getXFromMapObjectPixel(moPixel); + var y = this.getYFromMapObjectPixel(moPixel); + olPixel = new OpenLayers.Pixel(x, y); + } + return olPixel; + }, + + /** + * Method: getMapObjectPixelFromOLPixel + * Get a 3rd party pixel location from an OL pixel location + * + * Parameters: + * olPixel - {<OpenLayers.Pixel>} + * + * Returns: + * {Object} A MapObject Pixel, translated from the passed in + * OpenLayers.Pixel + * Returns null if null value is passed in + */ + getMapObjectPixelFromOLPixel: function(olPixel) { + var moPixel = null; + if (olPixel != null) { + moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y); + } + return moPixel; + }, + + CLASS_NAME: "OpenLayers.Layer.EventPane" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js b/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js new file mode 100644 index 0000000..f647238 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js @@ -0,0 +1,319 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer.js + */ + +/** + * Class: OpenLayers.Layer.FixedZoomLevels + * Some Layers will already have established zoom levels (like google + * or ve). Instead of trying to determine them and populate a resolutions[] + * Array with those values, we will hijack the resolution functionality + * here. + * + * When you subclass FixedZoomLevels: + * + * The initResolutions() call gets nullified, meaning no resolutions[] array + * is set up. Which would be a big problem getResolution() in Layer, since + * it merely takes map.zoom and indexes into resolutions[]... but.... + * + * The getResolution() call is also overridden. Instead of using the + * resolutions[] array, we simply calculate the current resolution based + * on the current extent and the current map size. But how will we be able + * to calculate the current extent without knowing the resolution...? + * + * The getExtent() function is also overridden. Instead of calculating extent + * based on the center point and the current resolution, we instead + * calculate the extent by getting the lonlats at the top-left and + * bottom-right by using the getLonLatFromViewPortPx() translation function, + * taken from the pixel locations (0,0) and the size of the map. But how + * will we be able to do lonlat-px translation without resolution....? + * + * The getZoomForResolution() method is overridden. Instead of indexing into + * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in + * the desired resolution. With this extent, we then call getZoomForExtent() + * + * + * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, + * it is your responsibility to provide the following three functions: + * + * - getLonLatFromViewPortPx + * - getViewPortPxFromLonLat + * - getZoomForExtent + * + * ...those three functions should generally be provided by any reasonable + * API that you might be working from. + * + */ +OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({ + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /* The following functions must all be implemented */ + /* by all base layers */ + /* */ + /********************************************************/ + + /** + * Constructor: OpenLayers.Layer.FixedZoomLevels + * Create a new fixed zoom levels layer. + */ + initialize: function() { + //this class is only just to add the following functions... + // nothing to actually do here... but it is probably a good + // idea to have layers that use these functions call this + // inititalize() anyways, in case at some point we decide we + // do want to put some functionality or state in here. + }, + + /** + * Method: initResolutions + * Populate the resolutions array + */ + initResolutions: function() { + + var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels']; + + for(var i=0, len=props.length; i<len; i++) { + var property = props[i]; + this[property] = (this.options[property] != null) + ? this.options[property] + : this.map[property]; + } + + if ( (this.minZoomLevel == null) || + (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){ + this.minZoomLevel = this.MIN_ZOOM_LEVEL; + } + + // + // At this point, we know what the minimum desired zoom level is, and + // we must calculate the total number of zoom levels. + // + // Because we allow for the setting of either the 'numZoomLevels' + // or the 'maxZoomLevel' properties... on either the layer or the + // map, we have to define some rules to see which we take into + // account first in this calculation. + // + // The following is the precedence list for these properties: + // + // (1) numZoomLevels set on layer + // (2) maxZoomLevel set on layer + // (3) numZoomLevels set on map + // (4) maxZoomLevel set on map* + // (5) none of the above* + // + // *Note that options (4) and (5) are only possible if the user + // _explicitly_ sets the 'numZoomLevels' property on the map to + // null, since it is set by default to 16. + // + + // + // Note to future: In 3.0, I think we should remove the default + // value of 16 for map.numZoomLevels. Rather, I think that value + // should be set as a default on the Layer.WMS class. If someone + // creates a 3rd party layer and does not specify any 'minZoomLevel', + // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly + // specified any of those on the map object either.. then I think + // it is fair to say that s/he wants all the zoom levels available. + // + // By making map.numZoomLevels *null* by default, that will be the + // case. As it is, I don't feel comfortable changing that right now + // as it would be a glaring API change and actually would probably + // break many peoples' codes. + // + + //the number of zoom levels we'd like to have. + var desiredZoomLevels; + + //this is the maximum number of zoom levels the layer will allow, + // given the specified starting minimum zoom level. + var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1; + + if ( ((this.options.numZoomLevels == null) && + (this.options.maxZoomLevel != null)) // (2) + || + ((this.numZoomLevels == null) && + (this.maxZoomLevel != null)) // (4) + ) { + //calculate based on specified maxZoomLevel (on layer or map) + desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1; + } else { + //calculate based on specified numZoomLevels (on layer or map) + // this covers cases (1) and (3) + desiredZoomLevels = this.numZoomLevels; + } + + if (desiredZoomLevels != null) { + //Now that we know what we would *like* the number of zoom levels + // to be, based on layer or map options, we have to make sure that + // it does not conflict with the actual limit, as specified by + // the constants on the layer itself (and calculated into the + // 'limitZoomLevels' variable). + this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels); + } else { + // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was + // set on either the layer or the map. So we just use the + // maximum limit as calculated by the layer's constants. + this.numZoomLevels = limitZoomLevels; + } + + //now that the 'numZoomLevels' is appropriately, safely set, + // we go back and re-calculate the 'maxZoomLevel'. + this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1; + + if (this.RESOLUTIONS != null) { + var resolutionsIndex = 0; + this.resolutions = []; + for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) { + this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i]; + } + this.maxResolution = this.resolutions[0]; + this.minResolution = this.resolutions[this.resolutions.length - 1]; + } + }, + + /** + * APIMethod: getResolution + * Get the current map resolution + * + * Returns: + * {Float} Map units per Pixel + */ + getResolution: function() { + + if (this.resolutions != null) { + return OpenLayers.Layer.prototype.getResolution.apply(this, arguments); + } else { + var resolution = null; + + var viewSize = this.map.getSize(); + var extent = this.getExtent(); + + if ((viewSize != null) && (extent != null)) { + resolution = Math.max( extent.getWidth() / viewSize.w, + extent.getHeight() / viewSize.h ); + } + return resolution; + } + }, + + /** + * APIMethod: getExtent + * Calculates using px-> lonlat translation functions on tl and br + * corners of viewport + * + * Returns: + * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat + * bounds of the current viewPort. + */ + getExtent: function () { + var size = this.map.getSize(); + var tl = this.getLonLatFromViewPortPx({ + x: 0, y: 0 + }); + var br = this.getLonLatFromViewPortPx({ + x: size.w, y: size.h + }); + + if ((tl != null) && (br != null)) { + return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat); + } else { + return null; + } + }, + + /** + * Method: getZoomForResolution + * Get the zoom level for a given resolution + * + * Parameters: + * resolution - {Float} + * + * Returns: + * {Integer} A suitable zoom level for the specified resolution. + * If no baselayer is set, returns null. + */ + getZoomForResolution: function(resolution) { + + if (this.resolutions != null) { + return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments); + } else { + var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []); + return this.getZoomForExtent(extent); + } + }, + + + + + /********************************************************/ + /* */ + /* Translation Functions */ + /* */ + /* The following functions translate GMaps and OL */ + /* formats for Pixel, LonLat, Bounds, and Zoom */ + /* */ + /********************************************************/ + + + // + // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom + // + + /** + * Method: getOLZoomFromMapObjectZoom + * Get the OL zoom index from the map object zoom level + * + * Parameters: + * moZoom - {Integer} + * + * Returns: + * {Integer} An OpenLayers Zoom level, translated from the passed in zoom + * Returns null if null value is passed in + */ + getOLZoomFromMapObjectZoom: function(moZoom) { + var zoom = null; + if (moZoom != null) { + zoom = moZoom - this.minZoomLevel; + if (this.map.baseLayer !== this) { + zoom = this.map.baseLayer.getZoomForResolution( + this.getResolutionForZoom(zoom) + ); + } + } + return zoom; + }, + + /** + * Method: getMapObjectZoomFromOLZoom + * Get the map object zoom level from the OL zoom level + * + * Parameters: + * olZoom - {Integer} + * + * Returns: + * {Integer} A MapObject level, translated from the passed in olZoom + * Returns null if null value is passed in + */ + getMapObjectZoomFromOLZoom: function(olZoom) { + var zoom = null; + if (olZoom != null) { + zoom = olZoom + this.minZoomLevel; + if (this.map.baseLayer !== this) { + zoom = this.getZoomForResolution( + this.map.baseLayer.getResolutionForZoom(zoom) + ); + } + } + return zoom; + }, + + CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels" +}); + diff --git a/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js b/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js new file mode 100644 index 0000000..564d071 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js @@ -0,0 +1,265 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Markers.js + * @requires OpenLayers/Request/XMLHttpRequest.js + */ + +/** + * Class: OpenLayers.Layer.GeoRSS + * Add GeoRSS Point features to your map. + * + * Inherits from: + * - <OpenLayers.Layer.Markers> + */ +OpenLayers.Layer.GeoRSS = OpenLayers.Class(OpenLayers.Layer.Markers, { + + /** + * Property: location + * {String} store url of text file + */ + location: null, + + /** + * Property: features + * {Array(<OpenLayers.Feature>)} + */ + features: null, + + /** + * APIProperty: formatOptions + * {Object} Hash of options which should be passed to the format when it is + * created. Must be passed in the constructor. + */ + formatOptions: null, + + /** + * Property: selectedFeature + * {<OpenLayers.Feature>} + */ + selectedFeature: null, + + /** + * APIProperty: icon + * {<OpenLayers.Icon>}. This determines the Icon to be used on the map + * for this GeoRSS layer. + */ + icon: null, + + /** + * APIProperty: popupSize + * {<OpenLayers.Size>} This determines the size of GeoRSS popups. If + * not provided, defaults to 250px by 120px. + */ + popupSize: null, + + /** + * APIProperty: useFeedTitle + * {Boolean} Set layer.name to the first <title> element in the feed. Default is true. + */ + useFeedTitle: true, + + /** + * Constructor: OpenLayers.Layer.GeoRSS + * Create a GeoRSS Layer. + * + * Parameters: + * name - {String} + * location - {String} + * options - {Object} + */ + initialize: function(name, location, options) { + OpenLayers.Layer.Markers.prototype.initialize.apply(this, [name, options]); + this.location = location; + this.features = []; + }, + + /** + * Method: destroy + */ + destroy: function() { + // Warning: Layer.Markers.destroy() must be called prior to calling + // clearFeatures() here, otherwise we leak memory. Indeed, if + // Layer.Markers.destroy() is called after clearFeatures(), it won't be + // able to remove the marker image elements from the layer's div since + // the markers will have been destroyed by clearFeatures(). + OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments); + this.clearFeatures(); + this.features = null; + }, + + /** + * Method: loadRSS + * Start the load of the RSS data. Don't do this when we first add the layer, + * since we may not be visible at any point, and it would therefore be a waste. + */ + loadRSS: function() { + if (!this.loaded) { + this.events.triggerEvent("loadstart"); + OpenLayers.Request.GET({ + url: this.location, + success: this.parseData, + scope: this + }); + this.loaded = true; + } + }, + + /** + * Method: moveTo + * If layer is visible and RSS has not been loaded, load RSS. + * + * Parameters: + * bounds - {Object} + * zoomChanged - {Object} + * minor - {Object} + */ + moveTo:function(bounds, zoomChanged, minor) { + OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments); + if(this.visibility && !this.loaded){ + this.loadRSS(); + } + }, + + /** + * Method: parseData + * Parse the data returned from the Events call. + * + * Parameters: + * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} + */ + parseData: function(ajaxRequest) { + var doc = ajaxRequest.responseXML; + if (!doc || !doc.documentElement) { + doc = OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText); + } + + if (this.useFeedTitle) { + var name = null; + try { + name = doc.getElementsByTagNameNS('*', 'title')[0].firstChild.nodeValue; + } + catch (e) { + name = doc.getElementsByTagName('title')[0].firstChild.nodeValue; + } + if (name) { + this.setName(name); + } + } + + var options = {}; + + OpenLayers.Util.extend(options, this.formatOptions); + + if (this.map && !this.projection.equals(this.map.getProjectionObject())) { + options.externalProjection = this.projection; + options.internalProjection = this.map.getProjectionObject(); + } + + var format = new OpenLayers.Format.GeoRSS(options); + var features = format.read(doc); + + for (var i=0, len=features.length; i<len; i++) { + var data = {}; + var feature = features[i]; + + // we don't support features with no geometry in the GeoRSS + // layer at this time. + if (!feature.geometry) { + continue; + } + + var title = feature.attributes.title ? + feature.attributes.title : "Untitled"; + + var description = feature.attributes.description ? + feature.attributes.description : "No description."; + + var link = feature.attributes.link ? feature.attributes.link : ""; + + var location = feature.geometry.getBounds().getCenterLonLat(); + + + data.icon = this.icon == null ? + OpenLayers.Marker.defaultIcon() : + this.icon.clone(); + + data.popupSize = this.popupSize ? + this.popupSize.clone() : + new OpenLayers.Size(250, 120); + + if (title || description) { + // we have supplemental data, store them. + data.title = title; + data.description = description; + + var contentHTML = '<div class="olLayerGeoRSSClose">[x]</div>'; + contentHTML += '<div class="olLayerGeoRSSTitle">'; + if (link) { + contentHTML += '<a class="link" href="'+link+'" target="_blank">'; + } + contentHTML += title; + if (link) { + contentHTML += '</a>'; + } + contentHTML += '</div>'; + contentHTML += '<div style="" class="olLayerGeoRSSDescription">'; + contentHTML += description; + contentHTML += '</div>'; + data['popupContentHTML'] = contentHTML; + } + var feature = new OpenLayers.Feature(this, location, data); + this.features.push(feature); + var marker = feature.createMarker(); + marker.events.register('click', feature, this.markerClick); + this.addMarker(marker); + } + this.events.triggerEvent("loadend"); + }, + + /** + * Method: markerClick + * + * Parameters: + * evt - {Event} + */ + markerClick: function(evt) { + var sameMarkerClicked = (this == this.layer.selectedFeature); + this.layer.selectedFeature = (!sameMarkerClicked) ? this : null; + for(var i=0, len=this.layer.map.popups.length; i<len; i++) { + this.layer.map.removePopup(this.layer.map.popups[i]); + } + if (!sameMarkerClicked) { + var popup = this.createPopup(); + OpenLayers.Event.observe(popup.div, "click", + OpenLayers.Function.bind(function() { + for(var i=0, len=this.layer.map.popups.length; i<len; i++) { + this.layer.map.removePopup(this.layer.map.popups[i]); + } + }, this) + ); + this.layer.map.addPopup(popup); + } + OpenLayers.Event.stop(evt); + }, + + /** + * Method: clearFeatures + * Destroy all features in this layer. + */ + clearFeatures: function() { + if (this.features != null) { + while(this.features.length > 0) { + var feature = this.features[0]; + OpenLayers.Util.removeItem(this.features, feature); + feature.destroy(); + } + } + }, + + CLASS_NAME: "OpenLayers.Layer.GeoRSS" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Google.js b/misc/openlayers/lib/OpenLayers/Layer/Google.js new file mode 100644 index 0000000..6e85fba --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Google.js @@ -0,0 +1,809 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/SphericalMercator.js + * @requires OpenLayers/Layer/EventPane.js + * @requires OpenLayers/Layer/FixedZoomLevels.js + * @requires OpenLayers/Lang.js + */ + +/** + * Class: OpenLayers.Layer.Google + * + * Provides a wrapper for Google's Maps API + * Normally the Terms of Use for this API do not allow wrapping, but Google + * have provided written consent to OpenLayers for this - see email in + * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html + * + * Inherits from: + * - <OpenLayers.Layer.SphericalMercator> + * - <OpenLayers.Layer.EventPane> + * - <OpenLayers.Layer.FixedZoomLevels> + */ +OpenLayers.Layer.Google = OpenLayers.Class( + OpenLayers.Layer.EventPane, + OpenLayers.Layer.FixedZoomLevels, { + + /** + * Constant: MIN_ZOOM_LEVEL + * {Integer} 0 + */ + MIN_ZOOM_LEVEL: 0, + + /** + * Constant: MAX_ZOOM_LEVEL + * {Integer} 21 + */ + MAX_ZOOM_LEVEL: 21, + + /** + * Constant: RESOLUTIONS + * {Array(Float)} Hardcode these resolutions so that they are more closely + * tied with the standard wms projection + */ + RESOLUTIONS: [ + 1.40625, + 0.703125, + 0.3515625, + 0.17578125, + 0.087890625, + 0.0439453125, + 0.02197265625, + 0.010986328125, + 0.0054931640625, + 0.00274658203125, + 0.001373291015625, + 0.0006866455078125, + 0.00034332275390625, + 0.000171661376953125, + 0.0000858306884765625, + 0.00004291534423828125, + 0.00002145767211914062, + 0.00001072883605957031, + 0.00000536441802978515, + 0.00000268220901489257, + 0.0000013411045074462891, + 0.00000067055225372314453 + ], + + /** + * APIProperty: type + * {GMapType} + */ + type: null, + + /** + * APIProperty: wrapDateLine + * {Boolean} Allow user to pan forever east/west. Default is true. + * Setting this to false only restricts panning if + * <sphericalMercator> is true. + */ + wrapDateLine: true, + + /** + * APIProperty: sphericalMercator + * {Boolean} Should the map act as a mercator-projected map? This will + * cause all interactions with the map to be in the actual map + * projection, which allows support for vector drawing, overlaying + * other maps, etc. + */ + sphericalMercator: false, + + /** + * Property: version + * {Number} The version of the Google Maps API + */ + version: null, + + /** + * Constructor: OpenLayers.Layer.Google + * + * Parameters: + * name - {String} A name for the layer. + * options - {Object} An optional object whose properties will be set + * on the layer. + */ + initialize: function(name, options) { + options = options || {}; + if(!options.version) { + options.version = typeof GMap2 === "function" ? "2" : "3"; + } + var mixin = OpenLayers.Layer.Google["v" + + options.version.replace(/\./g, "_")]; + if (mixin) { + OpenLayers.Util.applyDefaults(options, mixin); + } else { + throw "Unsupported Google Maps API version: " + options.version; + } + + OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS); + if (options.maxExtent) { + options.maxExtent = options.maxExtent.clone(); + } + + OpenLayers.Layer.EventPane.prototype.initialize.apply(this, + [name, options]); + OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, + [name, options]); + + if (this.sphericalMercator) { + OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator); + this.initMercatorParameters(); + } + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {<OpenLayers.Layer.Google>} An exact clone of this layer + */ + clone: function() { + /** + * This method isn't intended to be called by a subclass and it + * doesn't call the same method on the superclass. We don't call + * the super's clone because we don't want properties that are set + * on this layer after initialize (i.e. this.mapObject etc.). + */ + return new OpenLayers.Layer.Google( + this.name, this.getOptions() + ); + }, + + /** + * APIMethod: setVisibility + * Set the visibility flag for the layer and hide/show & redraw + * accordingly. Fire event unless otherwise specified + * + * Note that visibility is no longer simply whether or not the layer's + * style.display is set to "block". Now we store a 'visibility' state + * property on the layer class, this allows us to remember whether or + * not we *desire* for a layer to be visible. In the case where the + * map's resolution is out of the layer's range, this desire may be + * subverted. + * + * Parameters: + * visible - {Boolean} Display the layer (if in range) + */ + setVisibility: function(visible) { + // sharing a map container, opacity has to be set per layer + var opacity = this.opacity == null ? 1 : this.opacity; + OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments); + this.setOpacity(opacity); + }, + + /** + * APIMethod: display + * Hide or show the Layer + * + * Parameters: + * visible - {Boolean} + */ + display: function(visible) { + if (!this._dragging) { + this.setGMapVisibility(visible); + } + OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments); + }, + + /** + * Method: moveTo + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to + * do some init work in that case. + * dragging - {Boolean} + */ + moveTo: function(bounds, zoomChanged, dragging) { + this._dragging = dragging; + OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments); + delete this._dragging; + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for the entire layer (all images) + * + * Parameters: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity !== this.opacity) { + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "opacity" + }); + } + this.opacity = opacity; + } + // Though this layer's opacity may not change, we're sharing a container + // and need to update the opacity for the entire container. + if (this.getVisibility()) { + var container = this.getMapContainer(); + OpenLayers.Util.modifyDOMElement( + container, null, null, null, null, null, null, opacity + ); + } + }, + + /** + * APIMethod: destroy + * Clean up this layer. + */ + destroy: function() { + /** + * We have to override this method because the event pane destroy + * deletes the mapObject reference before removing this layer from + * the map. + */ + if (this.map) { + this.setGMapVisibility(false); + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache && cache.count <= 1) { + this.removeGMapElements(); + } + } + OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: removeGMapElements + * Remove all elements added to the dom. This should only be called if + * this is the last of the Google layers for the given map. + */ + removeGMapElements: function() { + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + // remove shared elements from dom + var container = this.mapObject && this.getMapContainer(); + if (container && container.parentNode) { + container.parentNode.removeChild(container); + } + var termsOfUse = cache.termsOfUse; + if (termsOfUse && termsOfUse.parentNode) { + termsOfUse.parentNode.removeChild(termsOfUse); + } + var poweredBy = cache.poweredBy; + if (poweredBy && poweredBy.parentNode) { + poweredBy.parentNode.removeChild(poweredBy); + } + if (this.mapObject && window.google && google.maps && + google.maps.event && google.maps.event.clearListeners) { + google.maps.event.clearListeners(this.mapObject, 'tilesloaded'); + } + } + }, + + /** + * APIMethod: removeMap + * On being removed from the map, also remove termsOfUse and poweredBy divs + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + // hide layer before removing + if (this.visibility && this.mapObject) { + this.setGMapVisibility(false); + } + // check to see if last Google layer in this map + var cache = OpenLayers.Layer.Google.cache[map.id]; + if (cache) { + if (cache.count <= 1) { + this.removeGMapElements(); + delete OpenLayers.Layer.Google.cache[map.id]; + } else { + // decrement the layer count + --cache.count; + } + } + // remove references to gmap elements + delete this.termsOfUse; + delete this.poweredBy; + delete this.mapObject; + delete this.dragObject; + OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments); + }, + + // + // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds + // + + /** + * APIMethod: getOLBoundsFromMapObjectBounds + * + * Parameters: + * moBounds - {Object} + * + * Returns: + * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the + * passed-in MapObject Bounds. + * Returns null if null value is passed in. + */ + getOLBoundsFromMapObjectBounds: function(moBounds) { + var olBounds = null; + if (moBounds != null) { + var sw = moBounds.getSouthWest(); + var ne = moBounds.getNorthEast(); + if (this.sphericalMercator) { + sw = this.forwardMercator(sw.lng(), sw.lat()); + ne = this.forwardMercator(ne.lng(), ne.lat()); + } else { + sw = new OpenLayers.LonLat(sw.lng(), sw.lat()); + ne = new OpenLayers.LonLat(ne.lng(), ne.lat()); + } + olBounds = new OpenLayers.Bounds(sw.lon, + sw.lat, + ne.lon, + ne.lat ); + } + return olBounds; + }, + + /** + * APIMethod: getWarningHTML + * + * Returns: + * {String} String with information on why layer is broken, how to get + * it working. + */ + getWarningHTML:function() { + return OpenLayers.i18n("googleWarning"); + }, + + + /************************************ + * * + * MapObject Interface Controls * + * * + ************************************/ + + + // Get&Set Center, Zoom + + /** + * APIMethod: getMapObjectCenter + * + * Returns: + * {Object} The mapObject's current center in Map Object format + */ + getMapObjectCenter: function() { + return this.mapObject.getCenter(); + }, + + /** + * APIMethod: getMapObjectZoom + * + * Returns: + * {Integer} The mapObject's current zoom, in Map Object format + */ + getMapObjectZoom: function() { + return this.mapObject.getZoom(); + }, + + + /************************************ + * * + * MapObject Primitives * + * * + ************************************/ + + + // LonLat + + /** + * APIMethod: getLongitudeFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Float} Longitude of the given MapObject LonLat + */ + getLongitudeFromMapObjectLonLat: function(moLonLat) { + return this.sphericalMercator ? + this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon : + moLonLat.lng(); + }, + + /** + * APIMethod: getLatitudeFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Float} Latitude of the given MapObject LonLat + */ + getLatitudeFromMapObjectLonLat: function(moLonLat) { + var lat = this.sphericalMercator ? + this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat : + moLonLat.lat(); + return lat; + }, + + // Pixel + + /** + * APIMethod: getXFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Integer} X value of the MapObject Pixel + */ + getXFromMapObjectPixel: function(moPixel) { + return moPixel.x; + }, + + /** + * APIMethod: getYFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Integer} Y value of the MapObject Pixel + */ + getYFromMapObjectPixel: function(moPixel) { + return moPixel.y; + }, + + CLASS_NAME: "OpenLayers.Layer.Google" +}); + +/** + * Property: OpenLayers.Layer.Google.cache + * {Object} Cache for elements that should only be created once per map. + */ +OpenLayers.Layer.Google.cache = {}; + + +/** + * Constant: OpenLayers.Layer.Google.v2 + * + * Mixin providing functionality specific to the Google Maps API v2. + * + * This API has been deprecated by Google. + * Developers are encouraged to migrate to v3 of the API; support for this + * is provided by <OpenLayers.Layer.Google.v3> + */ +OpenLayers.Layer.Google.v2 = { + + /** + * Property: termsOfUse + * {DOMElement} Div for Google's copyright and terms of use link + */ + termsOfUse: null, + + /** + * Property: poweredBy + * {DOMElement} Div for Google's powered by logo and link + */ + poweredBy: null, + + /** + * Property: dragObject + * {GDraggableObject} Since 2.93, Google has exposed the ability to get + * the maps GDraggableObject. We can now use this for smooth panning + */ + dragObject: null, + + /** + * Method: loadMapObject + * Load the GMap and register appropriate event listeners. If we can't + * load GMap2, then display a warning message. + */ + loadMapObject:function() { + if (!this.type) { + this.type = G_NORMAL_MAP; + } + var mapObject, termsOfUse, poweredBy; + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + // there are already Google layers added to this map + mapObject = cache.mapObject; + termsOfUse = cache.termsOfUse; + poweredBy = cache.poweredBy; + // increment the layer count + ++cache.count; + } else { + // this is the first Google layer for this map + + var container = this.map.viewPortDiv; + var div = document.createElement("div"); + div.id = this.map.id + "_GMap2Container"; + div.style.position = "absolute"; + div.style.width = "100%"; + div.style.height = "100%"; + container.appendChild(div); + + // create GMap and shuffle elements + try { + mapObject = new GMap2(div); + + // move the ToS and branding stuff up to the container div + termsOfUse = div.lastChild; + container.appendChild(termsOfUse); + termsOfUse.style.zIndex = "1100"; + termsOfUse.style.right = ""; + termsOfUse.style.bottom = ""; + termsOfUse.className = "olLayerGoogleCopyright"; + + poweredBy = div.lastChild; + container.appendChild(poweredBy); + poweredBy.style.zIndex = "1100"; + poweredBy.style.right = ""; + poweredBy.style.bottom = ""; + poweredBy.className = "olLayerGooglePoweredBy gmnoprint"; + + } catch (e) { + throw(e); + } + // cache elements for use by any other google layers added to + // this same map + OpenLayers.Layer.Google.cache[this.map.id] = { + mapObject: mapObject, + termsOfUse: termsOfUse, + poweredBy: poweredBy, + count: 1 + }; + } + + this.mapObject = mapObject; + this.termsOfUse = termsOfUse; + this.poweredBy = poweredBy; + + // ensure this layer type is one of the mapObject types + if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(), + this.type) === -1) { + this.mapObject.addMapType(this.type); + } + + //since v 2.93 getDragObject is now available. + if(typeof mapObject.getDragObject == "function") { + this.dragObject = mapObject.getDragObject(); + } else { + this.dragPanMapObject = null; + } + + if(this.isBaseLayer === false) { + this.setGMapVisibility(this.div.style.display !== "none"); + } + + }, + + /** + * APIMethod: onMapResize + */ + onMapResize: function() { + // workaround for resizing of invisible or not yet fully loaded layers + // where GMap2.checkResize() does not work. We need to load the GMap + // for the old div size, then checkResize(), and then call + // layer.moveTo() to trigger GMap.setCenter() (which will finish + // the GMap initialization). + if(this.visibility && this.mapObject.isLoaded()) { + this.mapObject.checkResize(); + } else { + if(!this._resized) { + var layer = this; + var handle = GEvent.addListener(this.mapObject, "load", function() { + GEvent.removeListener(handle); + delete layer._resized; + layer.mapObject.checkResize(); + layer.moveTo(layer.map.getCenter(), layer.map.getZoom()); + }); + } + this._resized = true; + } + }, + + /** + * Method: setGMapVisibility + * Display the GMap container and associated elements. + * + * Parameters: + * visible - {Boolean} Display the GMap elements. + */ + setGMapVisibility: function(visible) { + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + var container = this.mapObject.getContainer(); + if (visible === true) { + this.mapObject.setMapType(this.type); + container.style.display = ""; + this.termsOfUse.style.left = ""; + this.termsOfUse.style.display = ""; + this.poweredBy.style.display = ""; + cache.displayed = this.id; + } else { + if (cache.displayed === this.id) { + delete cache.displayed; + } + if (!cache.displayed) { + container.style.display = "none"; + this.termsOfUse.style.display = "none"; + // move ToU far to the left in addition to setting display + // to "none", because at the end of the GMap2 load + // sequence, display: none will be unset and ToU would be + // visible after loading a map with a google layer that is + // initially hidden. + this.termsOfUse.style.left = "-9999px"; + this.poweredBy.style.display = "none"; + } + } + } + }, + + /** + * Method: getMapContainer + * + * Returns: + * {DOMElement} the GMap container's div + */ + getMapContainer: function() { + return this.mapObject.getContainer(); + }, + + // + // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds + // + + /** + * APIMethod: getMapObjectBoundsFromOLBounds + * + * Parameters: + * olBounds - {<OpenLayers.Bounds>} + * + * Returns: + * {Object} A MapObject Bounds, translated from olBounds + * Returns null if null value is passed in + */ + getMapObjectBoundsFromOLBounds: function(olBounds) { + var moBounds = null; + if (olBounds != null) { + var sw = this.sphericalMercator ? + this.inverseMercator(olBounds.bottom, olBounds.left) : + new OpenLayers.LonLat(olBounds.bottom, olBounds.left); + var ne = this.sphericalMercator ? + this.inverseMercator(olBounds.top, olBounds.right) : + new OpenLayers.LonLat(olBounds.top, olBounds.right); + moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon), + new GLatLng(ne.lat, ne.lon)); + } + return moBounds; + }, + + + /************************************ + * * + * MapObject Interface Controls * + * * + ************************************/ + + + // Get&Set Center, Zoom + + /** + * APIMethod: setMapObjectCenter + * Set the mapObject to the specified center and zoom + * + * Parameters: + * center - {Object} MapObject LonLat format + * zoom - {int} MapObject zoom format + */ + setMapObjectCenter: function(center, zoom) { + this.mapObject.setCenter(center, zoom); + }, + + /** + * APIMethod: dragPanMapObject + * + * Parameters: + * dX - {Integer} + * dY - {Integer} + */ + dragPanMapObject: function(dX, dY) { + this.dragObject.moveBy(new GSize(-dX, dY)); + }, + + + // LonLat - Pixel Translation + + /** + * APIMethod: getMapObjectLonLatFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Object} MapObject LonLat translated from MapObject Pixel + */ + getMapObjectLonLatFromMapObjectPixel: function(moPixel) { + return this.mapObject.fromContainerPixelToLatLng(moPixel); + }, + + /** + * APIMethod: getMapObjectPixelFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Object} MapObject Pixel transtlated from MapObject LonLat + */ + getMapObjectPixelFromMapObjectLonLat: function(moLonLat) { + return this.mapObject.fromLatLngToContainerPixel(moLonLat); + }, + + + // Bounds + + /** + * APIMethod: getMapObjectZoomFromMapObjectBounds + * + * Parameters: + * moBounds - {Object} MapObject Bounds format + * + * Returns: + * {Object} MapObject Zoom for specified MapObject Bounds + */ + getMapObjectZoomFromMapObjectBounds: function(moBounds) { + return this.mapObject.getBoundsZoomLevel(moBounds); + }, + + /************************************ + * * + * MapObject Primitives * + * * + ************************************/ + + + // LonLat + + /** + * APIMethod: getMapObjectLonLatFromLonLat + * + * Parameters: + * lon - {Float} + * lat - {Float} + * + * Returns: + * {Object} MapObject LonLat built from lon and lat params + */ + getMapObjectLonLatFromLonLat: function(lon, lat) { + var gLatLng; + if(this.sphericalMercator) { + var lonlat = this.inverseMercator(lon, lat); + gLatLng = new GLatLng(lonlat.lat, lonlat.lon); + } else { + gLatLng = new GLatLng(lat, lon); + } + return gLatLng; + }, + + // Pixel + + /** + * APIMethod: getMapObjectPixelFromXY + * + * Parameters: + * x - {Integer} + * y - {Integer} + * + * Returns: + * {Object} MapObject Pixel from x and y parameters + */ + getMapObjectPixelFromXY: function(x, y) { + return new GPoint(x, y); + } + +}; diff --git a/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js b/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js new file mode 100644 index 0000000..067b7a0 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js @@ -0,0 +1,351 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Google.js + */ + +/** + * Constant: OpenLayers.Layer.Google.v3 + * + * Mixin providing functionality specific to the Google Maps API v3. + * + * To use this layer, you must include the GMaps v3 API in your html. + * + * Note that this layer configures the google.maps.map object with the + * "disableDefaultUI" option set to true. Using UI controls that the Google + * Maps API provides is not supported by the OpenLayers API. + */ +OpenLayers.Layer.Google.v3 = { + + /** + * Constant: DEFAULTS + * {Object} It is not recommended to change the properties set here. Note + * that Google.v3 layers only work when sphericalMercator is set to true. + * + * (code) + * { + * sphericalMercator: true, + * projection: "EPSG:900913" + * } + * (end) + */ + DEFAULTS: { + sphericalMercator: true, + projection: "EPSG:900913" + }, + + /** + * APIProperty: animationEnabled + * {Boolean} If set to true, the transition between zoom levels will be + * animated (if supported by the GMaps API for the device used). Set to + * false to match the zooming experience of other layer types. Default + * is true. Note that the GMaps API does not give us control over zoom + * animation, so if set to false, when zooming, this will make the + * layer temporarily invisible, wait until GMaps reports the map being + * idle, and make it visible again. The result will be a blank layer + * for a few moments while zooming. + */ + animationEnabled: true, + + /** + * Method: loadMapObject + * Load the GMap and register appropriate event listeners. + */ + loadMapObject: function() { + if (!this.type) { + this.type = google.maps.MapTypeId.ROADMAP; + } + var mapObject; + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + if (cache) { + // there are already Google layers added to this map + mapObject = cache.mapObject; + // increment the layer count + ++cache.count; + } else { + // this is the first Google layer for this map + // create GMap + var center = this.map.getCenter(); + var container = document.createElement('div'); + container.className = "olForeignContainer"; + container.style.width = '100%'; + container.style.height = '100%'; + mapObject = new google.maps.Map(container, { + center: center ? + new google.maps.LatLng(center.lat, center.lon) : + new google.maps.LatLng(0, 0), + zoom: this.map.getZoom() || 0, + mapTypeId: this.type, + disableDefaultUI: true, + keyboardShortcuts: false, + draggable: false, + disableDoubleClickZoom: true, + scrollwheel: false, + streetViewControl: false + }); + var googleControl = document.createElement('div'); + googleControl.style.width = '100%'; + googleControl.style.height = '100%'; + mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl); + + // cache elements for use by any other google layers added to + // this same map + cache = { + googleControl: googleControl, + mapObject: mapObject, + count: 1 + }; + OpenLayers.Layer.Google.cache[this.map.id] = cache; + } + this.mapObject = mapObject; + this.setGMapVisibility(this.visibility); + }, + + /** + * APIMethod: onMapResize + */ + onMapResize: function() { + if (this.visibility) { + google.maps.event.trigger(this.mapObject, "resize"); + } + }, + + /** + * Method: setGMapVisibility + * Display the GMap container and associated elements. + * + * Parameters: + * visible - {Boolean} Display the GMap elements. + */ + setGMapVisibility: function(visible) { + var cache = OpenLayers.Layer.Google.cache[this.map.id]; + var map = this.map; + if (cache) { + var type = this.type; + var layers = map.layers; + var layer; + for (var i=layers.length-1; i>=0; --i) { + layer = layers[i]; + if (layer instanceof OpenLayers.Layer.Google && + layer.visibility === true && layer.inRange === true) { + type = layer.type; + visible = true; + break; + } + } + var container = this.mapObject.getDiv(); + if (visible === true) { + if (container.parentNode !== map.div) { + if (!cache.rendered) { + var me = this; + google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() { + cache.rendered = true; + me.setGMapVisibility(me.getVisibility()); + me.moveTo(me.map.getCenter()); + }); + } else { + map.div.appendChild(container); + cache.googleControl.appendChild(map.viewPortDiv); + google.maps.event.trigger(this.mapObject, 'resize'); + } + } + this.mapObject.setMapTypeId(type); + } else if (cache.googleControl.hasChildNodes()) { + map.div.appendChild(map.viewPortDiv); + map.div.removeChild(container); + } + } + }, + + /** + * Method: getMapContainer + * + * Returns: + * {DOMElement} the GMap container's div + */ + getMapContainer: function() { + return this.mapObject.getDiv(); + }, + + // + // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds + // + + /** + * APIMethod: getMapObjectBoundsFromOLBounds + * + * Parameters: + * olBounds - {<OpenLayers.Bounds>} + * + * Returns: + * {Object} A MapObject Bounds, translated from olBounds + * Returns null if null value is passed in + */ + getMapObjectBoundsFromOLBounds: function(olBounds) { + var moBounds = null; + if (olBounds != null) { + var sw = this.sphericalMercator ? + this.inverseMercator(olBounds.bottom, olBounds.left) : + new OpenLayers.LonLat(olBounds.bottom, olBounds.left); + var ne = this.sphericalMercator ? + this.inverseMercator(olBounds.top, olBounds.right) : + new OpenLayers.LonLat(olBounds.top, olBounds.right); + moBounds = new google.maps.LatLngBounds( + new google.maps.LatLng(sw.lat, sw.lon), + new google.maps.LatLng(ne.lat, ne.lon) + ); + } + return moBounds; + }, + + + /************************************ + * * + * MapObject Interface Controls * + * * + ************************************/ + + + // LonLat - Pixel Translation + + /** + * APIMethod: getMapObjectLonLatFromMapObjectPixel + * + * Parameters: + * moPixel - {Object} MapObject Pixel format + * + * Returns: + * {Object} MapObject LonLat translated from MapObject Pixel + */ + getMapObjectLonLatFromMapObjectPixel: function(moPixel) { + var size = this.map.getSize(); + var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center); + var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center); + var res = this.map.getResolution(); + + var delta_x = moPixel.x - (size.w / 2); + var delta_y = moPixel.y - (size.h / 2); + + var lonlat = new OpenLayers.LonLat( + lon + delta_x * res, + lat - delta_y * res + ); + + if (this.wrapDateLine) { + lonlat = lonlat.wrapDateLine(this.maxExtent); + } + return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat); + }, + + /** + * APIMethod: getMapObjectPixelFromMapObjectLonLat + * + * Parameters: + * moLonLat - {Object} MapObject LonLat format + * + * Returns: + * {Object} MapObject Pixel transtlated from MapObject LonLat + */ + getMapObjectPixelFromMapObjectLonLat: function(moLonLat) { + var lon = this.getLongitudeFromMapObjectLonLat(moLonLat); + var lat = this.getLatitudeFromMapObjectLonLat(moLonLat); + var res = this.map.getResolution(); + var extent = this.map.getExtent(); + return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)), + (1/res * (extent.top - lat))); + }, + + + /** + * APIMethod: setMapObjectCenter + * Set the mapObject to the specified center and zoom + * + * Parameters: + * center - {Object} MapObject LonLat format + * zoom - {int} MapObject zoom format + */ + setMapObjectCenter: function(center, zoom) { + if (this.animationEnabled === false && zoom != this.mapObject.zoom) { + var mapContainer = this.getMapContainer(); + google.maps.event.addListenerOnce( + this.mapObject, + "idle", + function() { + mapContainer.style.visibility = ""; + } + ); + mapContainer.style.visibility = "hidden"; + } + this.mapObject.setOptions({ + center: center, + zoom: zoom + }); + }, + + + // Bounds + + /** + * APIMethod: getMapObjectZoomFromMapObjectBounds + * + * Parameters: + * moBounds - {Object} MapObject Bounds format + * + * Returns: + * {Object} MapObject Zoom for specified MapObject Bounds + */ + getMapObjectZoomFromMapObjectBounds: function(moBounds) { + return this.mapObject.getBoundsZoomLevel(moBounds); + }, + + /************************************ + * * + * MapObject Primitives * + * * + ************************************/ + + + // LonLat + + /** + * APIMethod: getMapObjectLonLatFromLonLat + * + * Parameters: + * lon - {Float} + * lat - {Float} + * + * Returns: + * {Object} MapObject LonLat built from lon and lat params + */ + getMapObjectLonLatFromLonLat: function(lon, lat) { + var gLatLng; + if(this.sphericalMercator) { + var lonlat = this.inverseMercator(lon, lat); + gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon); + } else { + gLatLng = new google.maps.LatLng(lat, lon); + } + return gLatLng; + }, + + // Pixel + + /** + * APIMethod: getMapObjectPixelFromXY + * + * Parameters: + * x - {Integer} + * y - {Integer} + * + * Returns: + * {Object} MapObject Pixel from x and y parameters + */ + getMapObjectPixelFromXY: function(x, y) { + return new google.maps.Point(x, y); + } + +}; diff --git a/misc/openlayers/lib/OpenLayers/Layer/Grid.js b/misc/openlayers/lib/OpenLayers/Layer/Grid.js new file mode 100644 index 0000000..a94075f --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Grid.js @@ -0,0 +1,1343 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/HTTPRequest.js + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Class: OpenLayers.Layer.Grid + * Base class for layers that use a lattice of tiles. Create a new grid + * layer with the <OpenLayers.Layer.Grid> constructor. + * + * Inherits from: + * - <OpenLayers.Layer.HTTPRequest> + */ +OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { + + /** + * APIProperty: tileSize + * {<OpenLayers.Size>} + */ + tileSize: null, + + /** + * Property: tileOriginCorner + * {String} If the <tileOrigin> property is not provided, the tile origin + * will be derived from the layer's <maxExtent>. The corner of the + * <maxExtent> used is determined by this property. Acceptable values + * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br" + * (bottom right). Default is "bl". + */ + tileOriginCorner: "bl", + + /** + * APIProperty: tileOrigin + * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles. + * If provided, requests for tiles at all resolutions will be aligned + * with this location (no tiles shall overlap this location). If + * not provided, the grid of tiles will be aligned with the layer's + * <maxExtent>. Default is ``null``. + */ + tileOrigin: null, + + /** APIProperty: tileOptions + * {Object} optional configuration options for <OpenLayers.Tile> instances + * created by this Layer, if supported by the tile class. + */ + tileOptions: null, + + /** + * APIProperty: tileClass + * {<OpenLayers.Tile>} The tile class to use for this layer. + * Defaults is OpenLayers.Tile.Image. + */ + tileClass: OpenLayers.Tile.Image, + + /** + * Property: grid + * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is + * an array of tiles. + */ + grid: null, + + /** + * APIProperty: singleTile + * {Boolean} Moves the layer into single-tile mode, meaning that one tile + * will be loaded. The tile's size will be determined by the 'ratio' + * property. When the tile is dragged such that it does not cover the + * entire viewport, it is reloaded. + */ + singleTile: false, + + /** APIProperty: ratio + * {Float} Used only when in single-tile mode, this specifies the + * ratio of the size of the single tile to the size of the map. + * Default value is 1.5. + */ + ratio: 1.5, + + /** + * APIProperty: buffer + * {Integer} Used only when in gridded mode, this specifies the number of + * extra rows and colums of tiles on each side which will + * surround the minimum grid tiles to cover the map. + * For very slow loading layers, a larger value may increase + * performance somewhat when dragging, but will increase bandwidth + * use significantly. + */ + buffer: 0, + + /** + * APIProperty: transitionEffect + * {String} The transition effect to use when the map is zoomed. + * Two posible values: + * + * "resize" - Existing tiles are resized on zoom to provide a visual + * effect of the zoom having taken place immediately. As the + * new tiles become available, they are drawn on top of the + * resized tiles (this is the default setting). + * "map-resize" - Existing tiles are resized on zoom and placed below the + * base layer. New tiles for the base layer will cover existing tiles. + * This setting is recommended when having an overlay duplicated during + * the transition is undesirable (e.g. street labels or big transparent + * fills). + * null - No transition effect. + * + * Using "resize" on non-opaque layers can cause undesired visual + * effects. Set transitionEffect to null in this case. + */ + transitionEffect: "resize", + + /** + * APIProperty: numLoadingTiles + * {Integer} How many tiles are still loading? + */ + numLoadingTiles: 0, + + /** + * Property: serverResolutions + * {Array(Number}} This property is documented in subclasses as + * an API property. + */ + serverResolutions: null, + + /** + * Property: loading + * {Boolean} Indicates if tiles are being loaded. + */ + loading: false, + + /** + * Property: backBuffer + * {DOMElement} The back buffer. + */ + backBuffer: null, + + /** + * Property: gridResolution + * {Number} The resolution of the current grid. Used for backbuffer and + * client zoom. This property is updated every time the grid is + * initialized. + */ + gridResolution: null, + + /** + * Property: backBufferResolution + * {Number} The resolution of the current back buffer. This property is + * updated each time a back buffer is created. + */ + backBufferResolution: null, + + /** + * Property: backBufferLonLat + * {Object} The top-left corner of the current back buffer. Includes lon + * and lat properties. This object is updated each time a back buffer + * is created. + */ + backBufferLonLat: null, + + /** + * Property: backBufferTimerId + * {Number} The id of the back buffer timer. This timer is used to + * delay the removal of the back buffer, thereby preventing + * flash effects caused by tile animation. + */ + backBufferTimerId: null, + + /** + * APIProperty: removeBackBufferDelay + * {Number} Delay for removing the backbuffer when all tiles have finished + * loading. Can be set to 0 when no css opacity transitions for the + * olTileImage class are used. Default is 0 for <singleTile> layers, + * 2500 for tiled layers. See <className> for more information on + * tile animation. + */ + removeBackBufferDelay: null, + + /** + * APIProperty: className + * {String} Name of the class added to the layer div. If not set in the + * options passed to the constructor then className defaults to + * "olLayerGridSingleTile" for single tile layers (see <singleTile>), + * and "olLayerGrid" for non single tile layers. + * + * Note: + * + * The displaying of tiles is not animated by default for single tile + * layers - OpenLayers' default theme (style.css) includes this: + * (code) + * .olLayerGrid .olTileImage { + * -webkit-transition: opacity 0.2s linear; + * -moz-transition: opacity 0.2s linear; + * -o-transition: opacity 0.2s linear; + * transition: opacity 0.2s linear; + * } + * (end) + * To animate tile displaying for any grid layer the following + * CSS rule can be used: + * (code) + * .olTileImage { + * -webkit-transition: opacity 0.2s linear; + * -moz-transition: opacity 0.2s linear; + * -o-transition: opacity 0.2s linear; + * transition: opacity 0.2s linear; + * } + * (end) + * In that case, to avoid flash effects, <removeBackBufferDelay> + * should not be zero. + */ + className: null, + + /** + * Register a listener for a particular event with the following syntax: + * (code) + * layer.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to layer.events.object. + * element - {DOMElement} A reference to layer.events.element. + * + * Supported event types: + * addtile - Triggered when a tile is added to this layer. Listeners receive + * an object as first argument, which has a tile property that + * references the tile that has been added. + * tileloadstart - Triggered when a tile starts loading. Listeners receive + * an object as first argument, which has a tile property that + * references the tile that starts loading. + * tileloaded - Triggered when each new tile is + * loaded, as a means of progress update to listeners. + * listeners can access 'numLoadingTiles' if they wish to keep + * track of the loading progress. Listeners are called with an object + * with a 'tile' property as first argument, making the loaded tile + * available to the listener, and an 'aborted' property, which will be + * true when loading was aborted and no tile data is available. + * tileerror - Triggered before the tileloaded event (i.e. when the tile is + * still hidden) if a tile failed to load. Listeners receive an object + * as first argument, which has a tile property that references the + * tile that could not be loaded. + * retile - Triggered when the layer recreates its tile grid. + */ + + /** + * Property: gridLayout + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + gridLayout: null, + + /** + * Property: rowSign + * {Number} 1 for grids starting at the top, -1 for grids starting at the + * bottom. This is used for several grid index and offset calculations. + */ + rowSign: null, + + /** + * Property: transitionendEvents + * {Array} Event names for transitionend + */ + transitionendEvents: [ + 'transitionend', 'webkitTransitionEnd', 'otransitionend', + 'oTransitionEnd' + ], + + /** + * Constructor: OpenLayers.Layer.Grid + * Create a new grid layer + * + * Parameters: + * name - {String} + * url - {String} + * params - {Object} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, + arguments); + this.grid = []; + this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this); + + this.initProperties(); + + this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1; + }, + + /** + * Method: initProperties + * Set any properties that depend on the value of singleTile. + * Currently sets removeBackBufferDelay and className + */ + initProperties: function() { + if (this.options.removeBackBufferDelay === undefined) { + this.removeBackBufferDelay = this.singleTile ? 0 : 2500; + } + + if (this.options.className === undefined) { + this.className = this.singleTile ? 'olLayerGridSingleTile' : + 'olLayerGrid'; + } + }, + + /** + * Method: setMap + * + * Parameters: + * map - {<OpenLayers.Map>} The map. + */ + setMap: function(map) { + OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map); + OpenLayers.Element.addClass(this.div, this.className); + }, + + /** + * Method: removeMap + * Called when the layer is removed from the map. + * + * Parameters: + * map - {<OpenLayers.Map>} The map. + */ + removeMap: function(map) { + this.removeBackBuffer(); + }, + + /** + * APIMethod: destroy + * Deconstruct the layer and clear the grid. + */ + destroy: function() { + this.removeBackBuffer(); + this.clearGrid(); + + this.grid = null; + this.tileSize = null; + OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: mergeNewParams + * Refetches tiles with new params merged, keeping a backbuffer. Each + * loading new tile will have a css class of '.olTileReplacing'. If a + * stylesheet applies a 'display: none' style to that class, any fade-in + * transition will not apply, and backbuffers for each tile will be removed + * as soon as the tile is loaded. + * + * Parameters: + * newParams - {Object} + * + * Returns: + * redrawn: {Boolean} whether the layer was actually redrawn. + */ + + /** + * Method: clearGrid + * Go through and remove all tiles from the grid, calling + * destroy() on each of them to kill circular references + */ + clearGrid:function() { + if (this.grid) { + for(var iRow=0, len=this.grid.length; iRow<len; iRow++) { + var row = this.grid[iRow]; + for(var iCol=0, clen=row.length; iCol<clen; iCol++) { + var tile = row[iCol]; + this.destroyTile(tile); + } + } + this.grid = []; + this.gridResolution = null; + this.gridLayout = null; + } + }, + + /** + * APIMethod: addOptions + * + * Parameters: + * newOptions - {Object} + * reinitialize - {Boolean} If set to true, and if resolution options of the + * current baseLayer were changed, the map will be recentered to make + * sure that it is displayed with a valid resolution, and a + * changebaselayer event will be triggered. + */ + addOptions: function (newOptions, reinitialize) { + var singleTileChanged = newOptions.singleTile !== undefined && + newOptions.singleTile !== this.singleTile; + OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments); + if (this.map && singleTileChanged) { + this.initProperties(); + this.clearGrid(); + this.tileSize = this.options.tileSize; + this.setTileSize(); + this.moveTo(null, true); + } + }, + + /** + * APIMethod: clone + * Create a clone of this layer + * + * Parameters: + * obj - {Object} Is this ever used? + * + * Returns: + * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.Grid(this.name, + this.url, + this.params, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + if (this.tileSize != null) { + obj.tileSize = this.tileSize.clone(); + } + + // we do not want to copy reference to grid, so we make a new array + obj.grid = []; + obj.gridResolution = null; + // same for backbuffer + obj.backBuffer = null; + obj.backBufferTimerId = null; + obj.loading = false; + obj.numLoadingTiles = 0; + + return obj; + }, + + /** + * Method: moveTo + * This function is called whenever the map is moved. All the moving + * of actual 'tiles' is done by the map, but moveTo's role is to accept + * a bounds and make sure the data that that bounds requires is pre-loaded. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + + OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments); + + bounds = bounds || this.map.getExtent(); + + if (bounds != null) { + + // if grid is empty or zoom has changed, we *must* re-tile + var forceReTile = !this.grid.length || zoomChanged; + + // total bounds of the tiles + var tilesBounds = this.getTilesBounds(); + + // the new map resolution + var resolution = this.map.getResolution(); + + // the server-supported resolution for the new map resolution + var serverResolution = this.getServerResolution(resolution); + + if (this.singleTile) { + + // We want to redraw whenever even the slightest part of the + // current bounds is not contained by our tile. + // (thus, we do not specify partial -- its default is false) + + if ( forceReTile || + (!dragging && !tilesBounds.containsBounds(bounds))) { + + // In single tile mode with no transition effect, we insert + // a non-scaled backbuffer when the layer is moved. But if + // a zoom occurs right after a move, i.e. before the new + // image is received, we need to remove the backbuffer, or + // an ill-positioned image will be visible during the zoom + // transition. + + if(zoomChanged && this.transitionEffect !== 'resize') { + this.removeBackBuffer(); + } + + if(!zoomChanged || this.transitionEffect === 'resize') { + this.applyBackBuffer(resolution); + } + + this.initSingleTile(bounds); + } + } else { + + // if the bounds have changed such that they are not even + // *partially* contained by our tiles (e.g. when user has + // programmatically panned to the other side of the earth on + // zoom level 18), then moveGriddedTiles could potentially have + // to run through thousands of cycles, so we want to reTile + // instead (thus, partial true). + forceReTile = forceReTile || + !tilesBounds.intersectsBounds(bounds, { + worldBounds: this.map.baseLayer.wrapDateLine && + this.map.getMaxExtent() + }); + + if(forceReTile) { + if(zoomChanged && (this.transitionEffect === 'resize' || + this.gridResolution === resolution)) { + this.applyBackBuffer(resolution); + } + this.initGriddedTiles(bounds); + } else { + this.moveGriddedTiles(); + } + } + } + }, + + /** + * Method: getTileData + * Given a map location, retrieve a tile and the pixel offset within that + * tile corresponding to the location. If there is not an existing + * tile in the grid that covers the given location, null will be + * returned. + * + * Parameters: + * loc - {<OpenLayers.LonLat>} map location + * + * Returns: + * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}), + * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel + * offset from top left). + */ + getTileData: function(loc) { + var data = null, + x = loc.lon, + y = loc.lat, + numRows = this.grid.length; + + if (this.map && numRows) { + var res = this.map.getResolution(), + tileWidth = this.tileSize.w, + tileHeight = this.tileSize.h, + bounds = this.grid[0][0].bounds, + left = bounds.left, + top = bounds.top; + + if (x < left) { + // deal with multiple worlds + if (this.map.baseLayer.wrapDateLine) { + var worldWidth = this.map.getMaxExtent().getWidth(); + var worldsAway = Math.ceil((left - x) / worldWidth); + x += worldWidth * worldsAway; + } + } + // tile distance to location (fractional number of tiles); + var dtx = (x - left) / (res * tileWidth); + var dty = (top - y) / (res * tileHeight); + // index of tile in grid + var col = Math.floor(dtx); + var row = Math.floor(dty); + if (row >= 0 && row < numRows) { + var tile = this.grid[row][col]; + if (tile) { + data = { + tile: tile, + // pixel index within tile + i: Math.floor((dtx - col) * tileWidth), + j: Math.floor((dty - row) * tileHeight) + }; + } + } + } + return data; + }, + + /** + * Method: destroyTile + * + * Parameters: + * tile - {<OpenLayers.Tile>} + */ + destroyTile: function(tile) { + this.removeTileMonitoringHooks(tile); + tile.destroy(); + }, + + /** + * Method: getServerResolution + * Return the closest server-supported resolution. + * + * Parameters: + * resolution - {Number} The base resolution. If undefined the + * map resolution is used. + * + * Returns: + * {Number} The closest server resolution value. + */ + getServerResolution: function(resolution) { + var distance = Number.POSITIVE_INFINITY; + resolution = resolution || this.map.getResolution(); + if(this.serverResolutions && + OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) { + var i, newDistance, newResolution, serverResolution; + for(i=this.serverResolutions.length-1; i>= 0; i--) { + newResolution = this.serverResolutions[i]; + newDistance = Math.abs(newResolution - resolution); + if (newDistance > distance) { + break; + } + distance = newDistance; + serverResolution = newResolution; + } + resolution = serverResolution; + } + return resolution; + }, + + /** + * Method: getServerZoom + * Return the zoom value corresponding to the best matching server + * resolution, taking into account <serverResolutions> and <zoomOffset>. + * + * Returns: + * {Number} The closest server supported zoom. This is not the map zoom + * level, but an index of the server's resolutions array. + */ + getServerZoom: function() { + var resolution = this.getServerResolution(); + return this.serverResolutions ? + OpenLayers.Util.indexOf(this.serverResolutions, resolution) : + this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0); + }, + + /** + * Method: applyBackBuffer + * Create, insert, scale and position a back buffer for the layer. + * + * Parameters: + * resolution - {Number} The resolution to transition to. + */ + applyBackBuffer: function(resolution) { + if(this.backBufferTimerId !== null) { + this.removeBackBuffer(); + } + var backBuffer = this.backBuffer; + if(!backBuffer) { + backBuffer = this.createBackBuffer(); + if(!backBuffer) { + return; + } + if (resolution === this.gridResolution) { + this.div.insertBefore(backBuffer, this.div.firstChild); + } else { + this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div); + } + this.backBuffer = backBuffer; + + // set some information in the instance for subsequent + // calls to applyBackBuffer where the same back buffer + // is reused + var topLeftTileBounds = this.grid[0][0].bounds; + this.backBufferLonLat = { + lon: topLeftTileBounds.left, + lat: topLeftTileBounds.top + }; + this.backBufferResolution = this.gridResolution; + } + + var ratio = this.backBufferResolution / resolution; + + // scale the tiles inside the back buffer + var tiles = backBuffer.childNodes, tile; + for (var i=tiles.length-1; i>=0; --i) { + tile = tiles[i]; + tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px'; + tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px'; + tile.style.width = Math.round(ratio * tile._w) + 'px'; + tile.style.height = Math.round(ratio * tile._h) + 'px'; + } + + // and position it (based on the grid's top-left corner) + var position = this.getViewPortPxFromLonLat( + this.backBufferLonLat, resolution); + var leftOffset = this.map.layerContainerOriginPx.x; + var topOffset = this.map.layerContainerOriginPx.y; + backBuffer.style.left = Math.round(position.x - leftOffset) + 'px'; + backBuffer.style.top = Math.round(position.y - topOffset) + 'px'; + }, + + /** + * Method: createBackBuffer + * Create a back buffer. + * + * Returns: + * {DOMElement} The DOM element for the back buffer, undefined if the + * grid isn't initialized yet. + */ + createBackBuffer: function() { + var backBuffer; + if(this.grid.length > 0) { + backBuffer = document.createElement('div'); + backBuffer.id = this.div.id + '_bb'; + backBuffer.className = 'olBackBuffer'; + backBuffer.style.position = 'absolute'; + var map = this.map; + backBuffer.style.zIndex = this.transitionEffect === 'resize' ? + this.getZIndex() - 1 : + // 'map-resize': + map.Z_INDEX_BASE.BaseLayer - + (map.getNumLayers() - map.getLayerIndex(this)); + for(var i=0, lenI=this.grid.length; i<lenI; i++) { + for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) { + var tile = this.grid[i][j], + markup = this.grid[i][j].createBackBuffer(); + if (markup) { + markup._i = i; + markup._j = j; + markup._w = tile.size.w; + markup._h = tile.size.h; + markup.id = tile.id + '_bb'; + backBuffer.appendChild(markup); + } + } + } + } + return backBuffer; + }, + + /** + * Method: removeBackBuffer + * Remove back buffer from DOM. + */ + removeBackBuffer: function() { + if (this._transitionElement) { + for (var i=this.transitionendEvents.length-1; i>=0; --i) { + OpenLayers.Event.stopObserving(this._transitionElement, + this.transitionendEvents[i], this._removeBackBuffer); + } + delete this._transitionElement; + } + if(this.backBuffer) { + if (this.backBuffer.parentNode) { + this.backBuffer.parentNode.removeChild(this.backBuffer); + } + this.backBuffer = null; + this.backBufferResolution = null; + if(this.backBufferTimerId !== null) { + window.clearTimeout(this.backBufferTimerId); + this.backBufferTimerId = null; + } + } + }, + + /** + * Method: moveByPx + * Move the layer based on pixel vector. + * + * Parameters: + * dx - {Number} + * dy - {Number} + */ + moveByPx: function(dx, dy) { + if (!this.singleTile) { + this.moveGriddedTiles(); + } + }, + + /** + * APIMethod: setTileSize + * Check if we are in singleTile mode and if so, set the size as a ratio + * of the map size (as specified by the layer's 'ratio' property). + * + * Parameters: + * size - {<OpenLayers.Size>} + */ + setTileSize: function(size) { + if (this.singleTile) { + size = this.map.getSize(); + size.h = parseInt(size.h * this.ratio, 10); + size.w = parseInt(size.w * this.ratio, 10); + } + OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]); + }, + + /** + * APIMethod: getTilesBounds + * Return the bounds of the tile grid. + * + * Returns: + * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the + * currently loaded tiles (including those partially or not at all seen + * onscreen). + */ + getTilesBounds: function() { + var bounds = null; + + var length = this.grid.length; + if (length) { + var bottomLeftTileBounds = this.grid[length - 1][0].bounds, + width = this.grid[0].length * bottomLeftTileBounds.getWidth(), + height = this.grid.length * bottomLeftTileBounds.getHeight(); + + bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left, + bottomLeftTileBounds.bottom, + bottomLeftTileBounds.left + width, + bottomLeftTileBounds.bottom + height); + } + return bounds; + }, + + /** + * Method: initSingleTile + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + */ + initSingleTile: function(bounds) { + this.events.triggerEvent("retile"); + + //determine new tile bounds + var center = bounds.getCenterLonLat(); + var tileWidth = bounds.getWidth() * this.ratio; + var tileHeight = bounds.getHeight() * this.ratio; + + var tileBounds = + new OpenLayers.Bounds(center.lon - (tileWidth/2), + center.lat - (tileHeight/2), + center.lon + (tileWidth/2), + center.lat + (tileHeight/2)); + + var px = this.map.getLayerPxFromLonLat({ + lon: tileBounds.left, + lat: tileBounds.top + }); + + if (!this.grid.length) { + this.grid[0] = []; + } + + var tile = this.grid[0][0]; + if (!tile) { + tile = this.addTile(tileBounds, px); + + this.addTileMonitoringHooks(tile); + tile.draw(); + this.grid[0][0] = tile; + } else { + tile.moveTo(tileBounds, px); + } + + //remove all but our single tile + this.removeExcessTiles(1,1); + + // store the resolution of the grid + this.gridResolution = this.getServerResolution(); + }, + + /** + * Method: calculateGridLayout + * Generate parameters for the grid layout. + * + * Parameters: + * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an + * object with a 'left' and 'top' properties. + * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an + * object with a 'lon' and 'lat' properties. + * resolution - {Number} + * + * Returns: + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + calculateGridLayout: function(bounds, origin, resolution) { + var tilelon = resolution * this.tileSize.w; + var tilelat = resolution * this.tileSize.h; + + var offsetlon = bounds.left - origin.lon; + var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; + + var rowSign = this.rowSign; + + var offsetlat = rowSign * (origin.lat - bounds.top + tilelat); + var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign; + + return { + tilelon: tilelon, tilelat: tilelat, + startcol: tilecol, startrow: tilerow + }; + + }, + + /** + * Method: getTileOrigin + * Determine the origin for aligning the grid of tiles. If a <tileOrigin> + * property is supplied, that will be returned. Otherwise, the origin + * will be derived from the layer's <maxExtent> property. In this case, + * the tile origin will be the corner of the <maxExtent> given by the + * <tileOriginCorner> property. + * + * Returns: + * {<OpenLayers.LonLat>} The tile origin. + */ + getTileOrigin: function() { + var origin = this.tileOrigin; + if (!origin) { + var extent = this.getMaxExtent(); + var edges = ({ + "tl": ["left", "top"], + "tr": ["right", "top"], + "bl": ["left", "bottom"], + "br": ["right", "bottom"] + })[this.tileOriginCorner]; + origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]); + } + return origin; + }, + + /** + * Method: getTileBoundsForGridIndex + * + * Parameters: + * row - {Number} The row of the grid + * col - {Number} The column of the grid + * + * Returns: + * {<OpenLayers.Bounds>} The bounds for the tile at (row, col) + */ + getTileBoundsForGridIndex: function(row, col) { + var origin = this.getTileOrigin(); + var tileLayout = this.gridLayout; + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + var startcol = tileLayout.startcol; + var startrow = tileLayout.startrow; + var rowSign = this.rowSign; + return new OpenLayers.Bounds( + origin.lon + (startcol + col) * tilelon, + origin.lat - (startrow + row * rowSign) * tilelat * rowSign, + origin.lon + (startcol + col + 1) * tilelon, + origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign + ); + }, + + /** + * Method: initGriddedTiles + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + */ + initGriddedTiles:function(bounds) { + this.events.triggerEvent("retile"); + + // work out mininum number of rows and columns; this is the number of + // tiles required to cover the viewport plus at least one for panning + + var viewSize = this.map.getSize(); + + var origin = this.getTileOrigin(); + var resolution = this.map.getResolution(), + serverResolution = this.getServerResolution(), + ratio = resolution / serverResolution, + tileSize = { + w: this.tileSize.w / ratio, + h: this.tileSize.h / ratio + }; + + var minRows = Math.ceil(viewSize.h/tileSize.h) + + 2 * this.buffer + 1; + var minCols = Math.ceil(viewSize.w/tileSize.w) + + 2 * this.buffer + 1; + + var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution); + this.gridLayout = tileLayout; + + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + + var layerContainerDivLeft = this.map.layerContainerOriginPx.x; + var layerContainerDivTop = this.map.layerContainerOriginPx.y; + + var tileBounds = this.getTileBoundsForGridIndex(0, 0); + var startPx = this.map.getViewPortPxFromLonLat( + new OpenLayers.LonLat(tileBounds.left, tileBounds.top) + ); + startPx.x = Math.round(startPx.x) - layerContainerDivLeft; + startPx.y = Math.round(startPx.y) - layerContainerDivTop; + + var tileData = [], center = this.map.getCenter(); + + var rowidx = 0; + do { + var row = this.grid[rowidx]; + if (!row) { + row = []; + this.grid.push(row); + } + + var colidx = 0; + do { + tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx); + var px = startPx.clone(); + px.x = px.x + colidx * Math.round(tileSize.w); + px.y = px.y + rowidx * Math.round(tileSize.h); + var tile = row[colidx]; + if (!tile) { + tile = this.addTile(tileBounds, px); + this.addTileMonitoringHooks(tile); + row.push(tile); + } else { + tile.moveTo(tileBounds, px, false); + } + var tileCenter = tileBounds.getCenterLonLat(); + tileData.push({ + tile: tile, + distance: Math.pow(tileCenter.lon - center.lon, 2) + + Math.pow(tileCenter.lat - center.lat, 2) + }); + + colidx += 1; + } while ((tileBounds.right <= bounds.right + tilelon * this.buffer) + || colidx < minCols); + + rowidx += 1; + } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer) + || rowidx < minRows); + + //shave off exceess rows and colums + this.removeExcessTiles(rowidx, colidx); + + var resolution = this.getServerResolution(); + // store the resolution of the grid + this.gridResolution = resolution; + + //now actually draw the tiles + tileData.sort(function(a, b) { + return a.distance - b.distance; + }); + for (var i=0, ii=tileData.length; i<ii; ++i) { + tileData[i].tile.draw(); + } + }, + + /** + * Method: getMaxExtent + * Get this layer's maximum extent. (Implemented as a getter for + * potential specific implementations in sub-classes.) + * + * Returns: + * {<OpenLayers.Bounds>} + */ + getMaxExtent: function() { + return this.maxExtent; + }, + + /** + * APIMethod: addTile + * Create a tile, initialize it, and add it to the layer div. + * + * Parameters + * bounds - {<OpenLayers.Bounds>} + * position - {<OpenLayers.Pixel>} + * + * Returns: + * {<OpenLayers.Tile>} The added OpenLayers.Tile + */ + addTile: function(bounds, position) { + var tile = new this.tileClass( + this, position, bounds, null, this.tileSize, this.tileOptions + ); + this.events.triggerEvent("addtile", {tile: tile}); + return tile; + }, + + /** + * Method: addTileMonitoringHooks + * This function takes a tile as input and adds the appropriate hooks to + * the tile so that the layer can keep track of the loading tiles. + * + * Parameters: + * tile - {<OpenLayers.Tile>} + */ + addTileMonitoringHooks: function(tile) { + + var replacingCls = 'olTileReplacing'; + + tile.onLoadStart = function() { + //if that was first tile then trigger a 'loadstart' on the layer + if (this.loading === false) { + this.loading = true; + this.events.triggerEvent("loadstart"); + } + this.events.triggerEvent("tileloadstart", {tile: tile}); + this.numLoadingTiles++; + if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) { + OpenLayers.Element.addClass(tile.getTile(), replacingCls); + } + }; + + tile.onLoadEnd = function(evt) { + this.numLoadingTiles--; + var aborted = evt.type === 'unload'; + this.events.triggerEvent("tileloaded", { + tile: tile, + aborted: aborted + }); + if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) { + var tileDiv = tile.getTile(); + if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') { + var bufferTile = document.getElementById(tile.id + '_bb'); + if (bufferTile) { + bufferTile.parentNode.removeChild(bufferTile); + } + } + OpenLayers.Element.removeClass(tileDiv, replacingCls); + } + //if that was the last tile, then trigger a 'loadend' on the layer + if (this.numLoadingTiles === 0) { + if (this.backBuffer) { + if (this.backBuffer.childNodes.length === 0) { + // no tiles transitioning, remove immediately + this.removeBackBuffer(); + } else { + // wait until transition has ended or delay has passed + this._transitionElement = aborted ? + this.div.lastChild : tile.imgDiv; + var transitionendEvents = this.transitionendEvents; + for (var i=transitionendEvents.length-1; i>=0; --i) { + OpenLayers.Event.observe(this._transitionElement, + transitionendEvents[i], + this._removeBackBuffer); + } + // the removal of the back buffer is delayed to prevent + // flash effects due to the animation of tile displaying + this.backBufferTimerId = window.setTimeout( + this._removeBackBuffer, this.removeBackBufferDelay + ); + } + } + this.loading = false; + this.events.triggerEvent("loadend"); + } + }; + + tile.onLoadError = function() { + this.events.triggerEvent("tileerror", {tile: tile}); + }; + + tile.events.on({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, + scope: this + }); + }, + + /** + * Method: removeTileMonitoringHooks + * This function takes a tile as input and removes the tile hooks + * that were added in addTileMonitoringHooks() + * + * Parameters: + * tile - {<OpenLayers.Tile>} + */ + removeTileMonitoringHooks: function(tile) { + tile.unload(); + tile.events.un({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, + scope: this + }); + }, + + /** + * Method: moveGriddedTiles + */ + moveGriddedTiles: function() { + var buffer = this.buffer + 1; + while(true) { + var tlTile = this.grid[0][0]; + var tlViewPort = { + x: tlTile.position.x + + this.map.layerContainerOriginPx.x, + y: tlTile.position.y + + this.map.layerContainerOriginPx.y + }; + var ratio = this.getServerResolution() / this.map.getResolution(); + var tileSize = { + w: Math.round(this.tileSize.w * ratio), + h: Math.round(this.tileSize.h * ratio) + }; + if (tlViewPort.x > -tileSize.w * (buffer - 1)) { + this.shiftColumn(true, tileSize); + } else if (tlViewPort.x < -tileSize.w * buffer) { + this.shiftColumn(false, tileSize); + } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) { + this.shiftRow(true, tileSize); + } else if (tlViewPort.y < -tileSize.h * buffer) { + this.shiftRow(false, tileSize); + } else { + break; + } + } + }, + + /** + * Method: shiftRow + * Shifty grid work + * + * Parameters: + * prepend - {Boolean} if true, prepend to beginning. + * if false, then append to end + * tileSize - {Object} rendered tile size; object with w and h properties + */ + shiftRow: function(prepend, tileSize) { + var grid = this.grid; + var rowIndex = prepend ? 0 : (grid.length - 1); + var sign = prepend ? -1 : 1; + var rowSign = this.rowSign; + var tileLayout = this.gridLayout; + tileLayout.startrow += sign * rowSign; + + var modelRow = grid[rowIndex]; + var row = grid[prepend ? 'pop' : 'shift'](); + for (var i=0, len=row.length; i<len; i++) { + var tile = row[i]; + var position = modelRow[i].position.clone(); + position.y += tileSize.h * sign; + tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position); + } + grid[prepend ? 'unshift' : 'push'](row); + }, + + /** + * Method: shiftColumn + * Shift grid work in the other dimension + * + * Parameters: + * prepend - {Boolean} if true, prepend to beginning. + * if false, then append to end + * tileSize - {Object} rendered tile size; object with w and h properties + */ + shiftColumn: function(prepend, tileSize) { + var grid = this.grid; + var colIndex = prepend ? 0 : (grid[0].length - 1); + var sign = prepend ? -1 : 1; + var tileLayout = this.gridLayout; + tileLayout.startcol += sign; + + for (var i=0, len=grid.length; i<len; i++) { + var row = grid[i]; + var position = row[colIndex].position.clone(); + var tile = row[prepend ? 'pop' : 'shift'](); + position.x += tileSize.w * sign; + tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position); + row[prepend ? 'unshift' : 'push'](tile); + } + }, + + /** + * Method: removeExcessTiles + * When the size of the map or the buffer changes, we may need to + * remove some excess rows and columns. + * + * Parameters: + * rows - {Integer} Maximum number of rows we want our grid to have. + * columns - {Integer} Maximum number of columns we want our grid to have. + */ + removeExcessTiles: function(rows, columns) { + var i, l; + + // remove extra rows + while (this.grid.length > rows) { + var row = this.grid.pop(); + for (i=0, l=row.length; i<l; i++) { + var tile = row[i]; + this.destroyTile(tile); + } + } + + // remove extra columns + for (i=0, l=this.grid.length; i<l; i++) { + while (this.grid[i].length > columns) { + var row = this.grid[i]; + var tile = row.pop(); + this.destroyTile(tile); + } + } + }, + + /** + * Method: onMapResize + * For singleTile layers, this will set a new tile size according to the + * dimensions of the map pane. + */ + onMapResize: function() { + if (this.singleTile) { + this.clearGrid(); + this.setTileSize(); + } + }, + + /** + * APIMethod: getTileBounds + * Returns The tile bounds for a layer given a pixel location. + * + * Parameters: + * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport. + * + * Returns: + * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location. + */ + getTileBounds: function(viewPortPx) { + var maxExtent = this.maxExtent; + var resolution = this.getResolution(); + var tileMapWidth = resolution * this.tileSize.w; + var tileMapHeight = resolution * this.tileSize.h; + var mapPoint = this.getLonLatFromViewPortPx(viewPortPx); + var tileLeft = maxExtent.left + (tileMapWidth * + Math.floor((mapPoint.lon - + maxExtent.left) / + tileMapWidth)); + var tileBottom = maxExtent.bottom + (tileMapHeight * + Math.floor((mapPoint.lat - + maxExtent.bottom) / + tileMapHeight)); + return new OpenLayers.Bounds(tileLeft, tileBottom, + tileLeft + tileMapWidth, + tileBottom + tileMapHeight); + }, + + CLASS_NAME: "OpenLayers.Layer.Grid" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js b/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js new file mode 100644 index 0000000..ccb0291 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js @@ -0,0 +1,230 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer.js + */ + +/** + * Class: OpenLayers.Layer.HTTPRequest + * + * Inherits from: + * - <OpenLayers.Layer> + */ +OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, { + + /** + * Constant: URL_HASH_FACTOR + * {Float} Used to hash URL param strings for multi-WMS server selection. + * Set to the Golden Ratio per Knuth's recommendation. + */ + URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2, + + /** + * Property: url + * {Array(String) or String} This is either an array of url strings or + * a single url string. + */ + url: null, + + /** + * Property: params + * {Object} Hashtable of key/value parameters + */ + params: null, + + /** + * APIProperty: reproject + * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html + * for information on the replacement for this functionality. + * {Boolean} Whether layer should reproject itself based on base layer + * locations. This allows reprojection onto commercial layers. + * Default is false: Most layers can't reproject, but layers + * which can create non-square geographic pixels can, like WMS. + * + */ + reproject: false, + + /** + * Constructor: OpenLayers.Layer.HTTPRequest + * + * Parameters: + * name - {String} + * url - {Array(String) or String} + * params - {Object} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); + this.url = url; + if (!this.params) { + this.params = OpenLayers.Util.extend({}, params); + } + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + this.url = null; + this.params = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this + * <OpenLayers.Layer.HTTPRequest> + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.HTTPRequest(this.name, + this.url, + this.params, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + return obj; + }, + + /** + * APIMethod: setUrl + * + * Parameters: + * newUrl - {String} + */ + setUrl: function(newUrl) { + this.url = newUrl; + }, + + /** + * APIMethod: mergeNewParams + * + * Parameters: + * newParams - {Object} + * + * Returns: + * redrawn: {Boolean} whether the layer was actually redrawn. + */ + mergeNewParams:function(newParams) { + this.params = OpenLayers.Util.extend(this.params, newParams); + var ret = this.redraw(); + if(this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "params" + }); + } + return ret; + }, + + /** + * APIMethod: redraw + * Redraws the layer. Returns true if the layer was redrawn, false if not. + * + * Parameters: + * force - {Boolean} Force redraw by adding random parameter. + * + * Returns: + * {Boolean} The layer was redrawn. + */ + redraw: function(force) { + if (force) { + return this.mergeNewParams({"_olSalt": Math.random()}); + } else { + return OpenLayers.Layer.prototype.redraw.apply(this, []); + } + }, + + /** + * Method: selectUrl + * selectUrl() implements the standard floating-point multiplicative + * hash function described by Knuth, and hashes the contents of the + * given param string into a float between 0 and 1. This float is then + * scaled to the size of the provided urls array, and used to select + * a URL. + * + * Parameters: + * paramString - {String} + * urls - {Array(String)} + * + * Returns: + * {String} An entry from the urls array, deterministically selected based + * on the paramString. + */ + selectUrl: function(paramString, urls) { + var product = 1; + for (var i=0, len=paramString.length; i<len; i++) { + product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR; + product -= Math.floor(product); + } + return urls[Math.floor(product * urls.length)]; + }, + + /** + * Method: getFullRequestString + * Combine url with layer's params and these newParams. + * + * does checking on the serverPath variable, allowing for cases when it + * is supplied with trailing ? or &, as well as cases where not. + * + * return in formatted string like this: + * "server?key1=value1&key2=value2&key3=value3" + * + * WARNING: The altUrl parameter is deprecated and will be removed in 3.0. + * + * Parameters: + * newParams - {Object} + * altUrl - {String} Use this as the url instead of the layer's url + * + * Returns: + * {String} + */ + getFullRequestString:function(newParams, altUrl) { + + // if not altUrl passed in, use layer's url + var url = altUrl || this.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); + var paramsString = OpenLayers.Util.getParameterString(allParams); + + // if url is not a string, it should be an array of strings, + // in which case we will deterministically select one of them in + // order to evenly distribute requests to different urls. + // + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(paramsString, url); + } + + // 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]; + } + } + paramsString = OpenLayers.Util.getParameterString(allParams); + + return OpenLayers.Util.urlAppend(url, paramsString); + }, + + CLASS_NAME: "OpenLayers.Layer.HTTPRequest" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Image.js b/misc/openlayers/lib/OpenLayers/Layer/Image.js new file mode 100644 index 0000000..b96e369 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Image.js @@ -0,0 +1,259 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Class: OpenLayers.Layer.Image + * Instances of OpenLayers.Layer.Image are used to display data from a web + * accessible image as a map layer. Create a new image layer with the + * <OpenLayers.Layer.Image> constructor. + * + * Inherits from: + * - <OpenLayers.Layer> + */ +OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, { + + /** + * Property: isBaseLayer + * {Boolean} The layer is a base layer. Default is true. Set this property + * in the layer options + */ + isBaseLayer: true, + + /** + * Property: url + * {String} URL of the image to use + */ + url: null, + + /** + * Property: extent + * {<OpenLayers.Bounds>} The image bounds in map units. This extent will + * also be used as the default maxExtent for the layer. If you wish + * to have a maxExtent that is different than the image extent, set the + * maxExtent property of the options argument (as with any other layer). + */ + extent: null, + + /** + * Property: size + * {<OpenLayers.Size>} The image size in pixels + */ + size: null, + + /** + * Property: tile + * {<OpenLayers.Tile.Image>} + */ + tile: null, + + /** + * Property: aspectRatio + * {Float} The ratio of height/width represented by a single pixel in the + * graphic + */ + aspectRatio: null, + + /** + * Constructor: OpenLayers.Layer.Image + * Create a new image layer + * + * Parameters: + * name - {String} A name for the layer. + * url - {String} Relative or absolute path to the image + * extent - {<OpenLayers.Bounds>} The extent represented by the image + * size - {<OpenLayers.Size>} The size (in pixels) of the image + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, extent, size, options) { + this.url = url; + this.extent = extent; + this.maxExtent = extent; + this.size = size; + OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); + + this.aspectRatio = (this.extent.getHeight() / this.size.h) / + (this.extent.getWidth() / this.size.w); + }, + + /** + * Method: destroy + * Destroy this layer + */ + destroy: function() { + if (this.tile) { + this.removeTileMonitoringHooks(this.tile); + this.tile.destroy(); + this.tile = null; + } + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Paramters: + * obj - {Object} An optional layer (is this ever used?) + * + * Returns: + * {<OpenLayers.Layer.Image>} An exact copy of this layer + */ + clone: function(obj) { + + if(obj == null) { + obj = new OpenLayers.Layer.Image(this.name, + this.url, + this.extent, + this.size, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + + return obj; + }, + + /** + * APIMethod: setMap + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + /** + * If nothing to do with resolutions has been set, assume a single + * resolution determined by ratio*extent/size - if an image has a + * pixel aspect ratio different than one (as calculated above), the + * image will be stretched in one dimension only. + */ + if( this.options.maxResolution == null ) { + this.options.maxResolution = this.aspectRatio * + this.extent.getWidth() / + this.size.w; + } + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + }, + + /** + * Method: moveTo + * Create the tile for the image or resize it for the new resolution + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + var firstRendering = (this.tile == null); + + if(zoomChanged || firstRendering) { + + //determine new tile size + this.setTileSize(); + + //determine new position (upper left corner of new bounds) + var ulPx = this.map.getLayerPxFromLonLat({ + lon: this.extent.left, + lat: this.extent.top + }); + + if(firstRendering) { + //create the new tile + this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, + null, this.tileSize); + this.addTileMonitoringHooks(this.tile); + } else { + //just resize the tile and set it's new position + this.tile.size = this.tileSize.clone(); + this.tile.position = ulPx.clone(); + } + this.tile.draw(); + } + }, + + /** + * Set the tile size based on the map size. + */ + setTileSize: function() { + var tileWidth = this.extent.getWidth() / this.map.getResolution(); + var tileHeight = this.extent.getHeight() / this.map.getResolution(); + this.tileSize = new OpenLayers.Size(tileWidth, tileHeight); + }, + + /** + * Method: addTileMonitoringHooks + * This function takes a tile as input and adds the appropriate hooks to + * the tile so that the layer can keep track of the loading tiles. + * + * Parameters: + * tile - {<OpenLayers.Tile>} + */ + addTileMonitoringHooks: function(tile) { + tile.onLoadStart = function() { + this.events.triggerEvent("loadstart"); + }; + tile.events.register("loadstart", this, tile.onLoadStart); + + tile.onLoadEnd = function() { + this.events.triggerEvent("loadend"); + }; + tile.events.register("loadend", this, tile.onLoadEnd); + tile.events.register("unload", this, tile.onLoadEnd); + }, + + /** + * Method: removeTileMonitoringHooks + * This function takes a tile as input and removes the tile hooks + * that were added in <addTileMonitoringHooks>. + * + * Parameters: + * tile - {<OpenLayers.Tile>} + */ + removeTileMonitoringHooks: function(tile) { + tile.unload(); + tile.events.un({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + scope: this + }); + }, + + /** + * APIMethod: setUrl + * + * Parameters: + * newUrl - {String} + */ + setUrl: function(newUrl) { + this.url = newUrl; + this.tile.draw(); + }, + + /** + * APIMethod: getURL + * The url we return is always the same (the image itself never changes) + * so we can ignore the bounds parameter (it will always be the same, + * anyways) + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + */ + getURL: function(bounds) { + return this.url; + }, + + CLASS_NAME: "OpenLayers.Layer.Image" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/KaMap.js b/misc/openlayers/lib/OpenLayers/Layer/KaMap.js new file mode 100644 index 0000000..3de2224 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/KaMap.js @@ -0,0 +1,192 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.KaMap + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.KaMap = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * {Boolean} KaMap Layer is always a base layer + */ + isBaseLayer: true, + + /** + * Constant: DEFAULT_PARAMS + * {Object} parameters set by default. The default parameters set + * the format via the 'i' parameter to 'jpeg'. + */ + DEFAULT_PARAMS: { + i: 'jpeg', + map: '' + }, + + /** + * Constructor: OpenLayers.Layer.KaMap + * + * Parameters: + * name - {String} + * url - {String} + * params - {Object} Parameters to be sent to the HTTP server in the + * query string for the tile. The format can be set via the 'i' + * parameter (defaults to jpg) , and the map should be set via + * the 'map' parameter. It has been reported that ka-Map may behave + * inconsistently if your format parameter does not match the format + * parameter configured in your config.php. (See ticket #327 for more + * information.) + * options - {Object} Additional options for the layer. Any of the + * APIProperties listed on this layer, and any layer types it + * extends, can be overridden through the options parameter. + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments); + this.params = OpenLayers.Util.applyDefaults( + this.params, this.DEFAULT_PARAMS + ); + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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) { + bounds = this.adjustBounds(bounds); + var mapRes = this.map.getResolution(); + var scale = Math.round((this.map.getScale() * 10000)) / 10000; + var pX = Math.round(bounds.left / mapRes); + var pY = -Math.round(bounds.top / mapRes); + return this.getFullRequestString( + { t: pY, + l: pX, + s: scale + }); + }, + + /** + * Method: calculateGridLayout + * ka-Map uses the center point of the map as an origin for + * its tiles. Override calculateGridLayout to center tiles + * correctly for this case. + * + * Parameters: + * bounds - {<OpenLayers.Bound>} + * origin - {<OpenLayers.LonLat>} + * resolution - {Number} + * + * Returns: + * {Object} Object containing properties tilelon, tilelat, startcol, + * startrow + */ + calculateGridLayout: function(bounds, origin, resolution) { + var tilelon = resolution*this.tileSize.w; + var tilelat = resolution*this.tileSize.h; + + var offsetlon = bounds.left; + var tilecol = Math.floor(offsetlon/tilelon) - this.buffer; + + var offsetlat = bounds.top; + var tilerow = Math.floor(offsetlat/tilelat) + this.buffer; + + return { + tilelon: tilelon, tilelat: tilelat, + startcol: tilecol, startrow: tilerow + }; + }, + + /** + * Method: getTileBoundsForGridIndex + * + * Parameters: + * row - {Number} The row of the grid + * col - {Number} The column of the grid + * + * Returns: + * {<OpenLayers.Bounds>} The bounds for the tile at (row, col) + */ + getTileBoundsForGridIndex: function(row, col) { + var origin = this.getTileOrigin(); + var tileLayout = this.gridLayout; + var tilelon = tileLayout.tilelon; + var tilelat = tileLayout.tilelat; + var minX = (tileLayout.startcol + col) * tilelon; + var minY = (tileLayout.startrow - row) * tilelat; + return new OpenLayers.Bounds( + minX, minY, + minX + tilelon, minY + tilelat + ); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.KaMap(this.name, + this.url, + this.params, + 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 + if (this.tileSize != null) { + obj.tileSize = this.tileSize.clone(); + } + + // we do not want to copy reference to grid, so we make a new array + obj.grid = []; + + return obj; + }, + + /** + * APIMethod: getTileBounds + * Returns The tile bounds for a layer given a pixel location. + * + * Parameters: + * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport. + * + * Returns: + * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location. + */ + getTileBounds: function(viewPortPx) { + var resolution = this.getResolution(); + var tileMapWidth = resolution * this.tileSize.w; + var tileMapHeight = resolution * this.tileSize.h; + var mapPoint = this.getLonLatFromViewPortPx(viewPortPx); + var tileLeft = tileMapWidth * Math.floor(mapPoint.lon / tileMapWidth); + var tileBottom = tileMapHeight * Math.floor(mapPoint.lat / tileMapHeight); + return new OpenLayers.Bounds(tileLeft, tileBottom, + tileLeft + tileMapWidth, + tileBottom + tileMapHeight); + }, + + CLASS_NAME: "OpenLayers.Layer.KaMap" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js b/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js new file mode 100644 index 0000000..be6bdb0 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js @@ -0,0 +1,143 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + * @requires OpenLayers/Layer/KaMap.js + */ + +/** + * Class: OpenLayers.Layer.KaMapCache + * + * This class is designed to talk directly to a web-accessible ka-Map + * cache generated by the precache2.php script. + * + * To create a a new KaMapCache layer, you must indicate also the "i" parameter + * (that will be used to calculate the file extension), and another special + * parameter, object names "metaTileSize", with "h" (height) and "w" (width) + * properties. + * + * // Create a new kaMapCache layer. + * var kamap_base = new OpenLayers.Layer.KaMapCache( + * "Satellite", + * "http://www.example.org/web/acessible/cache", + * {g: "satellite", map: "world", i: 'png24', metaTileSize: {w: 5, h: 5} } + * ); + * + * // Create an kaMapCache overlay layer (using "isBaseLayer: false"). + * // Forces the output to be a "gif", using the "i" parameter. + * var kamap_overlay = new OpenLayers.Layer.KaMapCache( + * "Streets", + * "http://www.example.org/web/acessible/cache", + * {g: "streets", map: "world", i: "gif", metaTileSize: {w: 5, h: 5} }, + * {isBaseLayer: false} + * ); + * + * The cache URLs must look like: + * var/cache/World/50000/Group_Name/def/t-440320/l20480 + * + * This means that the cache generated via tile.php will *not* work with + * this class, and should instead use the KaMap layer. + * + * More information is available in Ticket #1518. + * + * Inherits from: + * - <OpenLayers.Layer.KaMap> + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.KaMapCache = OpenLayers.Class(OpenLayers.Layer.KaMap, { + + /** + * Constant: IMAGE_EXTENSIONS + * {Object} Simple hash map to convert format to extension. + */ + IMAGE_EXTENSIONS: { + 'jpeg': 'jpg', + 'gif' : 'gif', + 'png' : 'png', + 'png8' : 'png', + 'png24' : 'png', + 'dithered' : 'png' + }, + + /** + * Constant: DEFAULT_FORMAT + * {Object} Simple hash map to convert format to extension. + */ + DEFAULT_FORMAT: 'jpeg', + + /** + * Constructor: OpenLayers.Layer.KaMapCache + * + * Parameters: + * name - {String} + * url - {String} + * params - {Object} Parameters to be sent to the HTTP server in the + * query string for the tile. The format can be set via the 'i' + * parameter (defaults to jpg) , and the map should be set via + * the 'map' parameter. It has been reported that ka-Map may behave + * inconsistently if your format parameter does not match the format + * parameter configured in your config.php. (See ticket #327 for more + * information.) + * options - {Object} Additional options for the layer. Any of the + * APIProperties listed on this layer, and any layer types it + * extends, can be overridden through the options parameter. + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.KaMap.prototype.initialize.apply(this, arguments); + this.extension = this.IMAGE_EXTENSIONS[this.params.i.toLowerCase() || this.DEFAULT_FORMAT]; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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) { + bounds = this.adjustBounds(bounds); + var mapRes = this.map.getResolution(); + var scale = Math.round((this.map.getScale() * 10000)) / 10000; + var pX = Math.round(bounds.left / mapRes); + var pY = -Math.round(bounds.top / mapRes); + + var metaX = Math.floor(pX / this.tileSize.w / this.params.metaTileSize.w) * this.tileSize.w * this.params.metaTileSize.w; + var metaY = Math.floor(pY / this.tileSize.h / this.params.metaTileSize.h) * this.tileSize.h * this.params.metaTileSize.h; + + var components = [ + "/", + this.params.map, + "/", + scale, + "/", + this.params.g.replace(/\s/g, '_'), + "/def/t", + metaY, + "/l", + metaX, + "/t", + pY, + "l", + pX, + ".", + this.extension + ]; + + var url = this.url; + + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(components.join(''), url); + } + return url + components.join(""); + }, + + CLASS_NAME: "OpenLayers.Layer.KaMapCache" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js b/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js new file mode 100644 index 0000000..8f7d979 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js @@ -0,0 +1,443 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository 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.Grid> + */ +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 + * {<OpenLayers.Size>} Tile size as produced by MapGuide server + **/ + defaultSize: new OpenLayers.Size(300,300), + + /** + * Property: tileOriginCorner + * {String} MapGuide tile server uses top-left as tile origin + **/ + tileOriginCorner: "tl", + + /** + * 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 - {Object} 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: + * {<OpenLayers.Layer.MapGuide>} 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: getURL + * Return a query string for this layer + * + * Parameters: + * bounds - {<OpenLayers.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; + }, + + CLASS_NAME: "OpenLayers.Layer.MapGuide" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/MapServer.js b/misc/openlayers/lib/OpenLayers/Layer/MapServer.js new file mode 100644 index 0000000..713035f --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/MapServer.js @@ -0,0 +1,181 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.MapServer + * Instances of OpenLayers.Layer.MapServer are used to display + * data from a MapServer CGI instance. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.MapServer = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Hashtable of default parameter key/value pairs + */ + DEFAULT_PARAMS: { + mode: "map", + map_imagetype: "png" + }, + + /** + * Constructor: OpenLayers.Layer.MapServer + * Create a new MapServer layer object + * + * Parameters: + * name - {String} A name for the layer + * url - {String} Base url for the MapServer CGI + * (e.g. http://www2.dmsolutions.ca/cgi-bin/mapserv) + * params - {Object} An object with key/value pairs representing the + * GetMap query string parameters and parameter values. + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, params, options) { + OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments); + + this.params = OpenLayers.Util.applyDefaults( + this.params, this.DEFAULT_PARAMS + ); + + // unless explicitly set in options, if the layer is transparent, + // it will be an overlay + if (options == null || options.isBaseLayer == null) { + this.isBaseLayer = ((this.params.transparent != "true") && + (this.params.transparent != true)); + } + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {<OpenLayers.Layer.MapServer>} An exact clone of this layer + */ + clone: function (obj) { + if (obj == null) { + obj = new OpenLayers.Layer.MapServer(this.name, + this.url, + this.params, + 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: getURL + * Return a query string for this layer + * + * Parameters: + * bounds - {<OpenLayers.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) { + bounds = this.adjustBounds(bounds); + // Make a list, so that getFullRequestString uses literal "," + var extent = [bounds.left, bounds. bottom, bounds.right, bounds.top]; + + var imageSize = this.getImageSize(); + + // make lists, so that literal ','s are used + var url = this.getFullRequestString( + {mapext: extent, + imgext: extent, + map_size: [imageSize.w, imageSize.h], + imgx: imageSize.w / 2, + imgy: imageSize.h / 2, + imgxy: [imageSize.w, imageSize.h] + }); + + return url; + }, + + /** + * Method: getFullRequestString + * combine the layer's url with its params and these newParams. + * + * Parameters: + * newParams - {Object} New parameters that should be added to the + * request string. + * altUrl - {String} (optional) Replace the URL in the full request + * string with the provided URL. + * + * Returns: + * {String} A string with the layer's url and parameters embedded in it. + */ + getFullRequestString:function(newParams, altUrl) { + // use layer's url unless altUrl passed in + var url = (altUrl == null) ? this.url : altUrl; + + // 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); + var paramsString = OpenLayers.Util.getParameterString(allParams); + + // if url is not a string, it should be an array of strings, + // in which case we will deterministically select one of them in + // order to evenly distribute requests to different urls. + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(paramsString, url); + } + + // 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]; + } + } + paramsString = OpenLayers.Util.getParameterString(allParams); + + // requestString always starts with url + var requestString = url; + + // MapServer 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; + }, + + CLASS_NAME: "OpenLayers.Layer.MapServer" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Markers.js b/misc/openlayers/lib/OpenLayers/Layer/Markers.js new file mode 100644 index 0000000..de848d4 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Markers.js @@ -0,0 +1,187 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer.js + */ + +/** + * Class: OpenLayers.Layer.Markers + * + * Inherits from: + * - <OpenLayers.Layer> + */ +OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, { + + /** + * APIProperty: isBaseLayer + * {Boolean} Markers layer is never a base layer. + */ + isBaseLayer: false, + + /** + * APIProperty: markers + * {Array(<OpenLayers.Marker>)} internal marker list + */ + markers: null, + + + /** + * Property: drawn + * {Boolean} internal state of drawing. This is a workaround for the fact + * that the map does not call moveTo with a zoomChanged when the map is + * first starting up. This lets us catch the case where we have *never* + * drawn the layer, and draw it even if the zoom hasn't changed. + */ + drawn: false, + + /** + * Constructor: OpenLayers.Layer.Markers + * Create a Markers layer. + * + * Parameters: + * name - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + this.markers = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + this.clearMarkers(); + this.markers = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for all the markers. + * + * Parameters: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity != this.opacity) { + this.opacity = opacity; + for (var i=0, len=this.markers.length; i<len; i++) { + this.markers[i].setOpacity(this.opacity); + } + } + }, + + /** + * Method: moveTo + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + if (zoomChanged || !this.drawn) { + for(var i=0, len=this.markers.length; i<len; i++) { + this.drawMarker(this.markers[i]); + } + this.drawn = true; + } + }, + + /** + * APIMethod: addMarker + * + * Parameters: + * marker - {<OpenLayers.Marker>} + */ + addMarker: function(marker) { + this.markers.push(marker); + + if (this.opacity < 1) { + marker.setOpacity(this.opacity); + } + + if (this.map && this.map.getExtent()) { + marker.map = this.map; + this.drawMarker(marker); + } + }, + + /** + * APIMethod: removeMarker + * + * Parameters: + * marker - {<OpenLayers.Marker>} + */ + removeMarker: function(marker) { + if (this.markers && this.markers.length) { + OpenLayers.Util.removeItem(this.markers, marker); + marker.erase(); + } + }, + + /** + * Method: clearMarkers + * This method removes all markers from a layer. The markers are not + * destroyed by this function, but are removed from the list of markers. + */ + clearMarkers: function() { + if (this.markers != null) { + while(this.markers.length > 0) { + this.removeMarker(this.markers[0]); + } + } + }, + + /** + * Method: drawMarker + * Calculate the pixel location for the marker, create it, and + * add it to the layer's div + * + * Parameters: + * marker - {<OpenLayers.Marker>} + */ + drawMarker: function(marker) { + var px = this.map.getLayerPxFromLonLat(marker.lonlat); + if (px == null) { + marker.display(false); + } else { + if (!marker.isDrawn()) { + var markerImg = marker.draw(px); + this.div.appendChild(markerImg); + } else if(marker.icon) { + marker.icon.moveTo(px); + } + } + }, + + /** + * APIMethod: getDataExtent + * Calculates the max extent which includes all of the markers. + * + * Returns: + * {<OpenLayers.Bounds>} + */ + getDataExtent: function () { + var maxExtent = null; + + if ( this.markers && (this.markers.length > 0)) { + var maxExtent = new OpenLayers.Bounds(); + for(var i=0, len=this.markers.length; i<len; i++) { + var marker = this.markers[i]; + maxExtent.extend(marker.lonlat); + } + } + + return maxExtent; + }, + + CLASS_NAME: "OpenLayers.Layer.Markers" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/OSM.js b/misc/openlayers/lib/OpenLayers/Layer/OSM.js new file mode 100644 index 0000000..49150fd --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/OSM.js @@ -0,0 +1,123 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + */ + +/** + * Class: OpenLayers.Layer.OSM + * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap + * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use + * a different layer instead, you need to provide a different + * URL to the constructor. Here's an example for using OpenCycleMap: + * + * (code) + * new OpenLayers.Layer.OSM("OpenCycleMap", + * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); + * (end) + * + * Inherits from: + * - <OpenLayers.Layer.XYZ> + */ +OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * APIProperty: name + * {String} The layer name. Defaults to "OpenStreetMap" if the first + * argument to the constructor is null or undefined. + */ + name: "OpenStreetMap", + + /** + * APIProperty: url + * {String} The tileset URL scheme. Defaults to + * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png + * (the official OSM tileset) if the second argument to the constructor + * is null or undefined. To use another tileset you can have something + * like this: + * (code) + * new OpenLayers.Layer.OSM("OpenCycleMap", + * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png", + * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]); + * (end) + */ + url: [ + 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png' + ], + + /** + * Property: attribution + * {String} The layer attribution. + */ + attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors", + + /** + * Property: sphericalMercator + * {Boolean} + */ + sphericalMercator: true, + + /** + * Property: wrapDateLine + * {Boolean} + */ + wrapDateLine: true, + + /** APIProperty: tileOptions + * {Object} optional configuration options for <OpenLayers.Tile> instances + * created by this Layer. Default is + * + * (code) + * {crossOriginKeyword: 'anonymous'} + * (end) + * + * When using OSM tilesets other than the default ones, it may be + * necessary to set this to + * + * (code) + * {crossOriginKeyword: null} + * (end) + * + * if the server does not send Access-Control-Allow-Origin headers. + */ + tileOptions: null, + + /** + * Constructor: OpenLayers.Layer.OSM + * + * Parameters: + * name - {String} The layer name. + * url - {String} The tileset URL scheme. + * options - {Object} Configuration options for the layer. Any inherited + * layer option can be set in this object (e.g. + * <OpenLayers.Layer.Grid.buffer>). + */ + initialize: function(name, url, options) { + OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments); + this.tileOptions = OpenLayers.Util.extend({ + crossOriginKeyword: 'anonymous' + }, this.options && this.options.tileOptions); + }, + + /** + * Method: clone + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.OSM( + this.name, this.url, this.getOptions()); + } + obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]); + return obj; + }, + + CLASS_NAME: "OpenLayers.Layer.OSM" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js b/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js new file mode 100644 index 0000000..10a4d02 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js @@ -0,0 +1,299 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Vector.js + * @requires OpenLayers/Geometry/Polygon.js + */ + +/** + * Class: OpenLayers.Layer.PointGrid + * A point grid layer dynamically generates a regularly spaced grid of point + * features. This is a specialty layer for cases where an application needs + * a regular grid of points. It can be used, for example, in an editing + * environment to snap to a grid. + * + * Create a new vector layer with the <OpenLayers.Layer.PointGrid> constructor. + * (code) + * // create a grid with points spaced at 10 map units + * var points = new OpenLayers.Layer.PointGrid({dx: 10, dy: 10}); + * + * // create a grid with different x/y spacing rotated 15 degrees clockwise. + * var points = new OpenLayers.Layer.PointGrid({dx: 5, dy: 10, rotation: 15}); + * (end) + * + * Inherits from: + * - <OpenLayers.Layer.Vector> + */ +OpenLayers.Layer.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * APIProperty: dx + * {Number} Point grid spacing in the x-axis direction (map units). + * Read-only. Use the <setSpacing> method to modify this value. + */ + dx: null, + + /** + * APIProperty: dy + * {Number} Point grid spacing in the y-axis direction (map units). + * Read-only. Use the <setSpacing> method to modify this value. + */ + dy: null, + + /** + * APIProperty: ratio + * {Number} Ratio of the desired grid size to the map viewport size. + * Default is 1.5. Larger ratios mean the grid is recalculated less often + * while panning. The <maxFeatures> setting has precedence when determining + * grid size. Read-only. Use the <setRatio> method to modify this value. + */ + ratio: 1.5, + + /** + * APIProperty: maxFeatures + * {Number} The maximum number of points to generate in the grid. Default + * is 250. Read-only. Use the <setMaxFeatures> method to modify this value. + */ + maxFeatures: 250, + + /** + * APIProperty: rotation + * {Number} Grid rotation (in degrees clockwise from the positive x-axis). + * Default is 0. Read-only. Use the <setRotation> method to modify this + * value. + */ + rotation: 0, + + /** + * APIProperty: origin + * {<OpenLayers.LonLat>} Grid origin. The grid lattice will be aligned with + * the origin. If not set at construction, the center of the map's maximum + * extent is used. Read-only. Use the <setOrigin> method to modify this + * value. + */ + origin: null, + + /** + * Property: gridBounds + * {<OpenLayers.Bounds>} Internally cached grid bounds (with optional + * rotation applied). + */ + gridBounds: null, + + /** + * Constructor: OpenLayers.Layer.PointGrid + * Creates a new point grid layer. + * + * Parameters: + * config - {Object} An object containing all configuration properties for + * the layer. The <dx> and <dy> properties are required to be set at + * construction. Any other layer properties may be set in this object. + */ + initialize: function(config) { + config = config || {}; + OpenLayers.Layer.Vector.prototype.initialize.apply(this, [config.name, config]); + }, + + /** + * Method: setMap + * The layer has been added to the map. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); + map.events.register("moveend", this, this.onMoveEnd); + }, + + /** + * Method: removeMap + * The layer has been removed from the map. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + map.events.unregister("moveend", this, this.onMoveEnd); + OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments); + }, + + /** + * APIMethod: setRatio + * Set the grid <ratio> property and update the grid. Can only be called + * after the layer has been added to a map with a center/extent. + * + * Parameters: + * ratio - {Number} + */ + setRatio: function(ratio) { + this.ratio = ratio; + this.updateGrid(true); + }, + + /** + * APIMethod: setMaxFeatures + * Set the grid <maxFeatures> property and update the grid. Can only be + * called after the layer has been added to a map with a center/extent. + * + * Parameters: + * maxFeatures - {Number} + */ + setMaxFeatures: function(maxFeatures) { + this.maxFeatures = maxFeatures; + this.updateGrid(true); + }, + + /** + * APIMethod: setSpacing + * Set the grid <dx> and <dy> properties and update the grid. If only one + * argument is provided, it will be set as <dx> and <dy>. Can only be + * called after the layer has been added to a map with a center/extent. + * + * Parameters: + * dx - {Number} + * dy - {Number} + */ + setSpacing: function(dx, dy) { + this.dx = dx; + this.dy = dy || dx; + this.updateGrid(true); + }, + + /** + * APIMethod: setOrigin + * Set the grid <origin> property and update the grid. Can only be called + * after the layer has been added to a map with a center/extent. + * + * Parameters: + * origin - {<OpenLayers.LonLat>} + */ + setOrigin: function(origin) { + this.origin = origin; + this.updateGrid(true); + }, + + /** + * APIMethod: getOrigin + * Get the grid <origin> property. + * + * Returns: + * {<OpenLayers.LonLat>} The grid origin. + */ + getOrigin: function() { + if (!this.origin) { + this.origin = this.map.getExtent().getCenterLonLat(); + } + return this.origin; + }, + + /** + * APIMethod: setRotation + * Set the grid <rotation> property and update the grid. Rotation values + * are in degrees clockwise from the positive x-axis (negative values + * for counter-clockwise rotation). Can only be called after the layer + * has been added to a map with a center/extent. + * + * Parameters: + * rotation - {Number} Degrees clockwise from the positive x-axis. + */ + setRotation: function(rotation) { + this.rotation = rotation; + this.updateGrid(true); + }, + + /** + * Method: onMoveEnd + * Listener for map "moveend" events. + */ + onMoveEnd: function() { + this.updateGrid(); + }, + + /** + * Method: getViewBounds + * Gets the (potentially rotated) view bounds for grid calculations. + * + * Returns: + * {<OpenLayers.Bounds>} + */ + getViewBounds: function() { + var bounds = this.map.getExtent(); + if (this.rotation) { + var origin = this.getOrigin(); + var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat); + var rect = bounds.toGeometry(); + rect.rotate(-this.rotation, rotationOrigin); + bounds = rect.getBounds(); + } + return bounds; + }, + + /** + * Method: updateGrid + * Update the grid. + * + * Parameters: + * force - {Boolean} Update the grid even if the previous bounds are still + * valid. + */ + updateGrid: function(force) { + if (force || this.invalidBounds()) { + var viewBounds = this.getViewBounds(); + var origin = this.getOrigin(); + var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat); + var viewBoundsWidth = viewBounds.getWidth(); + var viewBoundsHeight = viewBounds.getHeight(); + var aspectRatio = viewBoundsWidth / viewBoundsHeight; + var maxHeight = Math.sqrt(this.dx * this.dy * this.maxFeatures / aspectRatio); + var maxWidth = maxHeight * aspectRatio; + var gridWidth = Math.min(viewBoundsWidth * this.ratio, maxWidth); + var gridHeight = Math.min(viewBoundsHeight * this.ratio, maxHeight); + var center = viewBounds.getCenterLonLat(); + this.gridBounds = new OpenLayers.Bounds( + center.lon - (gridWidth / 2), + center.lat - (gridHeight / 2), + center.lon + (gridWidth / 2), + center.lat + (gridHeight / 2) + ); + var rows = Math.floor(gridHeight / this.dy); + var cols = Math.floor(gridWidth / this.dx); + var gridLeft = origin.lon + (this.dx * Math.ceil((this.gridBounds.left - origin.lon) / this.dx)); + var gridBottom = origin.lat + (this.dy * Math.ceil((this.gridBounds.bottom - origin.lat) / this.dy)); + var features = new Array(rows * cols); + var x, y, point; + for (var i=0; i<cols; ++i) { + x = gridLeft + (i * this.dx); + for (var j=0; j<rows; ++j) { + y = gridBottom + (j * this.dy); + point = new OpenLayers.Geometry.Point(x, y); + if (this.rotation) { + point.rotate(this.rotation, rotationOrigin); + } + features[(i*rows)+j] = new OpenLayers.Feature.Vector(point); + } + } + this.destroyFeatures(this.features, {silent: true}); + this.addFeatures(features, {silent: true}); + } + }, + + /** + * Method: invalidBounds + * Determine whether the previously generated point grid is invalid. + * This occurs when the map bounds extends beyond the previously + * generated grid bounds. + * + * Returns: + * {Boolean} + */ + invalidBounds: function() { + return !this.gridBounds || !this.gridBounds.containsBounds(this.getViewBounds()); + }, + + CLASS_NAME: "OpenLayers.Layer.PointGrid" + +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js b/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js new file mode 100644 index 0000000..accfac7 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js @@ -0,0 +1,125 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Layer.PointTrack + * Vector layer to display ordered point features as a line, creating one + * LineString feature for each pair of two points. + * + * Inherits from: + * - <OpenLayers.Layer.Vector> + */ +OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * APIProperty: dataFrom + * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or + * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines + * should get the data/attributes from one of the two points it is + * composed of, which one should it be? + */ + dataFrom: null, + + /** + * APIProperty: styleFrom + * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or + * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines + * should get the style from one of the two points it is composed of, + * which one should it be? + */ + styleFrom: null, + + /** + * Constructor: OpenLayers.PointTrack + * Constructor for a new OpenLayers.PointTrack instance. + * + * Parameters: + * name - {String} name of the layer + * options - {Object} Optional object with properties to tag onto the + * instance. + */ + + /** + * APIMethod: addNodes + * Adds point features that will be used to create lines from, using point + * pairs. The first point of a pair will be the source node, the second + * will be the target node. + * + * Parameters: + * pointFeatures - {Array(<OpenLayers.Feature>)} + * options - {Object} + * + * Supported options: + * silent - {Boolean} true to suppress (before)feature(s)added events + */ + addNodes: function(pointFeatures, options) { + if (pointFeatures.length < 2) { + throw new Error("At least two point features have to be added to " + + "create a line from"); + } + + var lines = new Array(pointFeatures.length-1); + + var pointFeature, startPoint, endPoint; + for(var i=0, len=pointFeatures.length; i<len; i++) { + pointFeature = pointFeatures[i]; + endPoint = pointFeature.geometry; + + if (!endPoint) { + var lonlat = pointFeature.lonlat; + endPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat); + } else if(endPoint.CLASS_NAME != "OpenLayers.Geometry.Point") { + throw new TypeError("Only features with point geometries are supported."); + } + + if(i > 0) { + var attributes = (this.dataFrom != null) ? + (pointFeatures[i+this.dataFrom].data || + pointFeatures[i+this.dataFrom].attributes) : + null; + var style = (this.styleFrom != null) ? + (pointFeatures[i+this.styleFrom].style) : + null; + var line = new OpenLayers.Geometry.LineString([startPoint, + endPoint]); + + lines[i-1] = new OpenLayers.Feature.Vector(line, attributes, + style); + } + + startPoint = endPoint; + } + + this.addFeatures(lines, options); + }, + + CLASS_NAME: "OpenLayers.Layer.PointTrack" +}); + +/** + * Constant: OpenLayers.Layer.PointTrack.SOURCE_NODE + * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and + * <OpenLayers.Layer.PointTrack.styleFrom> + */ +OpenLayers.Layer.PointTrack.SOURCE_NODE = -1; + +/** + * Constant: OpenLayers.Layer.PointTrack.TARGET_NODE + * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and + * <OpenLayers.Layer.PointTrack.styleFrom> + */ +OpenLayers.Layer.PointTrack.TARGET_NODE = 0; + +/** + * Constant: OpenLayers.Layer.PointTrack.dataFrom + * {Object} with the following keys - *deprecated* + * - SOURCE_NODE: take data/attributes from the source node of the line + * - TARGET_NODE: take data/attributes from the target node of the line + */ +OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 0}; diff --git a/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js b/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js new file mode 100644 index 0000000..60bb2fe --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js @@ -0,0 +1,146 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Layer.SphericalMercator + * A mixin for layers that wraps up the pieces neccesary to have a coordinate + * conversion for working with commercial APIs which use a spherical + * mercator projection. Using this layer as a base layer, additional + * layers can be used as overlays if they are in the same projection. + * + * A layer is given properties of this object by setting the sphericalMercator + * property to true. + * + * More projection information: + * - http://spatialreference.org/ref/user/google-projection/ + * + * Proj4 Text: + * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 + * +k=1.0 +units=m +nadgrids=@null +no_defs + * + * WKT: + * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84", + * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], + * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], + * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]], + * PROJECTION["Mercator_1SP_Google"], + * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], + * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], + * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST], + * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]] + */ +OpenLayers.Layer.SphericalMercator = { + + /** + * Method: getExtent + * Get the map's extent. + * + * Returns: + * {<OpenLayers.Bounds>} The map extent. + */ + getExtent: function() { + var extent = null; + if (this.sphericalMercator) { + extent = this.map.calculateBounds(); + } else { + extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this); + } + return extent; + }, + + /** + * Method: getLonLatFromViewPortPx + * Get a map location from a pixel location + * + * Parameters: + * viewPortPx - {<OpenLayers.Pixel>} + * + * Returns: + * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view + * port OpenLayers.Pixel, translated into lon/lat by map lib + * If the map lib is not loaded or not centered, returns null + */ + getLonLatFromViewPortPx: function (viewPortPx) { + return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments); + }, + + /** + * Method: getViewPortPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {<OpenLayers.LonLat>} + * + * Returns: + * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in + * OpenLayers.LonLat, translated into view port pixels by map lib + * If map lib is not loaded or not centered, returns null + */ + getViewPortPxFromLonLat: function (lonlat) { + return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments); + }, + + /** + * Method: initMercatorParameters + * Set up the mercator parameters on the layer: resolutions, + * projection, units. + */ + initMercatorParameters: function() { + // set up properties for Mercator - assume EPSG:900913 + this.RESOLUTIONS = []; + var maxResolution = 156543.03390625; + for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) { + this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom); + } + this.units = "m"; + this.projection = this.projection || "EPSG:900913"; + }, + + /** + * APIMethod: forwardMercator + * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator. + * + * Parameters: + * lon - {float} + * lat - {float} + * + * Returns: + * {<OpenLayers.LonLat>} The coordinates transformed to Mercator. + */ + forwardMercator: (function() { + var gg = new OpenLayers.Projection("EPSG:4326"); + var sm = new OpenLayers.Projection("EPSG:900913"); + return function(lon, lat) { + var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm); + return new OpenLayers.LonLat(point.x, point.y); + }; + })(), + + /** + * APIMethod: inverseMercator + * Given a x,y in Spherical Mercator, return a point in EPSG:4326. + * + * Parameters: + * x - {float} A map x in Spherical Mercator. + * y - {float} A map y in Spherical Mercator. + * + * Returns: + * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326. + */ + inverseMercator: (function() { + var gg = new OpenLayers.Projection("EPSG:4326"); + var sm = new OpenLayers.Projection("EPSG:900913"); + return function(x, y) { + var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg); + return new OpenLayers.LonLat(point.x, point.y); + }; + })() + +}; diff --git a/misc/openlayers/lib/OpenLayers/Layer/TMS.js b/misc/openlayers/lib/OpenLayers/Layer/TMS.js new file mode 100644 index 0000000..ab76847 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/TMS.js @@ -0,0 +1,202 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.TMS + * Create a layer for accessing tiles from services that conform with the + * Tile Map Service Specification + * (http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification). + * + * Example: + * (code) + * var layer = new OpenLayers.Layer.TMS( + * "My Layer", // name for display in LayerSwitcher + * "http://tilecache.osgeo.org/wms-c/Basic.py/", // service endpoint + * {layername: "basic", type: "png"} // required properties + * ); + * (end) + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: serviceVersion + * {String} Service version for tile requests. Default is "1.0.0". + */ + serviceVersion: "1.0.0", + + /** + * APIProperty: layername + * {String} The identifier for the <TileMap> as advertised by the service. + * For example, if the service advertises a <TileMap> with + * 'href="http://tms.osgeo.org/1.0.0/vmap0"', the <layername> property + * would be set to "vmap0". + */ + layername: null, + + /** + * APIProperty: type + * {String} The format extension corresponding to the requested tile image + * type. This is advertised in a <TileFormat> element as the + * "extension" attribute. For example, if the service advertises a + * <TileMap> with <TileFormat width="256" height="256" mime-type="image/jpeg" extension="jpg" />, + * the <type> property would be set to "jpg". + */ + type: null, + + /** + * APIProperty: isBaseLayer + * {Boolean} Make this layer a base layer. Default is true. Set false to + * use the layer as an overlay. + */ + isBaseLayer: true, + + /** + * APIProperty: tileOrigin + * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles. + * If provided, requests for tiles at all resolutions will be aligned + * with this location (no tiles shall overlap this location). If + * not provided, the grid of tiles will be aligned with the bottom-left + * corner of the map's <maxExtent>. Default is ``null``. + * + * Example: + * (code) + * var layer = new OpenLayers.Layer.TMS( + * "My Layer", + * "http://tilecache.osgeo.org/wms-c/Basic.py/", + * { + * layername: "basic", + * type: "png", + * // set if different than the bottom left of map.maxExtent + * tileOrigin: new OpenLayers.LonLat(-180, -90) + * } + * ); + * (end) + */ + tileOrigin: null, + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) <serverResolutions> can include + * resolutions that the server supports and that you don't want to + * provide with this layer; you can also look at <zoomOffset>, which is + * an alternative to <serverResolutions> for that specific purpose. + * (b) The map can work with resolutions that aren't supported by + * the server, i.e. that aren't in <serverResolutions>. When the + * map is displayed in such a resolution data for the closest + * server-supported resolution is loaded and the layer div is + * stretched as necessary. + */ + serverResolutions: null, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more zoom levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Using <zoomOffset> is an alternative to + * setting <serverResolutions> if you only want to expose a subset + * of the server resolutions. + */ + zoomOffset: 0, + + /** + * Constructor: OpenLayers.Layer.TMS + * + * Parameters: + * name - {String} Title to be displayed in a <OpenLayers.Control.LayerSwitcher> + * url - {String} Service endpoint (without the version number). E.g. + * "http://tms.osgeo.org/". + * options - {Object} Additional properties to be set on the layer. The + * <layername> and <type> properties must be set here. + */ + initialize: function(name, url, options) { + var newArguments = []; + newArguments.push(name, url, {}, options); + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + }, + + /** + * APIMethod: clone + * Create a complete copy of this layer. + * + * Parameters: + * obj - {Object} Should only be provided by subclasses that call this + * method. + * + * Returns: + * {<OpenLayers.Layer.TMS>} An exact clone of this <OpenLayers.Layer.TMS> + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.TMS(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: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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) { + bounds = this.adjustBounds(bounds); + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); + var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h)); + var z = this.getServerZoom(); + var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type; + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(path, url); + } + return url + path; + }, + + /** + * Method: setMap + * When the layer is added to a map, then we can fetch our origin + * (if we don't have one.) + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left, + this.map.maxExtent.bottom); + } + }, + + CLASS_NAME: "OpenLayers.Layer.TMS" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Text.js b/misc/openlayers/lib/OpenLayers/Layer/Text.js new file mode 100644 index 0000000..4a4c9e3 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Text.js @@ -0,0 +1,267 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Markers.js + * @requires OpenLayers/Format/Text.js + * @requires OpenLayers/Request/XMLHttpRequest.js + */ + +/** + * Class: OpenLayers.Layer.Text + * This layer creates markers given data in a text file. The <location> + * property of the layer (specified as a property of the options argument + * in the <OpenLayers.Layer.Text> constructor) points to a tab delimited + * file with data used to create markers. + * + * The first row of the data file should be a header line with the column names + * of the data. Each column should be delimited by a tab space. The + * possible columns are: + * - *point* lat,lon of the point where a marker is to be placed + * - *lat* Latitude of the point where a marker is to be placed + * - *lon* Longitude of the point where a marker is to be placed + * - *icon* or *image* URL of marker icon to use. + * - *iconSize* Size of Icon to use. + * - *iconOffset* Where the top-left corner of the icon is to be placed + * relative to the latitude and longitude of the point. + * - *title* The text of the 'title' is placed inside an 'h2' marker + * inside a popup, which opens when the marker is clicked. + * - *description* The text of the 'description' is placed below the h2 + * in the popup. this can be plain text or HTML. + * + * Example text file: + * (code) + * lat lon title description iconSize iconOffset icon + * 10 20 title description 21,25 -10,-25 http://www.openlayers.org/dev/img/marker.png + * (end) + * + * Inherits from: + * - <OpenLayers.Layer.Markers> + */ +OpenLayers.Layer.Text = OpenLayers.Class(OpenLayers.Layer.Markers, { + + /** + * APIProperty: location + * {String} URL of text file. Must be specified in the "options" argument + * of the constructor. Can not be changed once passed in. + */ + location:null, + + /** + * Property: features + * {Array(<OpenLayers.Feature>)} + */ + features: null, + + /** + * APIProperty: formatOptions + * {Object} Hash of options which should be passed to the format when it is + * created. Must be passed in the constructor. + */ + formatOptions: null, + + /** + * Property: selectedFeature + * {<OpenLayers.Feature>} + */ + selectedFeature: null, + + /** + * Constructor: OpenLayers.Layer.Text + * Create a text layer. + * + * Parameters: + * name - {String} + * options - {Object} Object with properties to be set on the layer. + * Must include <location> property. + */ + initialize: function(name, options) { + OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments); + this.features = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + // Warning: Layer.Markers.destroy() must be called prior to calling + // clearFeatures() here, otherwise we leak memory. Indeed, if + // Layer.Markers.destroy() is called after clearFeatures(), it won't be + // able to remove the marker image elements from the layer's div since + // the markers will have been destroyed by clearFeatures(). + OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments); + this.clearFeatures(); + this.features = null; + }, + + /** + * Method: loadText + * Start the load of the Text data. Don't do this when we first add the layer, + * since we may not be visible at any point, and it would therefore be a waste. + */ + loadText: function() { + if (!this.loaded) { + if (this.location != null) { + + var onFail = function(e) { + this.events.triggerEvent("loadend"); + }; + + this.events.triggerEvent("loadstart"); + OpenLayers.Request.GET({ + url: this.location, + success: this.parseData, + failure: onFail, + scope: this + }); + this.loaded = true; + } + } + }, + + /** + * Method: moveTo + * If layer is visible and Text has not been loaded, load Text. + * + * Parameters: + * bounds - {Object} + * zoomChanged - {Object} + * minor - {Object} + */ + moveTo:function(bounds, zoomChanged, minor) { + OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments); + if(this.visibility && !this.loaded){ + this.loadText(); + } + }, + + /** + * Method: parseData + * + * Parameters: + * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} + */ + parseData: function(ajaxRequest) { + var text = ajaxRequest.responseText; + + var options = {}; + + OpenLayers.Util.extend(options, this.formatOptions); + + if (this.map && !this.projection.equals(this.map.getProjectionObject())) { + options.externalProjection = this.projection; + options.internalProjection = this.map.getProjectionObject(); + } + + var parser = new OpenLayers.Format.Text(options); + var features = parser.read(text); + for (var i=0, len=features.length; i<len; i++) { + var data = {}; + var feature = features[i]; + var location; + var iconSize, iconOffset; + + location = new OpenLayers.LonLat(feature.geometry.x, + feature.geometry.y); + + if (feature.style.graphicWidth + && feature.style.graphicHeight) { + iconSize = new OpenLayers.Size( + feature.style.graphicWidth, + feature.style.graphicHeight); + } + + // FIXME: At the moment, we only use this if we have an + // externalGraphic, because icon has no setOffset API Method. + /** + * FIXME FIRST!! + * The Text format does all sorts of parseFloating + * The result of a parseFloat for a bogus string is NaN. That + * means the three possible values here are undefined, NaN, or a + * number. The previous check was an identity check for null. This + * means it was failing for all undefined or NaN. A slightly better + * check is for undefined. An even better check is to see if the + * value is a number (see #1441). + */ + if (feature.style.graphicXOffset !== undefined + && feature.style.graphicYOffset !== undefined) { + iconOffset = new OpenLayers.Pixel( + feature.style.graphicXOffset, + feature.style.graphicYOffset); + } + + if (feature.style.externalGraphic != null) { + data.icon = new OpenLayers.Icon(feature.style.externalGraphic, + iconSize, + iconOffset); + } else { + data.icon = OpenLayers.Marker.defaultIcon(); + + //allows for the case where the image url is not + // specified but the size is. use a default icon + // but change the size + if (iconSize != null) { + data.icon.setSize(iconSize); + } + } + + if ((feature.attributes.title != null) + && (feature.attributes.description != null)) { + data['popupContentHTML'] = + '<h2>'+feature.attributes.title+'</h2>' + + '<p>'+feature.attributes.description+'</p>'; + } + + data['overflow'] = feature.attributes.overflow || "auto"; + + var markerFeature = new OpenLayers.Feature(this, location, data); + this.features.push(markerFeature); + var marker = markerFeature.createMarker(); + if ((feature.attributes.title != null) + && (feature.attributes.description != null)) { + marker.events.register('click', markerFeature, this.markerClick); + } + this.addMarker(marker); + } + this.events.triggerEvent("loadend"); + }, + + /** + * Property: markerClick + * + * Parameters: + * evt - {Event} + * + * Context: + * - {<OpenLayers.Feature>} + */ + markerClick: function(evt) { + var sameMarkerClicked = (this == this.layer.selectedFeature); + this.layer.selectedFeature = (!sameMarkerClicked) ? this : null; + for(var i=0, len=this.layer.map.popups.length; i<len; i++) { + this.layer.map.removePopup(this.layer.map.popups[i]); + } + if (!sameMarkerClicked) { + this.layer.map.addPopup(this.createPopup()); + } + OpenLayers.Event.stop(evt); + }, + + /** + * Method: clearFeatures + */ + clearFeatures: function() { + if (this.features != null) { + while(this.features.length > 0) { + var feature = this.features[0]; + OpenLayers.Util.removeItem(this.features, feature); + feature.destroy(); + } + } + }, + + CLASS_NAME: "OpenLayers.Layer.Text" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/TileCache.js b/misc/openlayers/lib/OpenLayers/Layer/TileCache.js new file mode 100644 index 0000000..5b336be --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/TileCache.js @@ -0,0 +1,140 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.TileCache + * A read only TileCache layer. Used to requests tiles cached by TileCache in + * a web accessible cache. This means that you have to pre-populate your + * cache before this layer can be used. It is meant only to read tiles + * created by TileCache, and not to make calls to TileCache for tile + * creation. Create a new instance with the + * <OpenLayers.Layer.TileCache> constructor. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * {Boolean} Treat this layer as a base layer. Default is true. + */ + isBaseLayer: true, + + /** + * APIProperty: format + * {String} Mime type of the images returned. Default is image/png. + */ + format: 'image/png', + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) <serverResolutions> can include + * resolutions that the server supports and that you don't want to + * provide with this layer. (b) The map can work with resolutions + * that aren't supported by the server, i.e. that aren't in + * <serverResolutions>. When the map is displayed in such a resolution + * data for the closest server-supported resolution is loaded and the + * layer div is stretched as necessary. + */ + serverResolutions: null, + + /** + * Constructor: OpenLayers.Layer.TileCache + * Create a new read only TileCache layer. + * + * Parameters: + * name - {String} Name of the layer displayed in the interface + * url - {String} Location of the web accessible cache (not the location of + * your tilecache script!) + * layername - {String} Layer name as defined in the TileCache + * configuration + * options - {Object} Optional object with properties to be set on the + * layer. Note that you should speficy your resolutions to match + * your TileCache configuration. This can be done by setting + * the resolutions array directly (here or on the map), by setting + * maxResolution and numZoomLevels, or by using scale based properties. + */ + initialize: function(name, url, layername, options) { + this.layername = layername; + OpenLayers.Layer.Grid.prototype.initialize.apply(this, + [name, url, {}, options]); + this.extension = this.format.split('/')[1].toLowerCase(); + this.extension = (this.extension == 'jpg') ? 'jpeg' : this.extension; + }, + + /** + * APIMethod: clone + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.TileCache>} An exact clone of this + * <OpenLayers.Layer.TileCache> + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.TileCache(this.name, + this.url, + this.layername, + 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: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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 res = this.getServerResolution(); + var bbox = this.maxExtent; + var size = this.tileSize; + var tileX = Math.round((bounds.left - bbox.left) / (res * size.w)); + var tileY = Math.round((bounds.bottom - bbox.bottom) / (res * size.h)); + var tileZ = this.serverResolutions != null ? + OpenLayers.Util.indexOf(this.serverResolutions, res) : + this.map.getZoom(); + + var components = [ + this.layername, + OpenLayers.Number.zeroPad(tileZ, 2), + OpenLayers.Number.zeroPad(parseInt(tileX / 1000000), 3), + OpenLayers.Number.zeroPad((parseInt(tileX / 1000) % 1000), 3), + OpenLayers.Number.zeroPad((parseInt(tileX) % 1000), 3), + OpenLayers.Number.zeroPad(parseInt(tileY / 1000000), 3), + OpenLayers.Number.zeroPad((parseInt(tileY / 1000) % 1000), 3), + OpenLayers.Number.zeroPad((parseInt(tileY) % 1000), 3) + '.' + this.extension + ]; + var path = components.join('/'); + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(path, url); + } + url = (url.charAt(url.length - 1) == '/') ? url : url + '/'; + return url + path; + }, + + CLASS_NAME: "OpenLayers.Layer.TileCache" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js b/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js new file mode 100644 index 0000000..878cb4b --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js @@ -0,0 +1,184 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/XYZ.js + * @requires OpenLayers/Tile/UTFGrid.js + */ + +/** + * Class: OpenLayers.Layer.UTFGrid + * This Layer reads from UTFGrid tiled data sources. Since UTFGrids are + * essentially JSON-based ASCII art with attached attributes, they are not + * visibly rendered. In order to use them in the map, you must add a + * <OpenLayers.Control.UTFGrid> control as well. + * + * Example: + * + * (start code) + * var world_utfgrid = new OpenLayers.Layer.UTFGrid({ + * url: "/tiles/world_utfgrid/${z}/${x}/${y}.json", + * utfgridResolution: 4, + * displayInLayerSwitcher: false + * ); + * map.addLayer(world_utfgrid); + * + * var control = new OpenLayers.Control.UTFGrid({ + * layers: [world_utfgrid], + * handlerMode: 'move', + * callback: function(dataLookup) { + * // do something with returned data + * } + * }) + * (end code) + * + * + * Inherits from: + * - <OpenLayers.Layer.XYZ> + */ +OpenLayers.Layer.UTFGrid = OpenLayers.Class(OpenLayers.Layer.XYZ, { + + /** + * APIProperty: isBaseLayer + * Default is false, as UTFGrids are designed to be a transparent overlay layer. + */ + isBaseLayer: false, + + /** + * APIProperty: projection + * {<OpenLayers.Projection>} + * Source projection for the UTFGrids. Default is "EPSG:900913". + */ + projection: new OpenLayers.Projection("EPSG:900913"), + + /** + * Property: useJSONP + * {Boolean} + * Should we use a JSONP script approach instead of a standard AJAX call? + * + * Set to true for using utfgrids from another server. + * Avoids same-domain policy restrictions. + * Note that this only works if the server accepts + * the callback GET parameter and dynamically + * wraps the returned json in a function call. + * + * Default is false + */ + useJSONP: false, + + /** + * APIProperty: url + * {String} + * URL tempate for UTFGrid tiles. Include x, y, and z parameters. + * E.g. "/tiles/${z}/${x}/${y}.json" + */ + + /** + * APIProperty: utfgridResolution + * {Number} + * Ratio of the pixel width to the width of a UTFGrid data point. If an + * entry in the grid represents a 4x4 block of pixels, the + * utfgridResolution would be 4. Default is 2 (specified in + * <OpenLayers.Tile.UTFGrid>). + */ + + /** + * Property: tileClass + * {<OpenLayers.Tile>} The tile class to use for this layer. + * Defaults is <OpenLayers.Tile.UTFGrid>. + */ + tileClass: OpenLayers.Tile.UTFGrid, + + /** + * Constructor: OpenLayers.Layer.UTFGrid + * Create a new UTFGrid layer. + * + * Parameters: + * config - {Object} Configuration properties for the layer. + * + * Required configuration properties: + * url - {String} The url template for UTFGrid tiles. See the <url> property. + */ + initialize: function(options) { + OpenLayers.Layer.Grid.prototype.initialize.apply( + this, [options.name, options.url, {}, options] + ); + this.tileOptions = OpenLayers.Util.extend({ + utfgridResolution: this.utfgridResolution + }, this.tileOptions); + }, + + /** + * Method: createBackBuffer + * The UTFGrid cannot create a back buffer, so this method is overriden. + */ + createBackBuffer: function() {}, + + /** + * APIMethod: clone + * Create a clone of this layer + * + * Parameters: + * obj - {Object} Only used by a subclass of this layer. + * + * Returns: + * {<OpenLayers.Layer.UTFGrid>} An exact clone of this OpenLayers.Layer.UTFGrid + */ + clone: function (obj) { + if (obj == null) { + obj = new OpenLayers.Layer.UTFGrid(this.getOptions()); + } + + // get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + return obj; + }, + + /** + * APIProperty: getFeatureInfo + * Get details about a feature associated with a map location. The object + * returned will have id and data properties. If the given location + * doesn't correspond to a feature, null will be returned. + * + * Parameters: + * location - {<OpenLayers.LonLat>} map location + * + * Returns: + * {Object} Object representing the feature id and UTFGrid data + * corresponding to the given map location. Returns null if the given + * location doesn't hit a feature. + */ + getFeatureInfo: function(location) { + var info = null; + var tileInfo = this.getTileData(location); + if (tileInfo && tileInfo.tile) { + info = tileInfo.tile.getFeatureInfo(tileInfo.i, tileInfo.j); + } + return info; + }, + + /** + * APIMethod: getFeatureId + * Get the identifier for the feature associated with a map location. + * + * Parameters: + * location - {<OpenLayers.LonLat>} map location + * + * Returns: + * {String} The feature identifier corresponding to the given map location. + * Returns null if the location doesn't hit a feature. + */ + getFeatureId: function(location) { + var id = null; + var info = this.getTileData(location); + if (info.tile) { + id = info.tile.getFeatureId(info.i, info.j); + } + return id; + }, + + CLASS_NAME: "OpenLayers.Layer.UTFGrid" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Vector.js b/misc/openlayers/lib/OpenLayers/Layer/Vector.js new file mode 100644 index 0000000..4ef4cbf --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Vector.js @@ -0,0 +1,1007 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer.js + * @requires OpenLayers/Renderer.js + * @requires OpenLayers/StyleMap.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Console.js + * @requires OpenLayers/Lang.js + */ + +/** + * Class: OpenLayers.Layer.Vector + * Instances of OpenLayers.Layer.Vector are used to render vector data from + * a variety of sources. Create a new vector layer with the + * <OpenLayers.Layer.Vector> constructor. + * + * Inherits from: + * - <OpenLayers.Layer> + */ +OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { + + /** + * APIProperty: events + * {<OpenLayers.Events>} + * + * Register a listener for a particular event with the following syntax: + * (code) + * layer.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to layer.events.object. + * element - {DOMElement} A reference to layer.events.element. + * + * Supported map event types (in addition to those from <OpenLayers.Layer.events>): + * beforefeatureadded - Triggered before a feature is added. Listeners + * will receive an object with a *feature* property referencing the + * feature to be added. To stop the feature from being added, a + * listener should return false. + * beforefeaturesadded - Triggered before an array of features is added. + * Listeners will receive an object with a *features* property + * referencing the feature to be added. To stop the features from + * being added, a listener should return false. + * featureadded - Triggered after a feature is added. The event + * object passed to listeners will have a *feature* property with a + * reference to the added feature. + * featuresadded - Triggered after features are added. The event + * object passed to listeners will have a *features* property with a + * reference to an array of added features. + * beforefeatureremoved - Triggered before a feature is removed. Listeners + * will receive an object with a *feature* property referencing the + * feature to be removed. + * beforefeaturesremoved - Triggered before multiple features are removed. + * Listeners will receive an object with a *features* property + * referencing the features to be removed. + * featureremoved - Triggerd after a feature is removed. The event + * object passed to listeners will have a *feature* property with a + * reference to the removed feature. + * featuresremoved - Triggered after features are removed. The event + * object passed to listeners will have a *features* property with a + * reference to an array of removed features. + * beforefeatureselected - Triggered before a feature is selected. Listeners + * will receive an object with a *feature* property referencing the + * feature to be selected. To stop the feature from being selectd, a + * listener should return false. + * featureselected - Triggered after a feature is selected. Listeners + * will receive an object with a *feature* property referencing the + * selected feature. + * featureunselected - Triggered after a feature is unselected. + * Listeners will receive an object with a *feature* property + * referencing the unselected feature. + * beforefeaturemodified - Triggered when a feature is selected to + * be modified. Listeners will receive an object with a *feature* + * property referencing the selected feature. + * featuremodified - Triggered when a feature has been modified. + * Listeners will receive an object with a *feature* property referencing + * the modified feature. + * afterfeaturemodified - Triggered when a feature is finished being modified. + * Listeners will receive an object with a *feature* property referencing + * the modified feature. + * vertexmodified - Triggered when a vertex within any feature geometry + * has been modified. Listeners will receive an object with a + * *feature* property referencing the modified feature, a *vertex* + * property referencing the vertex modified (always a point geometry), + * and a *pixel* property referencing the pixel location of the + * modification. + * vertexremoved - Triggered when a vertex within any feature geometry + * has been deleted. Listeners will receive an object with a + * *feature* property referencing the modified feature, a *vertex* + * property referencing the vertex modified (always a point geometry), + * and a *pixel* property referencing the pixel location of the + * removal. + * sketchstarted - Triggered when a feature sketch bound for this layer + * is started. Listeners will receive an object with a *feature* + * property referencing the new sketch feature and a *vertex* property + * referencing the creation point. + * sketchmodified - Triggered when a feature sketch bound for this layer + * is modified. Listeners will receive an object with a *vertex* + * property referencing the modified vertex and a *feature* property + * referencing the sketch feature. + * sketchcomplete - Triggered when a feature sketch bound for this layer + * is complete. Listeners will receive an object with a *feature* + * property referencing the sketch feature. By returning false, a + * listener can stop the sketch feature from being added to the layer. + * refresh - Triggered when something wants a strategy to ask the protocol + * for a new set of features. + */ + + /** + * APIProperty: isBaseLayer + * {Boolean} The layer is a base layer. Default is false. Set this property + * in the layer options. + */ + isBaseLayer: false, + + /** + * APIProperty: isFixed + * {Boolean} Whether the layer remains in one place while dragging the + * map. Note that setting this to true will move the layer to the bottom + * of the layer stack. + */ + isFixed: false, + + /** + * APIProperty: features + * {Array(<OpenLayers.Feature.Vector>)} + */ + features: null, + + /** + * Property: filter + * {<OpenLayers.Filter>} The filter set in this layer, + * a strategy launching read requests can combined + * this filter with its own filter. + */ + filter: null, + + /** + * Property: selectedFeatures + * {Array(<OpenLayers.Feature.Vector>)} + */ + selectedFeatures: null, + + /** + * Property: unrenderedFeatures + * {Object} hash of features, keyed by feature.id, that the renderer + * failed to draw + */ + unrenderedFeatures: null, + + /** + * APIProperty: reportError + * {Boolean} report friendly error message when loading of renderer + * fails. + */ + reportError: true, + + /** + * APIProperty: style + * {Object} Default style for the layer + */ + style: null, + + /** + * Property: styleMap + * {<OpenLayers.StyleMap>} + */ + styleMap: null, + + /** + * Property: strategies + * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer. + */ + strategies: null, + + /** + * Property: protocol + * {<OpenLayers.Protocol>} Optional protocol for the layer. + */ + protocol: null, + + /** + * Property: renderers + * {Array(String)} List of supported Renderer classes. Add to this list to + * add support for additional renderers. This list is ordered: + * the first renderer which returns true for the 'supported()' + * method will be used, if not defined in the 'renderer' option. + */ + renderers: ['SVG', 'VML', 'Canvas'], + + /** + * Property: renderer + * {<OpenLayers.Renderer>} + */ + renderer: null, + + /** + * APIProperty: rendererOptions + * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for + * supported options. + */ + rendererOptions: null, + + /** + * APIProperty: geometryType + * {String} geometryType allows you to limit the types of geometries this + * layer supports. This should be set to something like + * "OpenLayers.Geometry.Point" to limit types. + */ + geometryType: null, + + /** + * Property: drawn + * {Boolean} Whether the Vector Layer features have been drawn yet. + */ + drawn: false, + + /** + * APIProperty: ratio + * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map. + */ + ratio: 1, + + /** + * Constructor: OpenLayers.Layer.Vector + * Create a new vector layer + * + * Parameters: + * name - {String} A name for the layer + * options - {Object} Optional object with non-default properties to set on + * the layer. + * + * Returns: + * {<OpenLayers.Layer.Vector>} A new vector layer + */ + initialize: function(name, options) { + OpenLayers.Layer.prototype.initialize.apply(this, arguments); + + // allow user-set renderer, otherwise assign one + if (!this.renderer || !this.renderer.supported()) { + this.assignRenderer(); + } + + // if no valid renderer found, display error + if (!this.renderer || !this.renderer.supported()) { + this.renderer = null; + this.displayError(); + } + + if (!this.styleMap) { + this.styleMap = new OpenLayers.StyleMap(); + } + + this.features = []; + this.selectedFeatures = []; + this.unrenderedFeatures = {}; + + // Allow for custom layer behavior + if(this.strategies){ + for(var i=0, len=this.strategies.length; i<len; i++) { + this.strategies[i].setLayer(this); + } + } + + }, + + /** + * APIMethod: destroy + * Destroy this layer + */ + destroy: function() { + if (this.strategies) { + var strategy, i, len; + for(i=0, len=this.strategies.length; i<len; i++) { + strategy = this.strategies[i]; + if(strategy.autoDestroy) { + strategy.destroy(); + } + } + this.strategies = null; + } + if (this.protocol) { + if(this.protocol.autoDestroy) { + this.protocol.destroy(); + } + this.protocol = null; + } + this.destroyFeatures(); + this.features = null; + this.selectedFeatures = null; + this.unrenderedFeatures = null; + if (this.renderer) { + this.renderer.destroy(); + } + this.renderer = null; + this.geometryType = null; + this.drawn = null; + OpenLayers.Layer.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * Create a clone of this layer. + * + * Note: Features of the layer are also cloned. + * + * Returns: + * {<OpenLayers.Layer.Vector>} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.Vector(this.name, this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]); + + // copy/set any non-init, non-simple values here + var features = this.features; + var len = features.length; + var clonedFeatures = new Array(len); + for(var i=0; i<len; ++i) { + clonedFeatures[i] = features[i].clone(); + } + obj.features = clonedFeatures; + + return obj; + }, + + /** + * Method: refresh + * Ask the layer to request features again and redraw them. Triggers + * the refresh event if the layer is in range and visible. + * + * Parameters: + * obj - {Object} Optional object with properties for any listener of + * the refresh event. + */ + refresh: function(obj) { + if(this.calculateInRange() && this.visibility) { + this.events.triggerEvent("refresh", obj); + } + }, + + /** + * Method: assignRenderer + * Iterates through the available renderer implementations and selects + * and assigns the first one whose "supported()" function returns true. + */ + assignRenderer: function() { + for (var i=0, len=this.renderers.length; i<len; i++) { + var rendererClass = this.renderers[i]; + var renderer = (typeof rendererClass == "function") ? + rendererClass : + OpenLayers.Renderer[rendererClass]; + if (renderer && renderer.prototype.supported()) { + this.renderer = new renderer(this.div, this.rendererOptions); + break; + } + } + }, + + /** + * Method: displayError + * Let the user know their browser isn't supported. + */ + displayError: function() { + if (this.reportError) { + OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported", + {renderers: this. renderers.join('\n')})); + } + }, + + /** + * Method: setMap + * The layer has been added to the map. + * + * If there is no renderer set, the layer can't be used. Remove it. + * Otherwise, give the renderer a reference to the map and set its size. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.prototype.setMap.apply(this, arguments); + + if (!this.renderer) { + this.map.removeLayer(this); + } else { + this.renderer.map = this.map; + + var newSize = this.map.getSize(); + newSize.w = newSize.w * this.ratio; + newSize.h = newSize.h * this.ratio; + this.renderer.setSize(newSize); + } + }, + + /** + * Method: afterAdd + * Called at the end of the map.addLayer sequence. At this point, the map + * will have a base layer. Any autoActivate strategies will be + * activated here. + */ + afterAdd: function() { + if(this.strategies) { + var strategy, i, len; + for(i=0, len=this.strategies.length; i<len; i++) { + strategy = this.strategies[i]; + if(strategy.autoActivate) { + strategy.activate(); + } + } + } + }, + + /** + * Method: removeMap + * The layer has been removed from the map. + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + this.drawn = false; + if(this.strategies) { + var strategy, i, len; + for(i=0, len=this.strategies.length; i<len; i++) { + strategy = this.strategies[i]; + if(strategy.autoActivate) { + strategy.deactivate(); + } + } + } + }, + + /** + * Method: onMapResize + * Notify the renderer of the change in size. + * + */ + onMapResize: function() { + OpenLayers.Layer.prototype.onMapResize.apply(this, arguments); + + var newSize = this.map.getSize(); + newSize.w = newSize.w * this.ratio; + newSize.h = newSize.h * this.ratio; + this.renderer.setSize(newSize); + }, + + /** + * Method: moveTo + * Reset the vector layer's div so that it once again is lined up with + * the map. Notify the renderer of the change of extent, and in the + * case of a change of zoom level (resolution), have the + * renderer redraw features. + * + * If the layer has not yet been drawn, cycle through the layer's + * features and draw each one. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} + * dragging - {Boolean} + */ + moveTo: function(bounds, zoomChanged, dragging) { + OpenLayers.Layer.prototype.moveTo.apply(this, arguments); + + var coordSysUnchanged = true; + if (!dragging) { + this.renderer.root.style.visibility = 'hidden'; + + var viewSize = this.map.getSize(), + viewWidth = viewSize.w, + viewHeight = viewSize.h, + offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2, + offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2; + offsetLeft += this.map.layerContainerOriginPx.x; + offsetLeft = -Math.round(offsetLeft); + offsetTop += this.map.layerContainerOriginPx.y; + offsetTop = -Math.round(offsetTop); + + this.div.style.left = offsetLeft + 'px'; + this.div.style.top = offsetTop + 'px'; + + var extent = this.map.getExtent().scale(this.ratio); + coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged); + + this.renderer.root.style.visibility = 'visible'; + + // Force a reflow on gecko based browsers to prevent jump/flicker. + // This seems to happen on only certain configurations; it was originally + // noticed in FF 2.0 and Linux. + if (OpenLayers.IS_GECKO === true) { + this.div.scrollLeft = this.div.scrollLeft; + } + + if (!zoomChanged && coordSysUnchanged) { + for (var i in this.unrenderedFeatures) { + var feature = this.unrenderedFeatures[i]; + this.drawFeature(feature); + } + } + } + if (!this.drawn || zoomChanged || !coordSysUnchanged) { + this.drawn = true; + var feature; + for(var i=0, len=this.features.length; i<len; i++) { + this.renderer.locked = (i !== (len - 1)); + feature = this.features[i]; + this.drawFeature(feature); + } + } + }, + + /** + * APIMethod: display + * Hide or show the Layer + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + OpenLayers.Layer.prototype.display.apply(this, arguments); + // we need to set the display style of the root in case it is attached + // to a foreign layer + var currentDisplay = this.div.style.display; + if(currentDisplay != this.renderer.root.style.display) { + this.renderer.root.style.display = currentDisplay; + } + }, + + /** + * APIMethod: addFeatures + * Add Features to the layer. + * + * Parameters: + * features - {Array(<OpenLayers.Feature.Vector>)} + * options - {Object} + */ + addFeatures: function(features, options) { + if (!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + + var notify = !options || !options.silent; + if(notify) { + var event = {features: features}; + var ret = this.events.triggerEvent("beforefeaturesadded", event); + if(ret === false) { + return; + } + features = event.features; + } + + // Track successfully added features for featuresadded event, since + // beforefeatureadded can veto single features. + var featuresAdded = []; + for (var i=0, len=features.length; i<len; i++) { + if (i != (features.length - 1)) { + this.renderer.locked = true; + } else { + this.renderer.locked = false; + } + var feature = features[i]; + + if (this.geometryType && + !(feature.geometry instanceof this.geometryType)) { + throw new TypeError('addFeatures: component should be an ' + + this.geometryType.prototype.CLASS_NAME); + } + + //give feature reference to its layer + feature.layer = this; + + if (!feature.style && this.style) { + feature.style = OpenLayers.Util.extend({}, this.style); + } + + if (notify) { + if(this.events.triggerEvent("beforefeatureadded", + {feature: feature}) === false) { + continue; + } + this.preFeatureInsert(feature); + } + + featuresAdded.push(feature); + this.features.push(feature); + this.drawFeature(feature); + + if (notify) { + this.events.triggerEvent("featureadded", { + feature: feature + }); + this.onFeatureInsert(feature); + } + } + + if(notify) { + this.events.triggerEvent("featuresadded", {features: featuresAdded}); + } + }, + + + /** + * APIMethod: removeFeatures + * Remove features from the layer. This erases any drawn features and + * removes them from the layer's control. The beforefeatureremoved + * and featureremoved events will be triggered for each feature. The + * featuresremoved event will be triggered after all features have + * been removed. To supress event triggering, use the silent option. + * + * Parameters: + * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be + * removed. + * options - {Object} Optional properties for changing behavior of the + * removal. + * + * Valid options: + * silent - {Boolean} Supress event triggering. Default is false. + */ + removeFeatures: function(features, options) { + if(!features || features.length === 0) { + return; + } + if (features === this.features) { + return this.removeAllFeatures(options); + } + if (!(OpenLayers.Util.isArray(features))) { + features = [features]; + } + if (features === this.selectedFeatures) { + features = features.slice(); + } + + var notify = !options || !options.silent; + + if (notify) { + this.events.triggerEvent( + "beforefeaturesremoved", {features: features} + ); + } + + for (var i = features.length - 1; i >= 0; i--) { + // We remain locked so long as we're not at 0 + // and the 'next' feature has a geometry. We do the geometry check + // because if all the features after the current one are 'null', we + // won't call eraseGeometry, so we break the 'renderer functions + // will always be called with locked=false *last*' rule. The end result + // is a possible gratiutious unlocking to save a loop through the rest + // of the list checking the remaining features every time. So long as + // null geoms are rare, this is probably okay. + if (i != 0 && features[i-1].geometry) { + this.renderer.locked = true; + } else { + this.renderer.locked = false; + } + + var feature = features[i]; + delete this.unrenderedFeatures[feature.id]; + + if (notify) { + this.events.triggerEvent("beforefeatureremoved", { + feature: feature + }); + } + + this.features = OpenLayers.Util.removeItem(this.features, feature); + // feature has no layer at this point + feature.layer = null; + + if (feature.geometry) { + this.renderer.eraseFeatures(feature); + } + + //in the case that this feature is one of the selected features, + // remove it from that array as well. + if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){ + OpenLayers.Util.removeItem(this.selectedFeatures, feature); + } + + if (notify) { + this.events.triggerEvent("featureremoved", { + feature: feature + }); + } + } + + if (notify) { + this.events.triggerEvent("featuresremoved", {features: features}); + } + }, + + /** + * APIMethod: removeAllFeatures + * Remove all features from the layer. + * + * Parameters: + * options - {Object} Optional properties for changing behavior of the + * removal. + * + * Valid options: + * silent - {Boolean} Supress event triggering. Default is false. + */ + removeAllFeatures: function(options) { + var notify = !options || !options.silent; + var features = this.features; + if (notify) { + this.events.triggerEvent( + "beforefeaturesremoved", {features: features} + ); + } + var feature; + for (var i = features.length-1; i >= 0; i--) { + feature = features[i]; + if (notify) { + this.events.triggerEvent("beforefeatureremoved", { + feature: feature + }); + } + feature.layer = null; + if (notify) { + this.events.triggerEvent("featureremoved", { + feature: feature + }); + } + } + this.renderer.clear(); + this.features = []; + this.unrenderedFeatures = {}; + this.selectedFeatures = []; + if (notify) { + this.events.triggerEvent("featuresremoved", {features: features}); + } + }, + + /** + * APIMethod: destroyFeatures + * Erase and destroy features on the layer. + * + * Parameters: + * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of + * features to destroy. If not supplied, all features on the layer + * will be destroyed. + * options - {Object} + */ + destroyFeatures: function(features, options) { + var all = (features == undefined); // evaluates to true if + // features is null + if(all) { + features = this.features; + } + if(features) { + this.removeFeatures(features, options); + for(var i=features.length-1; i>=0; i--) { + features[i].destroy(); + } + } + }, + + /** + * APIMethod: drawFeature + * Draw (or redraw) a feature on the layer. If the optional style argument + * is included, this style will be used. If no style is included, the + * feature's style will be used. If the feature doesn't have a style, + * the layer's style will be used. + * + * This function is not designed to be used when adding features to + * the layer (use addFeatures instead). It is meant to be used when + * the style of a feature has changed, or in some other way needs to + * visually updated *after* it has already been added to a layer. You + * must add the feature to the layer for most layer-related events to + * happen. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} + * style - {String | Object} Named render intent or full symbolizer object. + */ + drawFeature: function(feature, style) { + // don't try to draw the feature with the renderer if the layer is not + // drawn itself + if (!this.drawn) { + return; + } + if (typeof style != "object") { + if(!style && feature.state === OpenLayers.State.DELETE) { + style = "delete"; + } + var renderIntent = style || feature.renderIntent; + style = feature.style || this.style; + if (!style) { + style = this.styleMap.createSymbolizer(feature, renderIntent); + } + } + + var drawn = this.renderer.drawFeature(feature, style); + //TODO remove the check for null when we get rid of Renderer.SVG + if (drawn === false || drawn === null) { + this.unrenderedFeatures[feature.id] = feature; + } else { + delete this.unrenderedFeatures[feature.id]; + } + }, + + /** + * Method: eraseFeatures + * Erase features from the layer. + * + * Parameters: + * features - {Array(<OpenLayers.Feature.Vector>)} + */ + eraseFeatures: function(features) { + this.renderer.eraseFeatures(features); + }, + + /** + * Method: getFeatureFromEvent + * Given an event, return a feature if the event occurred over one. + * Otherwise, return null. + * + * Parameters: + * evt - {Event} + * + * Returns: + * {<OpenLayers.Feature.Vector>} A feature if one was under the event. + */ + getFeatureFromEvent: function(evt) { + if (!this.renderer) { + throw new Error('getFeatureFromEvent called on layer with no ' + + 'renderer. This usually means you destroyed a ' + + 'layer, but not some handler which is associated ' + + 'with it.'); + } + var feature = null; + var featureId = this.renderer.getFeatureIdFromEvent(evt); + if (featureId) { + if (typeof featureId === "string") { + feature = this.getFeatureById(featureId); + } else { + feature = featureId; + } + } + return feature; + }, + + /** + * APIMethod: getFeatureBy + * Given a property value, return the feature if it exists in the features array + * + * Parameters: + * property - {String} + * value - {String} + * + * Returns: + * {<OpenLayers.Feature.Vector>} A feature corresponding to the given + * property value or null if there is no such feature. + */ + getFeatureBy: function(property, value) { + //TBD - would it be more efficient to use a hash for this.features? + var feature = null; + for(var i=0, len=this.features.length; i<len; ++i) { + if(this.features[i][property] == value) { + feature = this.features[i]; + break; + } + } + return feature; + }, + + /** + * APIMethod: getFeatureById + * Given a feature id, return the feature if it exists in the features array + * + * Parameters: + * featureId - {String} + * + * Returns: + * {<OpenLayers.Feature.Vector>} A feature corresponding to the given + * featureId or null if there is no such feature. + */ + getFeatureById: function(featureId) { + return this.getFeatureBy('id', featureId); + }, + + /** + * APIMethod: getFeatureByFid + * Given a feature fid, return the feature if it exists in the features array + * + * Parameters: + * featureFid - {String} + * + * Returns: + * {<OpenLayers.Feature.Vector>} A feature corresponding to the given + * featureFid or null if there is no such feature. + */ + getFeatureByFid: function(featureFid) { + return this.getFeatureBy('fid', featureFid); + }, + + /** + * APIMethod: getFeaturesByAttribute + * Returns an array of features that have the given attribute key set to the + * given value. Comparison of attribute values takes care of datatypes, e.g. + * the string '1234' is not equal to the number 1234. + * + * Parameters: + * attrName - {String} + * attrValue - {Mixed} + * + * Returns: + * Array({<OpenLayers.Feature.Vector>}) An array of features that have the + * passed named attribute set to the given value. + */ + getFeaturesByAttribute: function(attrName, attrValue) { + var i, + feature, + len = this.features.length, + foundFeatures = []; + for(i = 0; i < len; i++) { + feature = this.features[i]; + if(feature && feature.attributes) { + if (feature.attributes[attrName] === attrValue) { + foundFeatures.push(feature); + } + } + } + return foundFeatures; + }, + + /** + * Unselect the selected features + * i.e. clears the featureSelection array + * change the style back + clearSelection: function() { + + var vectorLayer = this.map.vectorLayer; + for (var i = 0; i < this.map.featureSelection.length; i++) { + var featureSelection = this.map.featureSelection[i]; + vectorLayer.drawFeature(featureSelection, vectorLayer.style); + } + this.map.featureSelection = []; + }, + */ + + + /** + * APIMethod: onFeatureInsert + * method called after a feature is inserted. + * Does nothing by default. Override this if you + * need to do something on feature updates. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} + */ + onFeatureInsert: function(feature) { + }, + + /** + * APIMethod: preFeatureInsert + * method called before a feature is inserted. + * Does nothing by default. Override this if you + * need to do something when features are first added to the + * layer, but before they are drawn, such as adjust the style. + * + * Parameters: + * feature - {<OpenLayers.Feature.Vector>} + */ + preFeatureInsert: function(feature) { + }, + + /** + * APIMethod: getDataExtent + * Calculates the max extent which includes all of the features. + * + * Returns: + * {<OpenLayers.Bounds>} or null if the layer has no features with + * geometries. + */ + getDataExtent: function () { + var maxExtent = null; + var features = this.features; + if(features && (features.length > 0)) { + var geometry = null; + for(var i=0, len=features.length; i<len; i++) { + geometry = features[i].geometry; + if (geometry) { + if (maxExtent === null) { + maxExtent = new OpenLayers.Bounds(); + } + maxExtent.extend(geometry.getBounds()); + } + } + } + return maxExtent; + }, + + CLASS_NAME: "OpenLayers.Layer.Vector" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js b/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js new file mode 100644 index 0000000..075edaa --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js @@ -0,0 +1,154 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Layer.Vector.RootContainer + * A special layer type to combine multiple vector layers inside a single + * renderer root container. This class is not supposed to be instantiated + * from user space, it is a helper class for controls that require event + * processing for multiple vector layers. + * + * Inherits from: + * - <OpenLayers.Layer.Vector> + */ +OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * Property: displayInLayerSwitcher + * Set to false for this layer type + */ + displayInLayerSwitcher: false, + + /** + * APIProperty: layers + * Layers that are attached to this container. Required config option. + */ + layers: null, + + /** + * Constructor: OpenLayers.Layer.Vector.RootContainer + * Create a new root container for multiple vector layer. This constructor + * is not supposed to be used from user space, it is only to be used by + * controls that need feature selection across multiple vector layers. + * + * Parameters: + * name - {String} A name for the layer + * options - {Object} Optional object with non-default properties to set on + * the layer. + * + * Required options properties: + * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this + * container + * + * Returns: + * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root + * container + */ + + /** + * Method: display + */ + display: function() {}, + + /** + * Method: getFeatureFromEvent + * walk through the layers to find the feature returned by the event + * + * Parameters: + * evt - {Object} event object with a feature property + * + * Returns: + * {<OpenLayers.Feature.Vector>} + */ + getFeatureFromEvent: function(evt) { + var layers = this.layers; + var feature; + for(var i=0; i<layers.length; i++) { + feature = layers[i].getFeatureFromEvent(evt); + if(feature) { + return feature; + } + } + }, + + /** + * Method: setMap + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); + this.collectRoots(); + map.events.register("changelayer", this, this.handleChangeLayer); + }, + + /** + * Method: removeMap + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + removeMap: function(map) { + map.events.unregister("changelayer", this, this.handleChangeLayer); + this.resetRoots(); + OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments); + }, + + /** + * Method: collectRoots + * Collects the root nodes of all layers this control is configured with + * and moveswien the nodes to this control's layer + */ + collectRoots: function() { + var layer; + // walk through all map layers, because we want to keep the order + for(var i=0; i<this.map.layers.length; ++i) { + layer = this.map.layers[i]; + if(OpenLayers.Util.indexOf(this.layers, layer) != -1) { + layer.renderer.moveRoot(this.renderer); + } + } + }, + + /** + * Method: resetRoots + * Resets the root nodes back into the layers they belong to. + */ + resetRoots: function() { + var layer; + for(var i=0; i<this.layers.length; ++i) { + layer = this.layers[i]; + if(this.renderer && layer.renderer.getRenderLayerId() == this.id) { + this.renderer.moveRoot(layer.renderer); + } + } + }, + + /** + * Method: handleChangeLayer + * Event handler for the map's changelayer event. We need to rebuild + * this container's layer dom if order of one of its layers changes. + * This handler is added with the setMap method, and removed with the + * removeMap method. + * + * Parameters: + * evt - {Object} + */ + handleChangeLayer: function(evt) { + var layer = evt.layer; + if(evt.property == "order" && + OpenLayers.Util.indexOf(this.layers, layer) != -1) { + this.resetRoots(); + this.collectRoots(); + } + }, + + CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/WMS.js b/misc/openlayers/lib/OpenLayers/Layer/WMS.js new file mode 100644 index 0000000..15dee2f --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/WMS.js @@ -0,0 +1,267 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.WMS + * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web + * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS> + * constructor. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Constant: DEFAULT_PARAMS + * {Object} Hashtable of default parameter key/value pairs + */ + DEFAULT_PARAMS: { service: "WMS", + version: "1.1.1", + request: "GetMap", + styles: "", + format: "image/jpeg" + }, + + /** + * APIProperty: isBaseLayer + * {Boolean} Default is true for WMS layer + */ + isBaseLayer: true, + + /** + * APIProperty: encodeBBOX + * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no', + * but some services want it that way. Default false. + */ + encodeBBOX: false, + + /** + * APIProperty: noMagic + * {Boolean} If true, the image format will not be automagicaly switched + * from image/jpeg to image/png or image/gif when using + * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the + * constructor. Default false. + */ + noMagic: false, + + /** + * Property: yx + * {Object} Keys in this object are EPSG codes for which the axis order + * is to be reversed (yx instead of xy, LatLon instead of LonLat), with + * true as value. This is only relevant for WMS versions >= 1.3.0, and + * only if yx is not set in <OpenLayers.Projection.defaults> for the + * used projection. + */ + yx: {}, + + /** + * Constructor: OpenLayers.Layer.WMS + * Create a new WMS layer object + * + * Examples: + * + * The code below creates a simple WMS layer using the image/jpeg format. + * (code) + * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic", + * "http://wms.jpl.nasa.gov/wms.cgi", + * {layers: "modis,global_mosaic"}); + * (end) + * Note the 3rd argument (params). Properties added to this object will be + * added to the WMS GetMap requests used for this layer's tiles. The only + * mandatory parameter is "layers". Other common WMS params include + * "transparent", "styles" and "format". Note that the "srs" param will + * always be ignored. Instead, it will be derived from the baseLayer's or + * map's projection. + * + * The code below creates a transparent WMS layer with additional options. + * (code) + * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic", + * "http://wms.jpl.nasa.gov/wms.cgi", + * { + * layers: "modis,global_mosaic", + * transparent: true + * }, { + * opacity: 0.5, + * singleTile: true + * }); + * (end) + * Note that by default, a WMS layer is configured as baseLayer. Setting + * the "transparent" param to true will apply some magic (see <noMagic>). + * The default image format changes from image/jpeg to image/png, and the + * layer is not configured as baseLayer. + * + * Parameters: + * name - {String} A name for the layer + * url - {String} Base url for the WMS + * (e.g. http://wms.jpl.nasa.gov/wms.cgi) + * params - {Object} An object with key/value pairs representing the + * GetMap query string parameters and parameter values. + * options - {Object} Hashtable of extra options to tag onto the layer. + * These options include all properties listed above, plus the ones + * inherited from superclasses. + */ + initialize: function(name, url, params, options) { + var newArguments = []; + //uppercase params + params = OpenLayers.Util.upperCaseObject(params); + if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) { + params.EXCEPTIONS = "INIMAGE"; + } + newArguments.push(name, url, params, options); + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + OpenLayers.Util.applyDefaults( + this.params, + OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS) + ); + + + //layer is transparent + if (!this.noMagic && this.params.TRANSPARENT && + this.params.TRANSPARENT.toString().toLowerCase() == "true") { + + // unless explicitly set in options, make layer an overlay + if ( (options == null) || (!options.isBaseLayer) ) { + this.isBaseLayer = false; + } + + // jpegs can never be transparent, so intelligently switch the + // format, depending on the browser's capabilities + if (this.params.FORMAT == "image/jpeg") { + this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif" + : "image/png"; + } + } + + }, + + /** + * Method: clone + * Create a clone of this layer + * + * Returns: + * {<OpenLayers.Layer.WMS>} An exact clone of this layer + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.WMS(this.name, + this.url, + this.params, + 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; + }, + + /** + * APIMethod: reverseAxisOrder + * Returns true if the axis order is reversed for the WMS version and + * projection of the layer. + * + * Returns: + * {Boolean} true if the axis order is reversed, false otherwise. + */ + reverseAxisOrder: function() { + var projCode = this.projection.getCode(); + return parseFloat(this.params.VERSION) >= 1.3 && + !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] && + OpenLayers.Projection.defaults[projCode].yx)); + }, + + /** + * Method: getURL + * Return a GetMap query string for this layer + * + * Parameters: + * bounds - {<OpenLayers.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) { + bounds = this.adjustBounds(bounds); + + var imageSize = this.getImageSize(); + var newParams = {}; + // WMS 1.3 introduced axis order + var reverseAxisOrder = this.reverseAxisOrder(); + newParams.BBOX = this.encodeBBOX ? + bounds.toBBOX(null, reverseAxisOrder) : + bounds.toArray(reverseAxisOrder); + newParams.WIDTH = imageSize.w; + newParams.HEIGHT = imageSize.h; + var requestString = this.getFullRequestString(newParams); + return requestString; + }, + + /** + * APIMethod: mergeNewParams + * Catch changeParams and uppercase the new params to be merged in + * before calling changeParams on the super class. + * + * Once params have been changed, the tiles will be reloaded with + * the new parameters. + * + * Parameters: + * newParams - {Object} Hashtable of new params to use + */ + mergeNewParams:function(newParams) { + var upperParams = OpenLayers.Util.upperCaseObject(newParams); + var newArguments = [upperParams]; + return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this, + newArguments); + }, + + /** + * APIMethod: getFullRequestString + * Combine the layer's url with its params and these newParams. + * + * Add the SRS parameter from projection -- this is probably + * more eloquently done via a setProjection() method, but this + * works for now and always. + * + * Parameters: + * newParams - {Object} + * altUrl - {String} Use this as the url instead of the layer's url + * + * Returns: + * {String} + */ + getFullRequestString:function(newParams, altUrl) { + var mapProjection = this.map.getProjectionObject(); + var projectionCode = this.projection && this.projection.equals(mapProjection) ? + this.projection.getCode() : + mapProjection.getCode(); + var value = (projectionCode == "none") ? null : projectionCode; + if (parseFloat(this.params.VERSION) >= 1.3) { + this.params.CRS = value; + } else { + this.params.SRS = value; + } + + if (typeof this.params.TRANSPARENT == "boolean") { + newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE"; + } + + return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply( + this, arguments); + }, + + CLASS_NAME: "OpenLayers.Layer.WMS" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/WMTS.js b/misc/openlayers/lib/OpenLayers/Layer/WMTS.js new file mode 100644 index 0000000..9c41629 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/WMTS.js @@ -0,0 +1,510 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.WMTS + * Instances of the WMTS class allow viewing of tiles from a service that + * implements the OGC WMTS specification version 1.0.0. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * {Boolean} The layer will be considered a base layer. Default is true. + */ + isBaseLayer: true, + + /** + * Property: version + * {String} WMTS version. Default is "1.0.0". + */ + version: "1.0.0", + + /** + * APIProperty: requestEncoding + * {String} Request encoding. Can be "REST" or "KVP". Default is "KVP". + */ + requestEncoding: "KVP", + + /** + * APIProperty: url + * {String|Array(String)} The base URL or request URL template for the WMTS + * service. Must be provided. Array is only supported for base URLs, not + * for request URL templates. URL templates are only supported for + * REST <requestEncoding>. + */ + url: null, + + /** + * APIProperty: layer + * {String} The layer identifier advertised by the WMTS service. Must be + * provided. + */ + layer: null, + + /** + * APIProperty: matrixSet + * {String} One of the advertised matrix set identifiers. Must be provided. + */ + matrixSet: null, + + /** + * APIProperty: style + * {String} One of the advertised layer styles. Must be provided. + */ + style: null, + + /** + * APIProperty: format + * {String} The image MIME type. Default is "image/jpeg". + */ + format: "image/jpeg", + + /** + * APIProperty: tileOrigin + * {<OpenLayers.LonLat>} The top-left corner of the tile matrix in map + * units. If the tile origin for each matrix in a set is different, + * the <matrixIds> should include a topLeftCorner property. If + * not provided, the tile origin will default to the top left corner + * of the layer <maxExtent>. + */ + tileOrigin: null, + + /** + * APIProperty: tileFullExtent + * {<OpenLayers.Bounds>} The full extent of the tile set. If not supplied, + * the layer's <maxExtent> property will be used. + */ + tileFullExtent: null, + + /** + * APIProperty: formatSuffix + * {String} For REST request encoding, an image format suffix must be + * included in the request. If not provided, the suffix will be derived + * from the <format> property. + */ + formatSuffix: null, + + /** + * APIProperty: matrixIds + * {Array} A list of tile matrix identifiers. If not provided, the matrix + * identifiers will be assumed to be integers corresponding to the + * map zoom level. If a list of strings is provided, each item should + * be the matrix identifier that corresponds to the map zoom level. + * Additionally, a list of objects can be provided. Each object should + * describe the matrix as presented in the WMTS capabilities. These + * objects should have the propertes shown below. + * + * Matrix properties: + * identifier - {String} The matrix identifier (required). + * scaleDenominator - {Number} The matrix scale denominator. + * topLeftCorner - {<OpenLayers.LonLat>} The top left corner of the + * matrix. Must be provided if different than the layer <tileOrigin>. + * tileWidth - {Number} The tile width for the matrix. Must be provided + * if different than the width given in the layer <tileSize>. + * tileHeight - {Number} The tile height for the matrix. Must be provided + * if different than the height given in the layer <tileSize>. + */ + matrixIds: null, + + /** + * APIProperty: dimensions + * {Array} For RESTful request encoding, extra dimensions may be specified. + * Items in this list should be property names in the <params> object. + * Values of extra dimensions will be determined from the corresponding + * values in the <params> object. + */ + dimensions: null, + + /** + * APIProperty: params + * {Object} Extra parameters to include in tile requests. For KVP + * <requestEncoding>, these properties will be encoded in the request + * query string. For REST <requestEncoding>, these properties will + * become part of the request path, with order determined by the + * <dimensions> list. + */ + params: null, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Additionally, if this layer is to be used + * as an overlay and the cache has fewer zoom levels than the base + * layer, you can supply a negative zoomOffset. For example, if a + * map zoom level of 1 corresponds to your cache level zero, you would + * supply a -1 zoomOffset (and set the maxResolution of the layer + * appropriately). The zoomOffset value has no effect if complete + * matrix definitions (including scaleDenominator) are supplied in + * the <matrixIds> property. Defaults to 0 (no zoom offset). + */ + zoomOffset: 0, + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) <serverResolutions> can include + * resolutions that the server supports and that you don't want to + * provide with this layer; you can also look at <zoomOffset>, which is + * an alternative to <serverResolutions> for that specific purpose. + * (b) The map can work with resolutions that aren't supported by + * the server, i.e. that aren't in <serverResolutions>. When the + * map is displayed in such a resolution data for the closest + * server-supported resolution is loaded and the layer div is + * stretched as necessary. + */ + serverResolutions: null, + + /** + * Property: formatSuffixMap + * {Object} a map between WMTS 'format' request parameter and tile image file suffix + */ + formatSuffixMap: { + "image/png": "png", + "image/png8": "png", + "image/png24": "png", + "image/png32": "png", + "png": "png", + "image/jpeg": "jpg", + "image/jpg": "jpg", + "jpeg": "jpg", + "jpg": "jpg" + }, + + /** + * Property: matrix + * {Object} Matrix definition for the current map resolution. Updated by + * the <updateMatrixProperties> method. + */ + matrix: null, + + /** + * Constructor: OpenLayers.Layer.WMTS + * Create a new WMTS layer. + * + * Example: + * (code) + * var wmts = new OpenLayers.Layer.WMTS({ + * name: "My WMTS Layer", + * url: "http://example.com/wmts", + * layer: "layer_id", + * style: "default", + * matrixSet: "matrix_id" + * }); + * (end) + * + * Parameters: + * config - {Object} Configuration properties for the layer. + * + * Required configuration properties: + * url - {String} The base url for the service. See the <url> property. + * layer - {String} The layer identifier. See the <layer> property. + * style - {String} The layer style identifier. See the <style> property. + * matrixSet - {String} The tile matrix set identifier. See the <matrixSet> + * property. + * + * Any other documented layer properties can be provided in the config object. + */ + initialize: function(config) { + + // confirm required properties are supplied + var required = { + url: true, + layer: true, + style: true, + matrixSet: true + }; + for (var prop in required) { + if (!(prop in config)) { + throw new Error("Missing property '" + prop + "' in layer configuration."); + } + } + + config.params = OpenLayers.Util.upperCaseObject(config.params); + var args = [config.name, config.url, config.params, config]; + OpenLayers.Layer.Grid.prototype.initialize.apply(this, args); + + + // determine format suffix (for REST) + if (!this.formatSuffix) { + this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop(); + } + + // expand matrixIds (may be array of string or array of object) + if (this.matrixIds) { + var len = this.matrixIds.length; + if (len && typeof this.matrixIds[0] === "string") { + var ids = this.matrixIds; + this.matrixIds = new Array(len); + for (var i=0; i<len; ++i) { + this.matrixIds[i] = {identifier: ids[i]}; + } + } + } + + }, + + /** + * Method: setMap + */ + setMap: function() { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + }, + + /** + * Method: updateMatrixProperties + * Called when map resolution changes to update matrix related properties. + */ + updateMatrixProperties: function() { + this.matrix = this.getMatrix(); + if (this.matrix) { + if (this.matrix.topLeftCorner) { + this.tileOrigin = this.matrix.topLeftCorner; + } + if (this.matrix.tileWidth && this.matrix.tileHeight) { + this.tileSize = new OpenLayers.Size( + this.matrix.tileWidth, this.matrix.tileHeight + ); + } + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat( + this.maxExtent.left, this.maxExtent.top + ); + } + if (!this.tileFullExtent) { + this.tileFullExtent = this.maxExtent; + } + } + }, + + /** + * Method: moveTo + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to + * do some init work in that case. + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + if (zoomChanged || !this.matrix) { + this.updateMatrixProperties(); + } + return OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments); + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS> + */ + clone: function(obj) { + if (obj == null) { + obj = new OpenLayers.Layer.WMTS(this.options); + } + //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: getIdentifier + * Get the current index in the matrixIds array. + */ + getIdentifier: function() { + return this.getServerZoom(); + }, + + /** + * Method: getMatrix + * Get the appropriate matrix definition for the current map resolution. + */ + getMatrix: function() { + var matrix; + if (!this.matrixIds || this.matrixIds.length === 0) { + matrix = {identifier: this.getIdentifier()}; + } else { + // get appropriate matrix given the map scale if possible + if ("scaleDenominator" in this.matrixIds[0]) { + // scale denominator calculation based on WMTS spec + var denom = + OpenLayers.METERS_PER_INCH * + OpenLayers.INCHES_PER_UNIT[this.units] * + this.getServerResolution() / 0.28E-3; + var diff = Number.POSITIVE_INFINITY; + var delta; + for (var i=0, ii=this.matrixIds.length; i<ii; ++i) { + delta = Math.abs(1 - (this.matrixIds[i].scaleDenominator / denom)); + if (delta < diff) { + diff = delta; + matrix = this.matrixIds[i]; + } + } + } else { + // fall back on zoom as index + matrix = this.matrixIds[this.getIdentifier()]; + } + } + return matrix; + }, + + /** + * Method: getTileInfo + * Get tile information for a given location at the current map resolution. + * + * Parameters: + * loc - {<OpenLayers.LonLat} A location in map coordinates. + * + * Returns: + * {Object} An object with "col", "row", "i", and "j" properties. The col + * and row values are zero based tile indexes from the top left. The + * i and j values are the number of pixels to the left and top + * (respectively) of the given location within the target tile. + */ + getTileInfo: function(loc) { + var res = this.getServerResolution(); + + var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w); + var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h); + + var col = Math.floor(fx); + var row = Math.floor(fy); + + return { + col: col, + row: row, + i: Math.floor((fx - col) * this.tileSize.w), + j: Math.floor((fy - row) * this.tileSize.h) + }; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * Returns: + * {String} A URL for the tile corresponding to the given bounds. + */ + getURL: function(bounds) { + bounds = this.adjustBounds(bounds); + var url = ""; + if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) { + + var center = bounds.getCenterLonLat(); + var info = this.getTileInfo(center); + var matrixId = this.matrix.identifier; + var dimensions = this.dimensions, params; + + if (OpenLayers.Util.isArray(this.url)) { + url = this.selectUrl([ + this.version, this.style, this.matrixSet, + this.matrix.identifier, info.row, info.col + ].join(","), this.url); + } else { + url = this.url; + } + + if (this.requestEncoding.toUpperCase() === "REST") { + params = this.params; + if (url.indexOf("{") !== -1) { + var template = url.replace(/\{/g, "${"); + var context = { + // spec does not make clear if capital S or not + style: this.style, Style: this.style, + TileMatrixSet: this.matrixSet, + TileMatrix: this.matrix.identifier, + TileRow: info.row, + TileCol: info.col + }; + if (dimensions) { + var dimension, i; + for (i=dimensions.length-1; i>=0; --i) { + dimension = dimensions[i]; + context[dimension] = params[dimension.toUpperCase()]; + } + } + url = OpenLayers.String.format(template, context); + } else { + // include 'version', 'layer' and 'style' in tile resource url + var path = this.version + "/" + this.layer + "/" + this.style + "/"; + + // append optional dimension path elements + if (dimensions) { + for (var i=0; i<dimensions.length; i++) { + if (params[dimensions[i]]) { + path = path + params[dimensions[i]] + "/"; + } + } + } + + // append other required path elements + path = path + this.matrixSet + "/" + this.matrix.identifier + + "/" + info.row + "/" + info.col + "." + this.formatSuffix; + + if (!url.match(/\/$/)) { + url = url + "/"; + } + url = url + path; + } + } else if (this.requestEncoding.toUpperCase() === "KVP") { + + // assemble all required parameters + params = { + SERVICE: "WMTS", + REQUEST: "GetTile", + VERSION: this.version, + LAYER: this.layer, + STYLE: this.style, + TILEMATRIXSET: this.matrixSet, + TILEMATRIX: this.matrix.identifier, + TILEROW: info.row, + TILECOL: info.col, + FORMAT: this.format + }; + url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]); + + } + } + return url; + }, + + /** + * APIMethod: mergeNewParams + * Extend the existing layer <params> with new properties. Tiles will be + * reloaded with updated params in the request. + * + * Parameters: + * newParams - {Object} Properties to extend to existing <params>. + */ + mergeNewParams: function(newParams) { + if (this.requestEncoding.toUpperCase() === "KVP") { + return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply( + this, [OpenLayers.Util.upperCaseObject(newParams)] + ); + } + }, + + CLASS_NAME: "OpenLayers.Layer.WMTS" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js b/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js new file mode 100644 index 0000000..8581289 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js @@ -0,0 +1,105 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.WorldWind + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.WorldWind = OpenLayers.Class(OpenLayers.Layer.Grid, { + + DEFAULT_PARAMS: { + }, + + /** + * APIProperty: isBaseLayer + * {Boolean} WorldWind layer is a base layer by default. + */ + isBaseLayer: true, + + /** + * APIProperty: lzd + * {Float} LevelZeroTileSizeDegrees + */ + lzd: null, + + /** + * APIProperty: zoomLevels + * {Integer} Number of zoom levels. + */ + zoomLevels: null, + + /** + * Constructor: OpenLayers.Layer.WorldWind + * + * Parameters: + * name - {String} Name of Layer + * url - {String} Base URL + * lzd - {Float} Level zero tile size degrees + * zoomLevels - {Integer} number of zoom levels + * params - {Object} additional parameters + * options - {Object} additional options + */ + initialize: function(name, url, lzd, zoomLevels, params, options) { + this.lzd = lzd; + this.zoomLevels = zoomLevels; + var newArguments = []; + newArguments.push(name, url, params, options); + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + this.params = OpenLayers.Util.applyDefaults( + this.params, this.DEFAULT_PARAMS + ); + }, + + /** + * Method: getZoom + * Convert map zoom to WW zoom. + */ + getZoom: function () { + var zoom = this.map.getZoom(); + var extent = this.map.getMaxExtent(); + zoom = zoom - Math.log(this.maxResolution / (this.lzd/512))/Math.log(2); + return zoom; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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) { + bounds = this.adjustBounds(bounds); + var zoom = this.getZoom(); + var extent = this.map.getMaxExtent(); + var deg = this.lzd/Math.pow(2,this.getZoom()); + var x = Math.floor((bounds.left - extent.left)/deg); + var y = Math.floor((bounds.bottom - extent.bottom)/deg); + if (this.map.getResolution() <= (this.lzd/512) + && this.getZoom() <= this.zoomLevels) { + return this.getFullRequestString( + { L: zoom, + X: x, + Y: y + }); + } else { + return OpenLayers.Util.getImageLocation("blank.gif"); + } + + }, + + CLASS_NAME: "OpenLayers.Layer.WorldWind" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/XYZ.js b/misc/openlayers/lib/OpenLayers/Layer/XYZ.js new file mode 100644 index 0000000..5af5ce3 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/XYZ.js @@ -0,0 +1,172 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.XYZ + * The XYZ class is designed to make it easier for people who have tiles + * arranged by a standard XYZ grid. + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * Default is true, as this is designed to be a base tile source. + */ + isBaseLayer: true, + + /** + * APIProperty: sphericalMercator + * Whether the tile extents should be set to the defaults for + * spherical mercator. Useful for things like OpenStreetMap. + * Default is false, except for the OSM subclass. + */ + sphericalMercator: false, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more zoom levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Using <zoomOffset> is an alternative to + * setting <serverResolutions> if you only want to expose a subset + * of the server resolutions. + */ + zoomOffset: 0, + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) <serverResolutions> can include + * resolutions that the server supports and that you don't want to + * provide with this layer; you can also look at <zoomOffset>, which is + * an alternative to <serverResolutions> for that specific purpose. + * (b) The map can work with resolutions that aren't supported by + * the server, i.e. that aren't in <serverResolutions>. When the + * map is displayed in such a resolution data for the closest + * server-supported resolution is loaded and the layer div is + * stretched as necessary. + */ + serverResolutions: null, + + /** + * Constructor: OpenLayers.Layer.XYZ + * + * Parameters: + * name - {String} + * url - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, options) { + if (options && options.sphericalMercator || this.sphericalMercator) { + options = OpenLayers.Util.extend({ + projection: "EPSG:900913", + numZoomLevels: 19 + }, options); + } + OpenLayers.Layer.Grid.prototype.initialize.apply(this, [ + name || this.name, url || this.url, {}, options + ]); + }, + + /** + * APIMethod: clone + * Create a clone of this layer + * + * Parameters: + * obj - {Object} Is this ever used? + * + * Returns: + * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.XYZ(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + return obj; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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 xyz = this.getXYZ(bounds); + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + var s = '' + xyz.x + xyz.y + xyz.z; + url = this.selectUrl(s, url); + } + + return OpenLayers.String.format(url, xyz); + }, + + /** + * Method: getXYZ + * Calculates x, y and z for the given bounds. + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * Returns: + * {Object} - an object with x, y and z properties. + */ + getXYZ: function(bounds) { + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.maxExtent.left) / + (res * this.tileSize.w)); + var y = Math.round((this.maxExtent.top - bounds.top) / + (res * this.tileSize.h)); + var z = this.getServerZoom(); + + if (this.wrapDateLine) { + var limit = Math.pow(2, z); + x = ((x % limit) + limit) % limit; + } + + return {'x': x, 'y': y, 'z': z}; + }, + + /* APIMethod: setMap + * When the layer is added to a map, then we can fetch our origin + * (if we don't have one.) + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left, + this.maxExtent.bottom); + } + }, + + CLASS_NAME: "OpenLayers.Layer.XYZ" +}); diff --git a/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js b/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js new file mode 100644 index 0000000..1c3d57d --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js @@ -0,0 +1,260 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + +/* + * Development supported by a R&D grant DC08P02OUK006 - Old Maps Online + * (www.oldmapsonline.org) from Ministry of Culture of the Czech Republic. + */ + + +/** + * @requires OpenLayers/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.Zoomify + * + * Inherits from: + * - <OpenLayers.Layer.Grid> + */ +OpenLayers.Layer.Zoomify = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * Property: size + * {<OpenLayers.Size>} The Zoomify image size in pixels. + */ + size: null, + + /** + * APIProperty: isBaseLayer + * {Boolean} + */ + isBaseLayer: true, + + /** + * Property: standardTileSize + * {Integer} The size of a standard (non-border) square tile in pixels. + */ + standardTileSize: 256, + + /** + * Property: tileOriginCorner + * {String} This layer uses top-left as tile origin + **/ + tileOriginCorner: "tl", + + /** + * Property: numberOfTiers + * {Integer} Depth of the Zoomify pyramid, number of tiers (zoom levels) + * - filled during Zoomify pyramid initialization. + */ + numberOfTiers: 0, + + /** + * Property: tileCountUpToTier + * {Array(Integer)} Number of tiles up to the given tier of pyramid. + * - filled during Zoomify pyramid initialization. + */ + tileCountUpToTier: null, + + /** + * Property: tierSizeInTiles + * {Array(<OpenLayers.Size>)} Size (in tiles) for each tier of pyramid. + * - filled during Zoomify pyramid initialization. + */ + tierSizeInTiles: null, + + /** + * Property: tierImageSize + * {Array(<OpenLayers.Size>)} Image size in pixels for each pyramid tier. + * - filled during Zoomify pyramid initialization. + */ + tierImageSize: null, + + /** + * Constructor: OpenLayers.Layer.Zoomify + * + * Parameters: + * name - {String} A name for the layer. + * url - {String} - Relative or absolute path to the image or more + * precisly to the TileGroup[X] directories root. + * Flash plugin use the variable name "zoomifyImagePath" for this. + * size - {<OpenLayers.Size>} The size (in pixels) of the image. + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, size, options) { + + // initilize the Zoomify pyramid for given size + this.initializeZoomify(size); + + OpenLayers.Layer.Grid.prototype.initialize.apply(this, [ + name, url, size, {}, options + ]); + }, + + /** + * Method: initializeZoomify + * It generates constants for all tiers of the Zoomify pyramid + * + * Parameters: + * size - {<OpenLayers.Size>} The size of the image in pixels + * + */ + initializeZoomify: function( size ) { + + var imageSize = size.clone(); + this.size = size.clone(); + var tiles = new OpenLayers.Size( + Math.ceil( imageSize.w / this.standardTileSize ), + Math.ceil( imageSize.h / this.standardTileSize ) + ); + + this.tierSizeInTiles = [tiles]; + this.tierImageSize = [imageSize]; + + while (imageSize.w > this.standardTileSize || + imageSize.h > this.standardTileSize ) { + + imageSize = new OpenLayers.Size( + Math.floor( imageSize.w / 2 ), + Math.floor( imageSize.h / 2 ) + ); + tiles = new OpenLayers.Size( + Math.ceil( imageSize.w / this.standardTileSize ), + Math.ceil( imageSize.h / this.standardTileSize ) + ); + this.tierSizeInTiles.push( tiles ); + this.tierImageSize.push( imageSize ); + } + + this.tierSizeInTiles.reverse(); + this.tierImageSize.reverse(); + + this.numberOfTiers = this.tierSizeInTiles.length; + var resolutions = [1]; + this.tileCountUpToTier = [0]; + for (var i = 1; i < this.numberOfTiers; i++) { + resolutions.unshift(Math.pow(2, i)); + this.tileCountUpToTier.push( + this.tierSizeInTiles[i-1].w * this.tierSizeInTiles[i-1].h + + this.tileCountUpToTier[i-1] + ); + } + if (!this.serverResolutions) { + this.serverResolutions = resolutions; + } + }, + + /** + * APIMethod:destroy + */ + destroy: function() { + // for now, nothing special to do here. + OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments); + + // Remove from memory the Zoomify pyramid - is that enough? + this.tileCountUpToTier.length = 0; + this.tierSizeInTiles.length = 0; + this.tierImageSize.length = 0; + + }, + + /** + * APIMethod: clone + * + * Parameters: + * obj - {Object} + * + * Returns: + * {<OpenLayers.Layer.Zoomify>} An exact clone of this <OpenLayers.Layer.Zoomify> + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.Zoomify(this.name, + this.url, + this.size, + this.options); + } + + //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: getURL + * + * Parameters: + * bounds - {<OpenLayers.Bounds>} + * + * 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) { + bounds = this.adjustBounds(bounds); + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); + var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h)); + var z = this.getZoomForResolution( res ); + + var tileIndex = x + y * this.tierSizeInTiles[z].w + this.tileCountUpToTier[z]; + var path = "TileGroup" + Math.floor( (tileIndex) / 256 ) + + "/" + z + "-" + x + "-" + y + ".jpg"; + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + url = this.selectUrl(path, url); + } + return url + path; + }, + + /** + * Method: getImageSize + * getImageSize returns size for a particular tile. If bounds are given as + * first argument, size is calculated (bottom-right tiles are non square). + * + */ + getImageSize: function() { + if (arguments.length > 0) { + var bounds = this.adjustBounds(arguments[0]); + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w)); + var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h)); + var z = this.getZoomForResolution( res ); + var w = this.standardTileSize; + var h = this.standardTileSize; + if (x == this.tierSizeInTiles[z].w -1 ) { + var w = this.tierImageSize[z].w % this.standardTileSize; + } + if (y == this.tierSizeInTiles[z].h -1 ) { + var h = this.tierImageSize[z].h % this.standardTileSize; + } + return (new OpenLayers.Size(w, h)); + } else { + return this.tileSize; + } + }, + + /** + * APIMethod: setMap + * When the layer is added to a map, then we can fetch our origin + * (if we don't have one.) + * + * Parameters: + * map - {<OpenLayers.Map>} + */ + setMap: function(map) { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left, + this.map.maxExtent.top); + }, + + CLASS_NAME: "OpenLayers.Layer.Zoomify" +}); |