Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change tile loading logic to match native #5119

Merged
merged 9 commits into from
Sep 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 106 additions & 43 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,8 @@ class SourceCache extends Evented {
if (err) {
tile.state = 'errored';
if (err.status !== 404) this._source.fire('error', {tile: tile, error: err});
// continue to try loading parent/children tiles if a tile doesn't exist (404)
else this.update(this.transform);
return;
}

Expand Down Expand Up @@ -333,34 +335,19 @@ class SourceCache extends Evented {
this.transform = transform;
if (!this._sourceLoaded || this._paused) { return; }

let i;
let coord;
let tile;
let parentTile;

this.updateCacheSize(transform);

// Determine the overzooming/underzooming amounts.
const zoom = (this._source.roundZoom ? Math.round : Math.floor)(this.getZoom(transform));
const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom);
const maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this._source.minzoom);

// Retain is a list of tiles that we shouldn't delete, even if they are not
// the most ideal tile for the current viewport. This may include tiles like
// parent or child tiles that are *already* loaded.
const retain = {};

// Covered is a list of retained tiles who's areas are full covered by other,
// Covered is a list of retained tiles who's areas are fully covered by other,
// better, retained tiles. They are not drawn separately.
this._coveredTiles = {};

let visibleCoords;
let idealTileCoords;
if (!this.used) {
visibleCoords = [];
idealTileCoords = [];
} else if (this._source.coord) {
visibleCoords = transform.getVisibleWrappedCoordinates((this._source.coord: any));
idealTileCoords = transform.getVisibleWrappedCoordinates((this._source.coord: any));
} else {
visibleCoords = transform.coveringTiles({
idealTileCoords = transform.coveringTiles({
tileSize: this._source.tileSize,
minzoom: this._source.minzoom,
maxzoom: this._source.maxzoom,
Expand All @@ -369,37 +356,28 @@ class SourceCache extends Evented {
});

if (this._source.hasTile) {
visibleCoords = visibleCoords.filter((coord) => (this._source.hasTile: any)(coord));
idealTileCoords = idealTileCoords.filter((coord) => (this._source.hasTile: any)(coord));
}
}

for (i = 0; i < visibleCoords.length; i++) {
coord = visibleCoords[i];
tile = this._addTile(coord);

retain[coord.id] = true;

if (tile.hasData())
continue;
// Determine the overzooming/underzooming amounts.
const zoom = (this._source.roundZoom ? Math.round : Math.floor)(this.getZoom(transform));
const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom);
const maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this._source.minzoom);

// The tile we require is not yet loaded.
// Retain child or parent tiles that cover the same area.
if (!this._findLoadedChildren(coord, maxCoveringZoom, retain)) {
parentTile = this.findLoadedParent(coord, minCoveringZoom, retain);
if (parentTile) {
this._addTile(parentTile.coord);
}
}
}
// Retain is a list of tiles that we shouldn't delete, even if they are not
// the most ideal tile for the current viewport. This may include tiles like
// parent or child tiles that are *already* loaded.
const retain = this._updateRetainedTiles(idealTileCoords, zoom);

const parentsForFading = {};

if (isRasterType(this._source.type)) {
const ids = Object.keys(retain);
for (let k = 0; k < ids.length; k++) {
const id = ids[k];
coord = TileCoord.fromID(+id);
tile = this._tiles[id];
const coord = TileCoord.fromID(+id);
const tile = this._tiles[id];
if (!tile) continue;

// If the drawRasterTile has never seen this tile, then
Expand All @@ -410,7 +388,7 @@ class SourceCache extends Evented {
if (this._findLoadedChildren(coord, maxCoveringZoom, retain)) {
retain[id] = true;
}
parentTile = this.findLoadedParent(coord, minCoveringZoom, parentsForFading);
const parentTile = this.findLoadedParent(coord, minCoveringZoom, parentsForFading);
if (parentTile) {
this._addTile(parentTile.coord);
}
Expand All @@ -428,14 +406,99 @@ class SourceCache extends Evented {
for (fadedParent in parentsForFading) {
retain[fadedParent] = true;
}

// Remove the tiles we don't need anymore.
const remove = util.keysDifference(this._tiles, retain);
for (i = 0; i < remove.length; i++) {
for (let i = 0; i < remove.length; i++) {
this._removeTile(remove[i]);
}
}

_updateRetainedTiles(idealTileCoords: Array<TileCoord>, zoom: number): { [string]: boolean} {
let i, coord, tile, covered;

const retain = {};
const checked: {[number]: boolean } = {};
const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom);


for (i = 0; i < idealTileCoords.length; i++) {
coord = idealTileCoords[i];
tile = this._addTile(coord);
let parentWasRequested = false;
if (tile.hasData()) {
retain[coord.id] = true;
} else {
// The tile we require is not yet loaded or does not exist.
// We are now attempting to load child and parent tiles.

// As we descend up and down the tile pyramid of the ideal tile, we check whether the parent
// tile has been previously requested (and errored in this case due to the previous conditional)
// in order to determine if we need to request its parent.
parentWasRequested = tile.wasRequested();

// The tile isn't loaded yet, but retain it anyway because it's an ideal tile.
retain[coord.id] = true;
covered = true;
const overscaledZ = zoom + 1;
if (overscaledZ > this._source.maxzoom) {
// We're looking for an overzoomed child tile.
const childCoord = coord.children(this._source.maxzoom)[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't follow what's happening in this branch -- why do we only get the first child tile?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overzoomed children are different from regular children. While regular children increase the z by one, and enumerate all four quadrants, an "overzoomed child" is represented with the same z/x/y as its parent, except that the overzoomedZ value is increased by one. We're using overzoomed children to indicate that it's using the same data as the non-overzoomed tile, except "overzoomed" to a particular zoom level. This overzoom information is used while parsing the tile and placing labels.

const childTile = this.getTile(childCoord);
if (!!childTile && childTile.hasData()) {
retain[childCoord.id] = true;
} else {
covered = false;
}
} else {
// Check all four actual child tiles.
const children = coord.children(this._source.maxzoom);
for (let j = 0; j < children.length; j++) {
const childCoord = children[j];
const childTile = childCoord ? this.getTile(childCoord) : null;
if (!!childTile && childTile.hasData()) {
retain[childCoord.id] = true;
} else {
covered = false;
}
}
}

if (!covered) {

// We couldn't find child tiles that entirely cover the ideal tile.
for (let overscaledZ = zoom - 1; overscaledZ >= minCoveringZoom; --overscaledZ) {

const parentId = coord.scaledTo(overscaledZ);
if (checked[parentId.id]) {
// Break parent tile ascent, this route has been previously checked by another child.
break;
} else {
checked[parentId.id] = true;
}


tile = this.getTile(parentId);
if (!tile && parentWasRequested) {
tile = this._addTile(parentId);
}

if (tile) {
retain[parentId.id] = true;
// Save the current values, since they're the parent of the next iteration
// of the parent tile ascent loop.
parentWasRequested = tile.wasRequested();
if (tile.hasData()) {
break;
}
}
}
}
}
}

return retain;
}

/**
* Add a tile, given its coordinate, to the pyramid.
* @private
Expand Down
4 changes: 4 additions & 0 deletions src/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ class Tile {
animationLoop.set(this.fadeEndTime - Date.now());
}

wasRequested() {
return this.state === 'errored' || this.state === 'loaded' || this.state === 'reloading';
}

/**
* Given a data object with a 'buffers' property, load it into
* this tile's elementGroups and buffers properties and set loaded
Expand Down
8 changes: 8 additions & 0 deletions src/source/tile_coord.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ class TileCoord {
];
}

scaledTo(targetZ: number) {
if (targetZ <= this.z) {
return new TileCoord(targetZ, this.x >> (this.z - targetZ), this.y >> (this.z - targetZ), this.w); // parent or same
} else {
return new TileCoord(targetZ, this.x << (targetZ - this.z), this.y << (targetZ - this.z), this.w); // child
}
}

static cover(z: number, bounds: [Coordinate, Coordinate, Coordinate, Coordinate],
actualZ: number, renderWorldCopies: boolean | void) {
if (renderWorldCopies === undefined) {
Expand Down
2 changes: 0 additions & 2 deletions test/ignores.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
"render-tests/fill-extrusion-pattern/opacity": "https://github.com/mapbox/mapbox-gl-js/issues/3327",
"render-tests/geojson/inline-linestring-fill": "current behavior is arbitrary",
"render-tests/line-dasharray/zoom-history": "needs investigation",
"render-tests/raster-loading/missing": "https://github.com/mapbox/mapbox-gl-js/issues/4257",
"render-tests/raster-masking/overlapping": "https://github.com/mapbox/mapbox-gl-js/issues/5003",
"render-tests/regressions/mapbox-gl-js#3682": "skip - true",
"render-tests/runtime-styling/image-add-alpha": "failing on Circle CI 2.0, needs investigation",
"render-tests/runtime-styling/image-update-icon": "skip - https://github.com/mapbox/mapbox-gl-js/issues/4804",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@
{
"id": "raster",
"type": "raster",
"source": "contour"
"source": "contour",
"paint": {
"raster-fade-duration": 0
}
}
]
}
}
2 changes: 1 addition & 1 deletion test/suite_implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ ajax.getImage = function({ url }, callback) {
callback(null, png);
});
} else {
callback(error || new Error(response.statusCode));
callback(error || {status: response.statusCode});
}
});
};
Expand Down
Loading