Skip to content

Instantly share code, notes, and snippets.

@d1manson
Last active December 21, 2022 12:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save d1manson/a987e6af63a47129ab54c73b31a7b1bd to your computer and use it in GitHub Desktop.
Save d1manson/a987e6af63a47129ab54c73b31a7b1bd to your computer and use it in GitHub Desktop.
// For info on usage, development and debugging,
// see https://docs.google.com/a/landtech.co/document/d/1eB2oH3d7mpDfK8gxTiYphxPgBM7EDu5MeUNKrQwYIrQ/ . [private]
const Painter = require('./render/painter'),
Style = require('./style/style'),
EXTENT = require('./data/extent'),
Evented = require('./util/evented'),
TileCoord = require('./source/tile_coord'),
mat4 = require('@mapbox/gl-matrix').mat4,
Source = require('./source/source'),
Tile = require('./source/tile'),
Point = require('point-geometry'),
QueryFeatures = require('./source/query_features'),
SphericalMercator = require('@mapbox/sphericalmercator');
var sphericalMercator = new SphericalMercator();
const DEFAULT_RESOLUTION = 256;
const TILE_LOAD_TIMEOUT = 60000;
const TILE_CACHE_SIZE = 100;
const MAX_RENDER_SIZE = 1024; // for higher resolutions, we render in sections
const DEFAULT_BUFFER_ZONE_WIDTH = 0;
var layerStylesheetFromLayer = layer => layer && layer._eventedParent.stylesheet.layers.find(x=>x.id===layer.id);
class Style2 extends Style {
constructor(stylesheet, map, options){
super(stylesheet, map, options);
this._callsPendingStyleLoad = [];
}
addSource(id, source, options){
console.assert(!this._source, "can only load one source");
this._source = Source.create(id, source, this.dispatcher, this);
this._source.tiles = source.tiles;
this._source.map = this.map;
this._source.setEventedParent(this, {source: this._source});
this.sourceCaches[id] = {
getSource: () => this._source,
getVisibleCoordinates: () => [this._currentCoord],
getTile: () => this._currentTile,
reload: () => {},
serialize: () => this._source.serialize(),
map: {}
};
this.on('data', e => {
if(e.dataType !== "style"){
return;
}
this._recalculate(16); // TODO: use proper zoom value (which depends on z at the point we actually render)
while(this._callsPendingStyleLoad && this._callsPendingStyleLoad.length){
this._callsPendingStyleLoad.shift()();
}
this._callsPendingStyleLoad = null;
});
}
setPaintProperty(layer, prop, val){
if(this._callsPendingStyleLoad){
this._callsPendingStyleLoad.push(()=> super.setPaintProperty(layer, prop, val));
} else {
super.setPaintProperty(layer, prop, val);
}
}
setFilter(layer, filter){
if(this._callsPendingStyleLoad){
this._callsPendingStyleLoad.push(()=> super.setFilter(layer, filter));
} else {
super.setFilter(layer, filter);
}
}
};
class Painter2 extends Painter {
constructor(gl, transform){
super(gl, transform);
this._filterForZoom = 15;
}
resize(width, height) {
const gl = this.gl;
this.width = width;
this.height = height;
gl.viewport(0, 0, this.width, this.height);
}
renderLayer(painter, sourceCache, layer, coords) {
let layerStylesheet = layerStylesheetFromLayer(layer);
if (layerStylesheet && layerStylesheet.minzoom_ && this._filterForZoom < layerStylesheet.minzoom_) return;
if (layerStylesheet && layerStylesheet.maxzoom_ && this._filterForZoom >= layerStylesheet.maxzoom_) return;
super.renderLayer(painter, sourceCache, layer, coords);
}
enableTileClippingMask(){ }
};
class MapboxSingleTile extends Evented {
constructor(options) {
super();
this._initOptions = options = options || {};
this.transform = {zoom: 0, angle: 0, pitch: 0, scaleZoom: ()=> 0, cameraToCenterDistance: 1};
this._style = new Style2(Object.assign({}, options.style, {transition: {duration: 0}}), this);
this._style.setEventedParent(this, {style: this._style});
this._style.on('data', e => (e.dataType === "style") && this._style.update([], {transition: false}));
this._nextRenderId = 0;
this._canvas = document.createElement('canvas');
this._canvas.style.imageRendering = 'pixelated';
this._canvas.addEventListener('webglcontextlost', () => console.log("webglcontextlost"), false);
this._canvas.addEventListener('webglcontextrestored', () => this._createGlContext(), false);
this._createGlContext();
this.setResolution(DEFAULT_RESOLUTION, DEFAULT_BUFFER_ZONE_WIDTH);
this._pendingRenders = {}; // coord.id => render state
}
get _source(){
return this._style._source;
}
_calculatePosMatrix(transX, transY) {
/*
The returned matrix, M, is designed to be used as:
X_gl_coords = M * X_mvt_data_coords
where X_gl_coords are in the range [-1,1]
and X_mvt_data_coords are in the range [0,Extent], or rather they are nearly
within that range, they actually go outside it by about 10% to let polygons/lines
span across tile boundaries.
The translate/scale functions here could probably be ditched in favour of
the mat4 versions, but I found it easier to do the multiplies myself.
*/
var translate = (a, v) => {
this._tmpMat4f64b = this._tmpMat4f64b || new Float32Array(16);
mat4.identity(this._tmpMat4f64b);
mat4.translate(this._tmpMat4f64b, this._tmpMat4f64b,v);
mat4.multiply(a,this._tmpMat4f64b,a);
}
var scale = (a, v) => {
this._tmpMat4f64b = this._tmpMat4f64b || new Float32Array(16);
mat4.identity(this._tmpMat4f64b);
mat4.scale(this._tmpMat4f64b, this._tmpMat4f64b,v);
mat4.multiply(a,this._tmpMat4f64b,a);
}
this._tmpMat4f64 = this._tmpMat4f64 || new Float64Array(16); // reuse each time for GC's benefit
this._tmpMat4f32 = this._tmpMat4f32 || new Float32Array(16);
const factor = (this._resolution/this._canvasSizeInner) // this bit is 1 for further-zoomed out rendering
*(this._canvasSizeInner/this._canvasSizeFull);
const b = this._bufferZoneWidth/this._canvasSizeFull*2;
// The main calculation...
mat4.identity(this._tmpMat4f64);
translate(this._tmpMat4f64, [-EXTENT*transX/this._resolution, -EXTENT*transY/this._resolution,0]);
scale(this._tmpMat4f64,[2/EXTENT*factor, -2/EXTENT*factor, 1]);
translate(this._tmpMat4f64, [-1+b, 1-b, 0]);
this._tmpMat4f32.set(this._tmpMat4f64);
return this._tmpMat4f32;
}
_createGlContext(){
const attributes = Object.assign({
failIfMajorPerformanceCaveat: this._initOptions.failIfMajorPerformanceCaveat,
preserveDrawingBuffer: this._initOptions.preserveDrawingBuffer
}, require('mapbox-gl-supported').webGLContextAttributes);
this._gl = this._canvas.getContext('webgl', attributes) ||
this._canvas.getContext('experimental-webgl', attributes);
if (!this._gl) {
throw new Error('Failed to initialize WebGL');
}
console.log("hidedyhidi")
this.painter = new Painter2(this._gl, this.transform);
this.painter.style = this._style;
}
setPaintProperty(layer, prop, val){
this._style.setPaintProperty(layer, prop, val);
this._cancelAllPending(false);
this._style.update([], {transition: false});
}
setFilter(layer, filter){
// https://www.mapbox.com/mapbox-gl-js/style-spec/#types-filter
this._style.setFilter(layer, filter);
this._cancelAllPending(false);
this._style.update([], {transition: false});
}
// takes an array of layer names to show
setLayers(visibleLayers){
this._cancelAllPending(false);
Object.keys(this._style._layers).forEach(layerName =>
this._style.setLayoutProperty(layerName, 'visibility', visibleLayers.indexOf(layerName) > -1 ? 'visible' : 'none'));
this._style.update([], {transition: false});
}
getSuggestedBufferWidth(zoom){
let visibleLayerTypes = this.getLayersVisible(zoom).map(lyr => this._style._layers[lyr].type);
visibleLayerTypes = new Set(visibleLayerTypes);
if(visibleLayerTypes.size > 1){
console.warn("combining multiple layer types is probably not a good idea.");
}
if(visibleLayerTypes.has("circle") || visibleLayerTypes.has("symbol")){
return 30;
} else {
return 0;
}
}
getLayersVisible(zoom){
// if zoom is provided will filter by min/max zoom as well as by layer visibility
return Object.keys(this._style._layers)
.filter(lyr=>this._style.getLayoutProperty(lyr, 'visibility') === 'visible')
.filter(lyr => {
let layerStylesheet = layerStylesheetFromLayer(this._style._layers[lyr]);
return !zoom || (layerStylesheet &&
zoom >= layerStylesheet.minzoom_ &&
zoom <= layerStylesheet.maxzoom_);
});
}
filterForZoom(zoom){
if(zoom === this.painter._filterForZoom){
return;
}
this.painter._filterForZoom = zoom;
this._cancelAllPending(false);
}
setResolution(r, bufferZoneWidth){
bufferZoneWidth = bufferZoneWidth || 0;
if(r === this._resolution && this._bufferZoneWidth === bufferZoneWidth){
return;
}
this._resolution = r;
this._bufferZoneWidth = bufferZoneWidth;
this._canvasSizeFull = Math.min(r + 2*bufferZoneWidth, MAX_RENDER_SIZE);
this._canvasSizeInner = this._canvasSizeFull - 2*bufferZoneWidth;
this._canvas.width = this._canvasSizeFull;
this._canvas.height = this._canvasSizeFull;
this.transform.pixelsToGLUnits = [2 / this._canvasSizeFull, -2 / this._canvasSizeFull];
this.painter.resize(this._canvasSizeFull, this._canvasSizeFull);
this._cancelAllPending(false);
if(this._debugBufferEl){
this._debugBufferEl.style.width = this._canvasSizeFull + 'px';
this._debugBufferEl.style.height = this._canvasSizeFull + 'px';
}
}
_cancelAllPending(){
for(var id in this._pendingRenders){
this._cancelRender(this._pendingRenders[id]);
}
this._pendingRenders = {};
}
_cancelRender(state){
while(state.callbacks.length){
state.callbacks.shift().func("canceled");
}
clearTimeout(state.timeout);
delete this._pendingRenders[state.id];
this._source.abortTile(state.tile);
this._source.unloadTile(state.tile);
}
cancelRender(renderRef, state){
var state = Object.values(this._pendingRenders)
.find(state => state.renderId === renderRef.id);
if(!state){
return;
}
var idx = state.callbacks.indexOf(renderRef.callback);
(idx !== -1) && state.callbacks.splice(idx,1);
(state.callbacks.length === 0) && this._cancelRender(state);
}
renderTile(z, x, y, ctx, drawImageSpec, next){
var callback = {
func: next,
ctx: ctx,
drawImageSpec: drawImageSpec
};
var coord = new TileCoord(z, x, y, 0);
var id = coord.id;
var state = this._pendingRenders[id];
if(state){
state.callbacks.push(callback);
return {id: state.renderId, callback: callback};
}
var renderId = ++this._nextRenderId;
state = this._pendingRenders[id] = {
id: id,
callbacks: [callback],
tile: new Tile(coord.wrapped(), this._resolution, z),
coord: coord,
renderId: renderId,
timeout: setTimeout(() => {
delete this._pendingRenders[coord.id];
while(state.callbacks.length){
state.callbacks.shift().func("timeout");
}
}, TILE_LOAD_TIMEOUT)
};
this._source.loadTile(state.tile, err => {
state = this._pendingRenders[id];
if(!state || state.renderId !== renderId){
return; // render for this tile has been canceled, or superceded.
}
if(!err){
this.transform.zoom = z;
state.tile.tileSize = this._resolution;
this._style._currentCoord = state.coord;
this._style._currentTile = state.tile;
for(var xx=0; xx<this._resolution; xx+= this._canvasSizeInner){
for(var yy=0; yy<this._resolution; yy+=this._canvasSizeInner){
var relevantCallbacks = state.callbacks.filter(cb =>
cb.drawImageSpec.srcLeft + cb.drawImageSpec.srcWidth > xx &&
cb.drawImageSpec.srcLeft < xx + this._canvasSizeInner &&
cb.drawImageSpec.srcTop + cb.drawImageSpec.srcHeight > yy &&
cb.drawImageSpec.srcTop < yy + this._canvasSizeInner);
if(relevantCallbacks.length === 0){
continue;
}
state.coord.posMatrix = this._calculatePosMatrix(xx, yy);
this.painter.render(this._style, {
showTileBoundaries: this._initOptions.showTileBoundaries,
showOverdrawInspector: this._initOptions.showOverdrawInspector
});
relevantCallbacks.forEach(cb => {
// convert from [-bufferZoneWidth, resolution+bufferZoneWidth] to [0, canvasSizeFull]
// Note that requesting pixels from inside the buffer region is a special case,
// and has to be dealt with very carefully, using src[Left|Top]Extra....
let srcLeft = Math.max(0, cb.drawImageSpec.srcLeft-xx);
let srcLeftExtra = cb.drawImageSpec.srcLeft < 0 && xx === 0 ? Math.max(cb.drawImageSpec.srcLeft, -this._bufferZoneWidth) : 0;
let srcTop = Math.max(0, cb.drawImageSpec.srcTop-yy);
let srcTopExtra = cb.drawImageSpec.srcTop < 0 && yy === 0 ? Math.max(cb.drawImageSpec.srcTop, -this._bufferZoneWidth) : 0;
let srcRight = Math.min(this._canvasSizeFull,
this._bufferZoneWidth + cb.drawImageSpec.srcLeft + cb.drawImageSpec.srcWidth - xx);
let srcBottom = Math.min(this._canvasSizeFull,
this._bufferZoneWidth + cb.drawImageSpec.srcTop + cb.drawImageSpec.srcHeight - yy);
cb.ctx.drawImage(
this._canvas,
srcLeft + srcLeftExtra + this._bufferZoneWidth, srcTop + srcTopExtra + this._bufferZoneWidth,
srcRight - (srcLeft + srcLeftExtra), srcBottom - (srcTop + srcTopExtra),
cb.drawImageSpec.destLeft + ((xx > cb.drawImageSpec.srcLeft) && (xx - cb.drawImageSpec.srcLeft + srcLeftExtra)) |0,
cb.drawImageSpec.destTop + ((yy > cb.drawImageSpec.srcTop) && (yy - cb.drawImageSpec.srcTop + srcTopExtra))|0,
srcRight - (srcLeft + srcLeftExtra), srcBottom - (srcTop + srcTopExtra));
});
} // yy
} // xx
this._style._currentCoord = null;
this._style._currentTile = null;
}
while(state.callbacks.length){
state.callbacks.shift().func(err);
}
clearTimeout(state.timeout);
delete this._pendingRenders[id];
this._source.unloadTile(state.tile);
});
return {id: state.renderId, callback: callback};
}
latLngToTileCoords(opts){
let tileXY = sphericalMercator.px([opts.lng, opts.lat], opts.tileZ, false)
.map(x=>x/256 /* why 256? */);
let pointXY = tileXY.map(x => (x - (x|0)) * opts.extent);
return {
tileX: tileXY[0] |0,
tileY: tileXY[1] |0,
tileZ: opts.tileZ,
pointX: pointXY[0],
pointY: pointXY[1]
}
}
queryRenderedFeatures(opts){
let layers = {};
this.getLayersVisible(opts.renderedZoom)
.forEach(lyr => layers[lyr] = this._style._layers[lyr]);
let p = this.latLngToTileCoords(Object.assign({extent: EXTENT}, opts));
// collect the coordinates of the tile containing the given point, plus any with an overlapping buffer region
let coords = [];
let bufferSize = this._bufferZoneWidth/this._resolution * EXTENT; // measured in the same units as pointXY
coords.push(new TileCoord(p.tileZ, p.tileX, p.tileY, 0));
// consider including the left, right, top, bottom adjacent tiles (if the point is near to the given edge)
(p.pointX<bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX-1, p.tileY, 0));
(p.pointX>EXTENT-bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX+1, p.tileY, 0));
(p.pointY<bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX, p.tileY-1, 0));
(p.pointY>EXTENT-bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX, p.tileY+1, 0));
// and consider including the 4 corner adjacent tiles (again, if the point is near the given corner)
(p.pointX<bufferSize && p.pointY<bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX-1, p.tileY-1, 0));
(p.pointX<bufferSize && p.pointY>EXTENT-bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX-1, p.tileY+1, 0));
(p.pointX>EXTENT - bufferSize && p.pointY<bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX+1, p.tileY-1, 0));
(p.pointX>EXTENT - bufferSize && p.pointY>EXTENT-bufferSize) && coords.push(new TileCoord(p.tileZ, p.tileX+1, p.tileY+1, 0));
// prepare the fake tileCache (we will issue tile load requests in a moment)
let sourceCache = Object.create(this._style.sourceCaches.landinsight);
let tilesIn = coords.map(c => ({
tile: new Tile(c, this._resolution, opts.tileZ),
coord: c,
queryGeometry: [[Point.convert([
// for all but the 0th coord, we need to adjust the pointXY values to lie suitably outside the [0,EXTENT] range
p.pointX + EXTENT*(p.tileX-c.x),
p.pointY + EXTENT*(p.tileY-c.y),
])]],
scale: 1
}));
sourceCache.tilesIn = () => tilesIn;
let nTilesPending = coords.length;
return new Promise((res, rej) => {
let timer = setTimeout(() => {
timer = null;
rej("timeout");
}, opts.timeoutMS || 1000);
tilesIn.forEach(t => this._source.loadTile(t.tile, err => {
if(--nTilesPending>0 || !timer){
return;
}
clearTimeout(timer);
let featuresByRenderLayer = QueryFeatures.rendered(
sourceCache,
layers,
null /* query geometry is pre-specified in tilesIn */,
{circleFudgeExtraPx: 5}, opts.tileZ, 0);
let featuresBySourceLayer = {};
Object.keys(featuresByRenderLayer).forEach(f => featuresByRenderLayer[f].map(ff =>
(featuresBySourceLayer[ff.layer['source-layer']] = featuresBySourceLayer[ff.layer['source-layer']] || [])
.push(ff._vectorTileFeature.properties)));
res(featuresBySourceLayer);
}));
});
}
showCanvasForDebug(){
document.body.appendChild(this._canvas);
this._canvas.style.position = "fixed";
this._canvas.style.top = "250px";
this._canvas.style.right = "20px";
this._canvas.style.background = "#ccc";
var buffer = this._debugBufferEl = document.createElement('div');
buffer.style.position = "fixed";
buffer.style.top = "250px";
buffer.style.right = "20px";
buffer.style.border = '45px solid rgba(255,0,0,0.2)'; // TODO: use this._bufferZoneWidth properly
document.body.appendChild(buffer);
}
}
export default MapboxSingleTile;
/* global google document */
// For info on usage, development and debugging,
// see implementation in https://drive.google.com/drive/u/1/folders/0B5nRmwke0A0ORDBqMkRIeWdrb1U. [private]
import MapboxSingleTile from 'mapbox-gl/src/single-tile';
function MapboxGoogleOverlay(options) {
this.mapboxRenderer = new MapboxSingleTile(options);
this.mapboxRenderer.on('data', data => (data.dataType === "style") && options.onStyleLoaded && options.onStyleLoaded());
this._tileSize = 256;
this.tileSize = new google.maps.Size(this._tileSize, this._tileSize);
this.minZoom = 15;
this.maxZoom = 21;
this.availableZoom = 16;
this._unusedTilesPool = [];
this._bufferWidth = 0;
this._visibleTiles = new Map(); // domEl => {canv, ctx, renderSpec, renderRefs, nPending, nErrors etc.}
this.showDebugColors = false;
this._dummyTile = document.createElement('div');
this._visibility = true;
}
var MAX_TILE_POOL_SIZE = 30;
var CANV_SIZE = 512; // (needlessly) large canvases go on the GPU which makes drawImage a LOT faster.
MapboxGoogleOverlay.prototype._createTile = function(){
let el = document.createElement('div');
let canv = document.createElement('canvas');
canv.width = CANV_SIZE;
canv.height = CANV_SIZE;
canv.style.imageRendering = 'pixelated';
canv.style.visibility = this._visibility ? '' : 'hidden';
canv.style.position = 'absolute';
this._applyBufferWidthToCanv(canv);
el.appendChild(canv);
return el;
};
MapboxGoogleOverlay.prototype._applyBufferWidthToCanv = function(canv){
canv.style.top = '-' + this._bufferWidth + 'px';
canv.style.left = '-' + this._bufferWidth + 'px';
};
MapboxGoogleOverlay.prototype.queryRenderedFeatures = function(opts){
// opts = {lat, lng, zoom}
return this.mapboxRenderer.queryRenderedFeatures({
lat: opts.lat,
lng: opts.lng,
tileZ: this.availableZoom,
renderedZoom: opts.zoom
});
};
MapboxGoogleOverlay.prototype.queryRenderedFeaturesBasic = function(opts){
// opts = {lat, lng, zoom}. This is a temporary hack that checks if cursor is over pixel with color/some-opacity
let p = this.mapboxRenderer.latLngToTileCoords(Object.assign({
tileZ: this.availableZoom,
extent: 1
}, opts));
let ret = false;
this._visibleTiles.forEach(state => state.renderSpec.forEach(spec => {
let x = (p.tileX + p.pointX - spec.x) * spec.resolution - spec.srcLeft + spec.destLeft;
let y = (p.tileY + p.pointY - spec.y) * spec.resolution - spec.srcTop + spec.destTop;
if(x >=0 && x< CANV_SIZE && y>=0 && y<CANV_SIZE){
let color = state.ctx.getImageData(x,y,1,1).data;
ret |= !!new Uint32Array(color.buffer)[0];
}
}));
return ret;
};
MapboxGoogleOverlay.prototype._getRenderSpec = function(coord, zoom){
let buffer = this._bufferWidth | 0;
if(zoom === this.availableZoom){
return [{
z: this.availableZoom,
x: coord.x,
y: coord.y,
resolution: this._tileSize,
srcTop: 0 - buffer,
srcLeft: 0 - buffer,
srcWidth: this._tileSize + buffer * 2,
srcHeight: this._tileSize + buffer * 2,
destLeft: 0,
destTop: 0
}];
} else if (zoom > this.availableZoom){
let shift = (zoom-this.availableZoom);
let mask = (1<<shift)-1;
let isLeftEdge = (coord.x & mask) === 0;
let isRightEdge = (coord.x & mask) === mask;
let isTopEdge = (coord.y & mask) === 0;
let isBottomEdge = (coord.y & mask) === mask;
return [{
z: this.availableZoom,
x: coord.x >> shift,
y: coord.y >> shift,
resolution: this._tileSize * (1 << shift),
srcTop: (coord.y & mask)*this._tileSize - (isTopEdge && buffer),
srcLeft: (coord.x & mask)*this._tileSize - (isLeftEdge && buffer),
srcWidth: this._tileSize + (isLeftEdge && buffer) + (isRightEdge && buffer),
srcHeight: this._tileSize + (isTopEdge && buffer) + (isBottomEdge && buffer),
destLeft: 0 + !isLeftEdge && buffer,
destTop: 0 + !isTopEdge && buffer
}];
} else {
let shift = (this.availableZoom-zoom);
let nParts = (1<<shift);
let ret = [];
for(var xx=0; xx<nParts; xx++) for(var yy=0; yy<nParts; yy++){
ret.push({
z: this.availableZoom,
x: (coord.x << shift) | xx,
y: (coord.y << shift) | yy,
resolution: this._tileSize / (1 << shift),
srcTop: 0 - buffer,
srcLeft: 0 - buffer,
srcWidth: this._tileSize / (1 << shift) + buffer * 2,
srcHeight: this._tileSize / (1 << shift) + buffer * 2,
destLeft: xx * this._tileSize / (1 << shift),
destTop: yy * this._tileSize / (1 << shift)
});
}
return ret;
}
};
MapboxGoogleOverlay.prototype._renderTile = function(el){
let state = this._visibleTiles.get(el);
state.renderSpec = this._getRenderSpec(state.coord, state.zoom);
state.ctx.clearRect(0, 0, CANV_SIZE, CANV_SIZE);
state.nPending = state.renderSpec.length;
state.nErrors = 0;
state.nTimeouts = 0;
state.nSuccess = 0;
!this.showDebugColors && (state.canv.style.visibility = 'hidden');
this.showDebugColors && (state.canv.style.opacity = 0.1);
this.showDebugColors && (state.canv.style.backgroundColor = '#00f');
this.mapboxRenderer.filterForZoom(state.zoom);
this.mapboxRenderer.setResolution(state.renderSpec[0].resolution, this._bufferWidth);
state.renderRefs = state.renderSpec.map(spec => this.mapboxRenderer.renderTile(
spec.z, spec.x, spec.y, state.ctx, spec, err => {
state = this._visibleTiles.get(el);
state.nPending--;
if(err == "canceled"){
return;
} else if(err === 'timeout'){
state.nTimeouts++;
} else if(err) {
state.nErrors++;
} else {
state.nSuccess++;
this._visibility && (state.canv.style.visibility = '');
}
this.showDebugColors && (state.nPending === 0) && (state.nErrors + state.nTimeouts === 0) && (state.canv.style.opacity = '');
this.showDebugColors && (state.canv.style.backgroundColor =
(state.nTimeouts && '#ff0') || (state.nErrors && '#f00') || (state.nPending && '#0ff') || '');
}));
};
MapboxGoogleOverlay.prototype.getTile = function(coord, zoom) {
if(zoom < this.minZoom || zoom > this.maxZoom){
return this._dummyTile; // for some reason the zoom limits are ignored so we have to do this
}
let el = this._unusedTilesPool.pop() || this._createTile();
let canv = el.firstChild;
this._visibleTiles.set(el, {
canv: canv,
ctx: canv.getContext('2d'),
coord: coord,
zoom: zoom,
nPending: 0,
nErrors: 0,
nTimeouts: 0,
nSuccess: 0,
renderRefs: []
});
this._renderTile(el);
return el;
};
MapboxGoogleOverlay.prototype.setPaintProperty = function(layer, prop, val) {
this.mapboxRenderer.setPaintProperty(layer, prop, val);
this._visibleTiles.forEach((v,k)=>this._renderTile(k));
};
MapboxGoogleOverlay.prototype.setFilter = function(layer, filter) {
this.mapboxRenderer.setFilter(layer, filter);
this._visibleTiles.forEach((v,k)=>this._renderTile(k));
};
MapboxGoogleOverlay.prototype.setLayers = function(visibleLayers) {
this.mapboxRenderer.setLayers(visibleLayers);
this._bufferWidth = this.mapboxRenderer.getSuggestedBufferWidth();
this._visibleTiles.forEach(state => this._applyBufferWidthToCanv(state.canv));
this._visibleTiles.forEach((state,el) => this._renderTile(el));
};
MapboxGoogleOverlay.prototype.releaseTile = function(el){
if(el === this._dummyTile){
return;
}
var state = this._visibleTiles.get(el);
(state.nPending > 0) && state.renderRefs.map(ref=>this.mapboxRenderer.cancelRender(ref));
this._visibleTiles.delete(el);
if (this._unusedTilesPool.length < MAX_TILE_POOL_SIZE) {
this._unusedTilesPool.push(el);
}
};
MapboxGoogleOverlay.prototype.addToMap = function(map){
var idx = map.overlayMapTypes.indexOf(this);
(idx === -1) && map.overlayMapTypes.push(this);
};
MapboxGoogleOverlay.prototype.removeFromMap = function(map){
var idx = map.overlayMapTypes.indexOf(this);
(idx !== -1) && map.overlayMapTypes.removeAt(idx);
this._visibleTiles.forEach((v,k) => this.releaseTile(k));
};
MapboxGoogleOverlay.prototype.setVisiblity = function(visibility){
this._visibility = visibility;
this._visibleTiles.forEach((v,k) => k.style.visibility = visibility ? '' : 'hidden');
this._unusedTilesPool.forEach(k => k.style.visibility = visibility ? '' : 'hidden');
};
MapboxGoogleOverlay.prototype.getLayersVisible = function(zoom) {
return this.mapboxRenderer.getLayersVisible(zoom);
};
export default MapboxGoogleOverlay;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment