UNPKG

@arcgis/core

Version:

ArcGIS Maps SDK for JavaScript: A complete 2D and 3D mapping and data visualization API

344 lines (342 loc) • 17.5 kB
import type SpatialReference from "../geometry/SpatialReference.js"; import type Layer from "./Layer.js"; import type TileInfo from "./support/TileInfo.js"; import type { EsriPromiseMixin } from "../core/Promise.js"; import type { AbortOptions } from "../core/promiseUtils.js"; import type { BlendLayer, BlendLayerProperties } from "./mixins/BlendLayer.js"; import type { RefreshableLayer, RefreshableLayerProperties } from "./mixins/RefreshableLayer.js"; import type { ScaleRangeLayer, ScaleRangeLayerProperties } from "./mixins/ScaleRangeLayer.js"; import type { TileInfoProperties } from "./support/TileInfo.js"; import type { SpatialReferenceProperties } from "../geometry/SpatialReference.js"; import type { LayerProperties } from "./Layer.js"; export interface BaseTileLayerProperties extends LayerProperties, RefreshableLayerProperties, ScaleRangeLayerProperties, BlendLayerProperties { /** * The spatial reference of the layer. * * @default {@link geometry/SpatialReference#WebMercator} */ spatialReference?: SpatialReferenceProperties; /** The tiling scheme information for the layer. */ tileInfo?: TileInfoProperties; } /** * This class may be extended to create a custom TileLayer. It is a generic tile layer class designed to work with various tile sources, * not just ArcGIS cached [map services](https://enterprise.arcgis.com/en/server/latest/publish-services/windows/what-is-a-map-service.htm), * which are specifically handled by the [TileLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/TileLayer/) class. Tile layers are composed of images, such as satellite imagery, * that are composed of square tiles mosaicked together in columns and rows, giving the layer the appearance that it is one continuous image. * They have several levels of detail (LOD) that permit users to zoom in to any region of the map and load additional tiles that depict features * in higher resolution at larger map scales. * * Tile layers often provide geographic context for other layers such as * [FeatureLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/FeatureLayer/) and tend to perform better than other layers, * such as [MapImageLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/MapImageLayer/) and [ImageryLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/ImageryLayer/), * that request and display a single image per view. * * You can create a custom tile layer by calling [Accessor.createSubclass()](https://developers.arcgis.com/javascript/latest/references/core/core/Accessor/#createSubclass) * on the `BaseTileLayer` class. You may create a custom tile layer for one of the following reasons: * * * The source of the images isn't explicitly supported by the ArcGIS Maps SDK for JavaScript * * Images need to be preprocessed prior to display in the view * * ### Setting up a custom tile layer * Unlike TileLayer, the BaseTileLayer does not make assumptions about the type of data it retrieves, making it more flexible and capable of working * with a wider variety of tile sources. By default, the BaseTileLayer is configured to use the Web Mercator spatial reference. * If a custom tile source does not use the Web Mercator spatial reference, the BaseTileLayer must be explicitly configured with three key properties * in its constructor to ensure proper alignment with the source. Those properties are [tileInfo](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#tileInfo), [spatialReference](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#spatialReference), * and [fullExtent](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#fullExtent). * * ```js * // set the spatial reference to New Zealand Transverse Mercator * const spatialReference = new SpatialReference({ * wkid: 2193 * }); * * // create the origin point for the tileInfo * // The upper left corner of the tiling scheme, * const origin = new Point({ * x: -4020900, * y: 19998100, * spatialReference * }); * * // Create LODs based on the source tiles * const tileInfo = new TileInfo({ * spatialReference, * origin: origin, * format: "mixed", * lods: [{ * "level": 0, * "resolution": 156543.03392799935, * "scale": 591657527.591552 * }, * { * "level": 1, * "resolution": 78271.51696399967, * "scale": 295828763.795776 * }, * // other LODs * ] * }); * * const MyCustomTileLayer = BaseTileLayer.createSubclass({ * constructor() { * this.tileInfo = tileInfo; * this.spatialReference = spatialReference; * this.fullExtent = new Extent ({ * xmin: -1497310.4689000002, * ymin: 3678220.3271, * xmax: 4749968.6755, * ymax: 7192314.8459, * spatialReference * }) * }, * // properties of the custom tile layer * properties: { * urlTemplate: null, * } * }); * ``` * * ### Request images as they are defined * * To request images as they are predefined from a data source, overwrite the [getTileUrl()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#getTileUrl) * method so it returns the URL for the requested tile for a given level, row and column. * * ```js * // override getTileUrl() * // generate the tile url for a given level, row and column * getTileUrl: function (level, row, col) { * return this.urlTemplate.replace("{z}", level).replace("{x}", col).replace("{y}", row); * } * ``` * * ### Preprocess images prior to display * * If data needs to be preprocessed prior to display, then override the [fetchTile()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#fetchTile) * method. For example, if you need to apply a * [compositing operation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation) * to the image returned from the server before the image is displayed then you would override this method. * * ```js * // override fetchTile() method to process the data returned * // from the server. * fetchTile: function (level, row, col, options) { * // call getTileUrl method to construct the Url for the image * // for given level, row and column * let url = this.getTileUrl(level, row, col); * * // request for the tile based on the url returned from getTileUrl() method. * // the signal option ensures that obsolete requests are aborted. * return esriRequest(url, { * responseType: "image", * signal: options && options.signal * }) * .then(function (response) { * // when esriRequest resolves successfully, * // process the image that is returned * let image = response.data; * let width = this.tileInfo.size[0]; * let height = this.tileInfo.size[0]; * * // create a canvas with a filled rectangle * let canvas = document.createElement("canvas"); * let context = canvas.getContext("2d"); * canvas.width = width; * canvas.height = height; * * // Apply the color provided by the layer to the fill rectangle * if (this.tint) { * context.fillStyle = this.tint.toCss(); * context.fillRect(0, 0, width, height); * // apply multiply blend mode to canvas' fill color and the tile * // returned from the server to darken the tile * context.globalCompositeOperation = "multiply"; * } * context.drawImage(image, 0, 0, width, height); * return canvas; * }.bind(this)); * } * ``` * * See the following samples for examples of how this works: * * * [Sample - Custom TileLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-tilelayer/) * * [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/) * * [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/) * * If the custom tile layer requires loadable resources, then you must load all loadable * dependencies on the layer, within the [load()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#load) method. Add the promise returned * from the loadable resource with the [addResolvingPromise()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#addResolvingPromise) method. * The layer will then wait for all of dependencies to * finish loading before it is considered [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded). * * ```js * // Override load method * load: function () { * // multiply property is an array of ArcGIS cached map services * this.multiply.forEach(function (layer) { * // loop through each tile layers and call * // load method on each layer * let promise = layer.load(); * * // add the promise of each load() method to addResolvingPromise() * // the custom tile layer will be loaded when every promise is resolved * this.addResolvingPromise(promise); * }, this); * } * ``` * The layer is responsible for generating the tile URL and fetching tiles from the server for the * level, row, and column provided by the [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). The * LayerView displays the fetched tiles. * * @since 4.4 * @see [Creating Custom Layers and LayerViews (slides) - 2017 Esri Dev Summit](https://proceedings.esri.com/library/userconf/devsummit17/papers/dev_int_97.pdf) * @see [Creating Custom Layers and LayerViews (video)](https://youtu.be/QOoZ1lgWESA) * @see [Sample - Custom TileLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-tilelayer/) * @see [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/) * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/) */ export default abstract class BaseTileLayer extends BaseTileLayerSuperclass { /** * The spatial reference of the layer. * * @default {@link geometry/SpatialReference#WebMercator} */ get spatialReference(): SpatialReference; set spatialReference(value: SpatialReferenceProperties); /** The tiling scheme information for the layer. */ get tileInfo(): TileInfo; set tileInfo(value: TileInfoProperties); /** * For [BaseTileLayer](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/) the type is `base-tile`. * * @default "base-tile" */ get type(): "base-tile" | "bing-maps"; /** * Adds a Promise to the layer's loadable chain. * This is typically used in the [load()](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#load) method to ensure that all * loadable resources required for the layer * to function are loaded prior to this layer resolving and becoming [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded). * * @param promiseToLoad - A promise that must resolve for the layer * to resolve and move from the `loading` [status](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loadStatus) to being * [loaded](https://developers.arcgis.com/javascript/latest/references/core/layers/BaseTileLayer/#loaded). * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/) * @example * // The requiredLayer must load() prior to the MyCustomTileLayer * // resolving and moving to the "loaded" status. * let MyCustomTileLayer = BaseTileLayer.createSubclass({ * load: function() { * let promise = this.requiredLayer.load(); * this.addResolvingPromise(promise); * } * }); */ addResolvingPromise<U, V extends EsriPromiseMixin>(promiseToLoad: PromiseLike<U> | V | PromiseLike<V> | null | undefined): void; /** * This method fetches a tile for the given level, row and column present in the view. Override this method if the data or image * returned from the server needs to be processed before it can be displayed. * * @param level - Level of detail of the tile to fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @param row - The row (y) position of the tile fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @param col - The column (x) position of the tile to fetch. This value is provided by [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @param options - Optional settings for the tile request. The options have the following properties. * @returns Returns a promise that resolves to an * [HTMLImageElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement) or * [HTMLCanvasElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement). * @see [Sample - Custom BlendLayer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-blendlayer/) * @see [Sample - Custom LERC Layer](https://developers.arcgis.com/javascript/latest/sample-code/layers-custom-lerc-2d/) * @example * // Process the image returned from the server before * // it is displayed. * fetchTile: function (level, row, col, options) { * // call getTileUrl method to construct the URL for * // the image for the given level, row and col * let url = this.getTileUrl(level, row, col); * * // request for the tile based on the generated url. * // the signal option ensures that obsolete requests are aborted. * return esriRequest(url, { * responseType: "image", * signal: options && options.signal * }) * .then(function (response) { * // get the image from the response * let image = response.data; * let width = this.tileInfo.size[0]; * let height = this.tileInfo.size[0]; * * let canvas = document.createElement("canvas"); * canvas.width = width; * canvas.height = height; * let context = canvas.getContext("2d"); * * // tint is a custom property of this layer * // Apply the tint color provided by the application * // to the canvas * if (this.tint) { * context.fillStyle = this.tint.toCss(); * context.fillRect(0, 0, width, height); * * // The pixels of the top layer are multiplied by the corresponding * // pixel of the bottom layer. A darker picture is the result. * context.globalCompositeOperation = "multiply"; * } * context.drawImage(image, 0, 0, width, height); * return canvas; * }.bind(this)); * } */ fetchTile(level: number, row: number, col: number, options?: AbortOptions): Promise<HTMLImageElement | HTMLCanvasElement>; /** * Returns the bounds of the tile as an array of four numbers that be readily * converted to an [Extent](https://developers.arcgis.com/javascript/latest/references/core/geometry/Extent/) object. * The value for each item in the array is described in the following table: * * Index | Value * ------|------ * 0 | Minimum x-value * 1 | Minimum y-value * 2 | Maximum x-value * 3 | Maximum y-value * * @param level - The level of detail (LOD) of the tile. * @param row - The tile's row (y) position in the dataset. * @param column - The tiles column (x) position in the dataset. * @param out - Array for storing the tile bounds or extent. * @returns Returns an array representing the tile bounds or extent. */ getTileBounds(level: number, row: number, column: number, out?: [ number, number, number, number ]): [ number, number, number, number ]; /** * This method returns a URL to an image for a given level, row and column. * Override this method to construct the URL for the image based on user interaction. * * @param level - Level of detail. This value is provided by the * [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @param row - Tile row. This value is provided by the * [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @param col - Tile column. This value is provided by the * [LayerView](https://developers.arcgis.com/javascript/latest/references/core/views/layers/LayerView/). * @returns Returns the URL to the tile image. * @example * // generate the tile url for a given level, row and column * getTileUrl: function (level, row, col) { * // urlTemplate is a property of the custom layer. * // value is provided by the application * return this.urlTemplate.replace("{z}", level).replace("{x}", col).replace("{y}", row); * }, */ getTileUrl(level: number, row: number, col: number): string | null | undefined; } declare const BaseTileLayerSuperclass: typeof Layer & typeof RefreshableLayer & typeof ScaleRangeLayer & typeof BlendLayer