maptalks TileLayer tiles merge/clip/transform etc tool
- 
This plugin requires the runtime environment to support OffscreenCanvas. Pay attention to relevant compatibility. Especially the Safari browser
 - 
Considering performance, all operations are completed within the Web Worker
 - 
If you are familiar with other map engines, you can also apply them to other map engines leaflet demo
 
- mapzen terrain tile encode
 - arcgis terrain tile encode
 - qgis gray terrain tile encode
 - terrain encode with colors
 - color terrain tile
 
- flipY
 - css filter
 - tile opacity
 - tile mosaic
 - tile old photo
 - tile invert color
 - gaussian Blur
 - tiles globalCompositeOperation
 - get tile with mosaic
 
- water mark
 - underground by clip tile
 - terrain-tiles-blendmode
 - terrain-tiles-blendmode shadow
 - leaflet clip demo
 - leaflet gettile demo
 - leaflet reproject tile demo
 - leaflet-imagetile-clip demo
 - maplibre reproject EPSG4326 to EPSG3857 demo
 
npm i maptalks
#or
# npm i maptalks-gl
npm i maptalks.tileclip<script type="text/javascript" src="https://unpkg.com/maptalks-gl/dist/maptalks-gl.js"></script>
<script src="https://unpkg.com/maptalks.tileclip/dist/maptalks.tileclip.js"></script>return TileActor instance
import {
    getTileActor,
    getBlankTile,
    get404Tile,
    getBlankVTTile
} from 'maptalks.tileclip'
const tileActor = getTileActor();Tile clip worker interaction class. about maptalks. Actor details
import {
    getTileActor
} from 'maptalks.tileclip'
const tileActor = getTileActor();| method | describe | 
|---|---|
| getTile(options) | Request tile support for batch and some processing | 
| getTileWithMaxZoom(options) | Cutting tiles, automatically cutting beyond the maximum level limit | 
| layoutTiles(options) | Tile layout arrangement | 
| reProjectTile(options) | Tile reprojection | 
| rectifyTile(options) | Corrective tiles, only applicable to Chinese users | 
| injectMask(maskId, Polygon/MultiPolygon) | Inject geojson data for tile clipping service | 
| removeMask(maskId) | remove Inject geojson data | 
| maskHasInjected(maskId) | Has the geojson data been injected | 
| clipTile(options) | Crop tiles using injected geojson data | 
| tileIntersectMask(options) | Does tile intersect with mask | 
| encodeTerrainTile(options) | Encode other terrain tiles into mapbox terrain service format | 
| colorTerrainTile(options) | Terrain tile color matching | 
| terrainTileFixBoundary(options) | Reset the skirt edge of the terrain tile using neighbor tiles | 
| imageSlicing(options) | Cut a large image into multiple small images | 
| injectImage(options) | inject image source for getImageTile | 
| removeImage(imageId) | remove image source | 
| imageHasInjected(imageId) | Has the image data been injected | 
| getImageTile(options) | get tile data from injectImage | 
| getVTTile(options) | get VT tile, support merge vt data | 
export type postProcessingOptionsType = {
    flipY?:boolean;
    filter?: string; //[CanvasRenderingContext2D.filter](https://mdn.org.cn/en-US/docs/Web/API/CanvasRenderingContext2D/filter)
    opacity?: number;//tile opacity if need
    gaussianBlurRadius?: number;// gaussian Blur Radius if need
    mosaicSize?: number;//Mosaic pixel size 
    oldPhoto?: boolean;//Old photo effect
    invertColor?:boolean;// invert Color
}
export type fetchOptionsType = {
    referrer?: string;//fetch referrer
    headers?: Record<string, string>;// fetch headers params. if need
    fetchOptions?: Record<string, any>;//fetch options. if need, If it exists, headers will be ignored
    timeout?: number;//fetch timeout if need
    indexedDBCache?: boolean;//cache tile data by IndexedDB 
}
//tile default return ImageBitMap
export type returnResultType = {
    returnUint32Buffer?:boolean;//return to Unit32 ArrayBuffer 
    returnBlobURL?: boolean;// to return Blob URL by createObjectURL() When the blob URL is no longer in use, be sure to destroy its value revokeObjectURL() 
    returnBase64?: boolean;// return base64 
    quality?:number;//image quality 0-1,MIME types is image/webp https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Guides/MIME_types/Common_types
}all methods return Promise with cancel() method
getTile(options)get tile ImageBitmap by fetch in worker, returnPromiseoptions.url:tile url or tiles urls...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
tileActor.getTile({
    url: 'https://services.arcgisonline.com/ArcGIS/rest/services/Word_Imagery/MapServer/tile/12/1663/3425',
    //or url:[ur1,ur2],
    fetchOptions: {
        referrer: document.location.href,
        headers: {
            ...
        }
        ...
    },
    oldPhoto: true,
}).then(imagebitmap => {
    consle.log(imagebitmap);
}).catch(error => {
    //do some things
})
//or if you want to cancel task
const promise = tileActor.getTile({
    ...
});
//mock cancel fetch task
setTimeout(() => {
    promise.cancel();
}, 2000);
promise.then((imagebitmap) => {
}).catch(error => {
    //do some things
    console.error(error);
})getTileWithMaxZoom(options)get tile ImageBitmap by fetch in worker, returnPromise. When the level exceeds the maximum level, tiles will be automatically cutoptions.x:tile coloptions.y:tile rowoptions.z:tile zoomoptions.maxAvailableZoom:tile The maximum visible level, such as 18options.urlTemplate:tile urlTemplate.https://services.arcgisonline.com/ArcGIS/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x} or tiles urlTemplatesoptions?.subdomains:subdomains, such as [1, 2, 3, 4, 5]...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
const {
    x,
    y,
    z
} = tile;
const urlTemplate = baseLayer.options.urlTemplate;
const maxAvailableZoom = 18;
tileActor.getTileWithMaxZoom({
    x,
    y,
    z,
    urlTemplate,
    //or urlTemplate:[urlTemplate1,urlTemplate2],
    maxAvailableZoom,
    fetchOptions: {
        referrer: document.location.href,
        headers: {
            ...
        }
        ...
    }
}).then(imagebitmap => {
    consle.log(imagebitmap);
}).catch(error => {
    //do some things
})
//or if you want to cancel task
const promise = tileActor.getTileWithMaxZoom({
    ...
});
//mock cancel fetch task
setTimeout(() => {
    promise.cancel();
}, 2000);
promise.then((imagebitmap) => {
}).catch(error => {
    //do some things
    console.error(error);
})layoutTile(options)layout tiles ImageBitmap by fetch in worker, returnPromise.options.urlTemplate:tile urlTemplate.https://services.arcgisonline.com/ArcGIS/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x}options.tiles: tile Data setoptions?.subdomains:subdomains, such as [1, 2, 3, 4, 5]...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
const {
    x,
    y,
    z
} = tile;
const urlTemplate = baseLayer.options.urlTemplate;
tileActor.layoutTiles({
    urlTemplate,
    tiles: [
        [x, y, z],
        [x + 1, y, z],
        [x, y + 1, z],
        [x + 1, y + 1, z]
    ]
    fetchOptions: {
        referrer: document.location.href,
        headers: {
            ...
        }
        ...
    }
}).then(imagebitmap => {
    consle.log(imagebitmap);
}).catch(error => {
    //do some things
})
//or if you want to cancel task
const promise = tileActor.layoutTiles({
    ...
});
//mock cancel fetch task
setTimeout(() => {
    promise.cancel();
}, 2000);
promise.then((imagebitmap) => {
}).catch(error => {
    //do some things
    console.error(error);
})reProjectTile(options)Reprojection tile in worker, returnPromiseoptions.x:tile coloptions.y:tile rowoptions.z:tile zoomoptions.projection: Projection code, only supportEPSG:4326,EPSG:3857. Note that only global standard pyramid slicing is supportedoptions.maxAvailableZoom:tile The maximum visible level, such as 18options.urlTemplate:tile urlTemplate.https://services.arcgisonline.com/ArcGIS/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x} or tiles urlTemplatesoptions?.subdomains:subdomains, such as [1, 2, 3, 4, 5]options?.isGCJ02: Is it the isGCJ02 coordinate systemoptions?.errorLog: Is there a printing error...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
const {
    x,
    y,
    z
} = tile;
const maxAvailableZoom = 18;
tileActor.reProjectTile({
    x,
    y,
    z,
    urlTemplate,
    projection: 'EPSG:4326',
    maxAvailableZoom: 18,
}).then(imagebitmap => {
    callback(imagebitmap);
}).catch(error => {
    //do some things
    console.error(error);
})
//or if you want to cancel task
const promise = tileActor.reProjectTile({
    ...
});
//mock cancel fetch task
setTimeout(() => {
    promise.cancel();
}, 2000);
promise.then((imagebitmap) => {
}).catch(error => {
    //do some things
    console.error(error);
})rectifyTile(options)rectify tile ImageBitmap by fetch in worker, returnPromise. When the level exceeds the maximum level, tiles will be automatically cutoptions.x:tile coloptions.y:tile rowoptions.z:tile zoomoptions.maxAvailableZoom:tile The maximum visible level, such as 18options.urlTemplate:tile urlTemplate.https://services.arcgisonline.com/ArcGIS/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x} or tiles urlTemplatesoptions.projection: 'EPSG:4326' | 'EPSG:3857';options.tileBBOX:tile BBOX[minx,miny,maxx,maxy]options.transform: 'WGS84-GCJ02' | 'GCJ02-WGS84',options.tileSize: tile sizeoptions?.subdomains:subdomains, such as [1, 2, 3, 4, 5]...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
  baseLayer.on('renderercreate', function(e) {
      //load tile image
      //   img(Image): an Image object
      //   url(String): the url of the tile
      e.renderer.loadTileBitmap = function(url, tile, callback) {
          // console.log(tile);
          const {
              x,
              y,
              z
          } = tile;
          const urlTemplate = baseLayer.options.urlTemplate;
          const maxAvailableZoom = 18;
          tileActor.rectifyTile({
              x,
              y,
              z,
              urlTemplate,
              maxAvailableZoom,
              tileBBOX: baseLayer._getTileBBox(tile),
              projection: baseLayer.getProjection().code,
              tileSize: baseLayer.getTileSize().width,
              transform: 'GCJ02-WGS84',
              mapZoom: map.getZoom()
          }).then(imagebitmap => {
              callback(imagebitmap);
          }).catch(error => {
              //do some things
              console.error(error);
          })
      };
  });- 
injectMask(maskId,Polygon/MultiPolygon)inject Mask(GeoJSON. Polygon) for clip tiles . returnPromisemaskId: mask id, Cache mask data in the workerPolygon/MultiPolygonGeoJSON Polygon/MultiPolygon GeoJSON SPEC
 
const maskId = 'china';
const polygon = {
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": []
    }
}
tileActor.injectMask(maskId, polygon).then(data => {
    // baseLayer.addTo(map);
}).catch(error => {
    console.error(error);
})- 
removeMask(maskId)remove Mask from cache . returnPromisemaskId: mask id
 
const maskId = 'china';
tileActor.removeMask(maskId).then(data => {
}).catch(error => {
    console.error(error);
})- 
maskHasInjected(maskId)Has the mask been injected . returnBooleanmaskId: mask id
 
const maskId = 'china';
const result = tileActor.maskHasInjected(maskId);clipTile(options)clip tile by mask . returnPromiseoptions.tile:tile ImageBitmap dataoptions.tileBBOX:tile BBOX[minx,miny,maxx,maxy]options.projection: Projection code, such as : EPSG:3857options.maskId:mask keyoptions?.tileSize:tile sizeoptions?.reverse:whether or not clip reverseoptions?.bufferSize: Buffer contour pixel size...returnResultTypereturnResultType params
import * as maptalks from 'maptalks-gl';
import {
    getTileActor
} from 'maptalks.tileclip';
const tileActor = getTileActor();
const maskId = 'china';
const baseLayer = new maptalks.TileLayer('base', {
    debug: true,
    urlTemplate: '/arcgisonline/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x}',
    subdomains: ["a", "b", "c", "d"],
    // bufferPixel: 1
})
baseLayer.on('renderercreate', function(e) {
    //load tile image
    //   img(Image): an Image object
    //   url(String): the url of the tile
    e.renderer.loadTileBitmap = function(url, tile, callback) {
        //get Tile data
        tileActor.getTile({
            url: maptalks.Util.getAbsoluteURL(url)
        }).then(imagebitmap => {
            //clip tile
            tileActor.clipTile({
                tile: imagebitmap,
                tileBBOX: baseLayer._getTileBBox(tile),
                projection: baseLayer.getProjection().code,
                tileSize: baseLayer.getTileSize().width,
                maskId,
            }).then(image => {
                callback(image);
            }).catch(error => {
                //do some things
                console.error(error);
            })
        }).catch(error => {
            //do some things
            console.error(error);
        })
    };
});
const polygon = {
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": []
    }
}
tileActor.injectMask(maskId, polygon).then(data => {
    baseLayer.addTo(map);
}).catch(error => {
    console.error(error);
})- 
tileIntersectMask(options)Does tile intersect with mask . returnPromiseoptions.tileBBOX:tile BBOX[minx,miny,maxx,maxy]options.maskId:mask key
 
import * as maptalks from 'maptalks-gl';
import {
    getTileActor
} from 'maptalks.tileclip';
const tileActor = getTileActor();
const maskId = 'china';
const baseLayer = new maptalks.TileLayer('base', {
    debug: true,
    urlTemplate: '/arcgisonline/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x}',
    subdomains: ["a", "b", "c", "d"],
    // bufferPixel: 1
})
baseLayer.on('renderercreate', function(e) {
    //load tile image
    //   img(Image): an Image object
    //   url(String): the url of the tile
    e.renderer.loadTileBitmap = function(url, tile, callback) {
        const tileBBOX = baseLayer._getTileBBox(tile);
        const blankTile = () => {
            callback(maptalks.getBlankTile())
        }
        tileActor.tileIntersectMask({
            tileBBOX,
            maskId
        }).then(result => {
            // callback(result);
            const {
                intersect
            } = result;
            if (intersect) {
                tileActor.getTile({
                    url: maptalks.Util.getAbsoluteURL(url)
                }).then(imagebitmap => {
                    callback(imagebitmap);
                }).catch(error => {
                    //do some things
                    console.error(error);
                    blankTile();
                })
            } else {
                blankTile();
            }
        }).catch(error => {
            //do some things
            console.error(error);
            blankTile();
        })
    };
});
const polygon = {
    "type": "Feature",
    "geometry": {
        "type": "Polygon",
        "coordinates": []
    }
}
tileActor.injectMask(maskId, polygon).then(data => {
    baseLayer.addTo(map);
}).catch(error => {
    console.error(error);
})encodeTerrainTile(options)transform other terrain tile to mapbox terrain rgb tile by fetch in worker, returnPromiseoptions.url:tile urloptions.terrainType:'mapzen' | 'tianditu' | 'cesium'|'arcgs'|'qgis-gray'options?.terrainWidthdefault is 65options?.minHeightmin height when terrainType is 'qgis-gray'options?.maxHeightmax height when terrainType is 'qgis-gray'options?.tileSizedefault value is 256options?.terrainColorsColored terrain tiles. Color interpolation based on altitude...fetchOptionsTypefetchOptionsType params...returnResultTypereturnResultType params
  baseLayer.on('renderercreate', function(e) {
      //load tile image
      //   img(Image): an Image object
      //   url(String): the url of the tile
      e.renderer.loadTileBitmap = function(url, tile, callback) {
          //transform mapzen terrain tile to mapbox terrain rgb tile
          tileActor.encodeTerrainTile({
              url: maptalks.Util.getAbsoluteURL(url),
              terrainType: 'mapzen',
              // timeout: 5000
          }).then(imagebitmap => {
              callback(imagebitmap)
          }).catch(error => {
              //do some things
              console.error(error);
          })
      };
  });colorTerrainTile(options)Terrain tile color matching, returnPromiseoptions.tile:tile data, is ImageBitMapoptions.colors: Color Mapping Table...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
   const colors = [
       [0, "#4B2991"],
       [176, "#872CA2"],
       [353, "#C0369D"],
       [530, "#EA4F88"],
       [706, "#FA7876"],
       [883, "#F6A97A"],
       [1060, "#EDD9A3"],
       [1236, "#EDD9A3"],
       [1413, "#ffffff"],
       [1590, "#ffffff"]
   ]
   baseLayer.on('renderercreate', function(e) {
       //load tile image
       //   img(Image): an Image object
       //   url(String): the url of the tile
       e.renderer.loadTileBitmap = function(url, tile, callback) {
           tileActor.getTile({
               url: maptalks.Util.getAbsoluteURL(url)
           }).then(imagebitmap => {
               tileActor.colorTerrainTile({
                   tile: imagebitmap,
                   colors
               }).then(image => {
                   callback(image);
               }).catch(error => {
                   console.error(error);
               })
           }).catch(error => {
               //do some things
               // console.error(error);
               callback(maptalks.get404Tile())
           })
       };
   });terrainTileFixBoundary(options)Reset the skirt edge of the terrain tile using neighbor tiles , returnPromiseoptions.tiles:tiles collection...returnResultTypereturnResultType params
const tile = {
    x,
    y,
    z,
    image: ...
};
const tileRight = {
    x: x + 1,
    y,
    z: image: ...
}
const tileBottom = {
    x,
    y: y + 1,
    z,
    image: ...
}
tileActor.terrainTileFixBoundary({
    tiles: [tile, tileRight, tileBottom],
    returnUint32Buffer: true
}).then(imagebitmap => {
}).catch(error => {
    //do some things
    // console.error(error);
    callback(maptalks.get404Tile())
})imageSlicing(options)slice big image in worker, returnPromiseoptions.url:image url or images urls...fetchOptionsTypefetchOptionsType params...postProcessingOptionsTypepostProcessingOptions params...returnResultTypereturnResultType params
tileActor.imageSlicing({
    url: './big.png',
    //or url:[ur1,ur2],
    fetchOptions: {
        referrer: document.location.href,
        headers: {
            ...
        }
        ...
    }
}).then(result => {
    consle.log(result);
}).catch(error => {
    //do some things
})- 
injectImage(options)inject Image for getImageTile . returnPromiseoptions.url:image urloptions.imageBBOX:image BBOX[minx,miny,maxx,maxy]. Note that the coordinates of the bounding box should be consistent with the projected imageoptions.imageId:image url...fetchOptionsTypefetchOptionsType params
 
const imageId = 'china';
tileActor.injectImage({
    imageId,
    url: './test.jpg',
    imageBBOX: [120, 31, 121, 32]
}).then(data => {
    // baseLayer.addTo(map);
}).catch(error => {
    console.error(error);
})- 
removeImage(imageId)remove image from cache . returnPromiseimageId: image id
 
const imageId = 'china';
tileActor.removeImage(maskId).then(data => {
}).catch(error => {
    console.error(error);
})- 
imageHasInjected(imageId)Has the image been injected . returnBooleanimageId: image id
 
const imageId = 'china';
const result = tileActor.imageHasInjected(maskId);getImageTile(options)get tile data from image . returnPromiseoptions.tileBBOX:tile BBOX[minx,miny,maxx,maxy]options.projection: Projection code, such as : EPSG:3857options.imageId:mask keyoptions?.tileSize:tile size...postProcessingOptionsTypepostProcessingOptionsType params...returnResultTypereturnResultType params
import * as maptalks from 'maptalks-gl';
import {
    getTileActor
} from 'maptalks.tileclip';
const tileActor = getTileActor();
const imageId = 'china';
const baseLayer = new maptalks.TileLayer('base', {
    debug: true,
    urlTemplate: '/arcgisonline/rest/services/Word_Imagery/MapServer/tile/{z}/{y}/{x}',
    subdomains: ["a", "b", "c", "d"],
    // bufferPixel: 1
})
baseLayer.on('renderercreate', function(e) {
    //load tile image
    //   img(Image): an Image object
    //   url(String): the url of the tile
    e.renderer.loadTileBitmap = function(url, tile, callback) {
        tileActor.getImageTile({
            imageId,
            projection: baseLayer.getProjection().code,
            tileSize: baseLayer.getTileSize().width,
            tileBBOX: baseLayer._getTileBBox(tile),
        }).then(imagebitmap => {
            // console.log(imagebitmap);
            callback(imagebitmap);
        }).catch(error => {
            //do some things
            console.error(error);
        })
    };
});
tileActor.injectImage({
    imageId,
    url: './unnamed.jpg',
    imageBBOX: [...m1, ...m2]
}).then(data => {
    baseLayer.addTo(groupLayer);
}).catch(error => {
    console.error(error);
})getVTTile(options)get vt tile arraybuffer by fetch in worker, returnPromiseoptions.url:tile url or tiles urls...fetchOptionsTypefetchOptionsType params
const layer = new maptalks.VectorTileLayer("geo", {
    style,
    debugTileData: true,
    version: 1,
    // urlTemplate: './../assets/data/suzhou_line/{z}/{x}/{y}.pbf'
    urlTemplate: 'xxx'
});
const roadTileUrl = 'xxx';
layer.on('renderercreate', function(e) {
    e.renderer.loadTileArrayBuffer = function(url, tile, callback, options) {
        console.log(options.command);
        const {
            x,
            y,
            z
        } = tile;
        const url1 = roadTileUrl.replace("{x}", x).replace('{y}', y).replace('{z}', z);
        tileActor.getVTTile({
            //will merge mvt data
            url: [url, url1],
            indexedDBCache: true
        }).then(buffer => {
            callback(null, buffer);
        }).catch(error => {
            console.log(error);
            callback(error);
        })
    };
});