summaryrefslogtreecommitdiff
path: root/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js
diff options
context:
space:
mode:
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js')
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js480
1 files changed, 480 insertions, 0 deletions
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'
+});