mirror of
https://github.com/FoggedLens/iD.git
synced 2026-03-22 19:13:35 +00:00
Add loadTileAtLoc to fetch data tile for a specific location
(closes #4890) This lets iD request needed tiles outside of the viewport, for example to properly straighten lines or validate features that may have unloaded connections.
This commit is contained in:
@@ -107,35 +107,63 @@ export function coreContext() {
|
||||
return context;
|
||||
};
|
||||
|
||||
context.loadTiles = utilCallWhenIdle(function(projection, callback) {
|
||||
|
||||
function wrapcb(callback, cid) {
|
||||
return function(err, result) {
|
||||
if (err) {
|
||||
// 400 Bad Request, 401 Unauthorized, 403 Forbidden..
|
||||
if (err.status === 400 || err.status === 401 || err.status === 403) {
|
||||
connection.logout();
|
||||
}
|
||||
return callback.call(context, err);
|
||||
|
||||
} else if (connection.getConnectionId() !== cid) {
|
||||
return callback.call(context, { message: 'Connection Switched', status: -1 });
|
||||
|
||||
} else {
|
||||
return callback.call(context, err, result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
context.loadTiles = function(projection, callback) {
|
||||
var cid;
|
||||
function done(err, result) {
|
||||
if (connection.getConnectionId() !== cid) {
|
||||
if (callback) callback({ message: 'Connection Switched', status: -1 });
|
||||
return;
|
||||
}
|
||||
if (!err) history.merge(result.data, result.extent);
|
||||
if (callback) callback(err, result);
|
||||
}
|
||||
if (connection && context.editable()) {
|
||||
cid = connection.getConnectionId();
|
||||
connection.loadTiles(projection, done);
|
||||
utilCallWhenIdle(function() {
|
||||
connection.loadTiles(projection, wrapcb(done, cid));
|
||||
})();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
context.loadTileAtLoc = function(loc, callback) {
|
||||
var cid;
|
||||
function done(err, result) {
|
||||
if (!err) history.merge(result.data, result.extent);
|
||||
if (callback) callback(err, result);
|
||||
}
|
||||
if (connection && context.editable()) {
|
||||
cid = connection.getConnectionId();
|
||||
utilCallWhenIdle(function() {
|
||||
connection.loadTileAtLoc(loc, wrapcb(done, cid));
|
||||
})();
|
||||
}
|
||||
};
|
||||
|
||||
context.loadEntity = function(entityID, callback) {
|
||||
var cid;
|
||||
function done(err, result) {
|
||||
if (connection.getConnectionId() !== cid) {
|
||||
if (callback) callback({ message: 'Connection Switched', status: -1 });
|
||||
return;
|
||||
}
|
||||
if (!err) history.merge(result.data, result.extent);
|
||||
if (callback) callback(err, result);
|
||||
}
|
||||
if (connection) {
|
||||
cid = connection.getConnectionId();
|
||||
connection.loadEntity(entityID, done);
|
||||
connection.loadEntity(entityID, wrapcb(done, cid));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -166,11 +194,11 @@ export function coreContext() {
|
||||
};
|
||||
|
||||
var minEditableZoom = 16;
|
||||
context.minEditableZoom = function(_) {
|
||||
context.minEditableZoom = function(val) {
|
||||
if (!arguments.length) return minEditableZoom;
|
||||
minEditableZoom = _;
|
||||
minEditableZoom = val;
|
||||
if (connection) {
|
||||
connection.tileZoom(_);
|
||||
connection.tileZoom(val);
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -178,9 +206,9 @@ export function coreContext() {
|
||||
|
||||
/* History */
|
||||
var inIntro = false;
|
||||
context.inIntro = function(_) {
|
||||
context.inIntro = function(val) {
|
||||
if (!arguments.length) return inIntro;
|
||||
inIntro = _;
|
||||
inIntro = val;
|
||||
return context;
|
||||
};
|
||||
|
||||
@@ -284,9 +312,9 @@ export function coreContext() {
|
||||
/* Copy/Paste */
|
||||
var copyIDs = [], copyGraph;
|
||||
context.copyGraph = function() { return copyGraph; };
|
||||
context.copyIDs = function(_) {
|
||||
context.copyIDs = function(val) {
|
||||
if (!arguments.length) return copyIDs;
|
||||
copyIDs = _;
|
||||
copyIDs = val;
|
||||
copyGraph = history.graph();
|
||||
return context;
|
||||
};
|
||||
@@ -355,42 +383,42 @@ export function coreContext() {
|
||||
|
||||
/* Container */
|
||||
var container = d3_select(document.body);
|
||||
context.container = function(_) {
|
||||
context.container = function(val) {
|
||||
if (!arguments.length) return container;
|
||||
container = _;
|
||||
container = val;
|
||||
container.classed('id-container', true);
|
||||
return context;
|
||||
};
|
||||
var embed;
|
||||
context.embed = function(_) {
|
||||
context.embed = function(val) {
|
||||
if (!arguments.length) return embed;
|
||||
embed = _;
|
||||
embed = val;
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/* Assets */
|
||||
var assetPath = '';
|
||||
context.assetPath = function(_) {
|
||||
context.assetPath = function(val) {
|
||||
if (!arguments.length) return assetPath;
|
||||
assetPath = _;
|
||||
assetPath = val;
|
||||
return context;
|
||||
};
|
||||
|
||||
var assetMap = {};
|
||||
context.assetMap = function(_) {
|
||||
context.assetMap = function(val) {
|
||||
if (!arguments.length) return assetMap;
|
||||
assetMap = _;
|
||||
assetMap = val;
|
||||
return context;
|
||||
};
|
||||
|
||||
context.asset = function(_) {
|
||||
var filename = assetPath + _;
|
||||
context.asset = function(val) {
|
||||
var filename = assetPath + val;
|
||||
return assetMap[filename] || filename;
|
||||
};
|
||||
|
||||
context.imagePath = function(_) {
|
||||
return context.asset('img/' + _);
|
||||
context.imagePath = function(val) {
|
||||
return context.asset('img/' + val);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -63,16 +63,29 @@ export function operationStraighten(selectedIDs, context) {
|
||||
|
||||
|
||||
operation.disabled = function() {
|
||||
var osm = context.connection();
|
||||
var reason = action.disabled(context.graph());
|
||||
if (reason) {
|
||||
return reason;
|
||||
} else if (osm && !coords.every(osm.isDataLoaded)) {
|
||||
} else if (someMissing()) {
|
||||
return 'not_downloaded';
|
||||
} else if (selectedIDs.some(context.hasHiddenConnections)) {
|
||||
return 'connected_to_hidden';
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
function someMissing() {
|
||||
var osm = context.connection();
|
||||
if (osm) {
|
||||
var missing = coords.filter(function(loc) { return !osm.isDataLoaded(loc); });
|
||||
if (missing.length) {
|
||||
missing.forEach(function(loc) { context.loadTileAtLoc(loc); });
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import osmAuth from 'osm-auth';
|
||||
import rbush from 'rbush';
|
||||
|
||||
import { JXON } from '../util/jxon';
|
||||
import { geoExtent, geoVecAdd } from '../geo';
|
||||
import { geoExtent, geoRawMercator, geoVecAdd, geoZoomToScale } from '../geo';
|
||||
import { osmEntity, osmNode, osmNote, osmRelation, osmWay } from '../osm';
|
||||
import {
|
||||
utilArrayChunk, utilArrayGroupBy, utilArrayUniq, utilRebind,
|
||||
@@ -27,8 +27,8 @@ var oauth = osmAuth({
|
||||
});
|
||||
|
||||
var _blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*'];
|
||||
var _tileCache = { loaded: {}, inflight: {}, seen: {}, rtree: rbush() };
|
||||
var _noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
var _tileCache = { toLoad: {}, loaded: {}, inflight: {}, seen: {}, rtree: rbush() };
|
||||
var _noteCache = { toLoad: {}, loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
var _userCache = { toLoad: {}, user: {} };
|
||||
var _changeset = {};
|
||||
|
||||
@@ -58,13 +58,18 @@ function abortRequest(i) {
|
||||
}
|
||||
|
||||
|
||||
function abortUnwantedRequests(cache, tiles) {
|
||||
function hasInflightRequests(cache) {
|
||||
return Object.keys(cache.inflight).length;
|
||||
}
|
||||
|
||||
|
||||
function abortUnwantedRequests(cache, visibleTiles) {
|
||||
Object.keys(cache.inflight).forEach(function(k) {
|
||||
var wanted = tiles.find(function(tile) { return k === tile.id; });
|
||||
if (!wanted) {
|
||||
abortRequest(cache.inflight[k]);
|
||||
delete cache.inflight[k];
|
||||
}
|
||||
if (cache.toLoad[k]) return;
|
||||
if (visibleTiles.find(function(tile) { return k === tile.id; })) return;
|
||||
|
||||
abortRequest(cache.inflight[k]);
|
||||
delete cache.inflight[k];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -365,8 +370,8 @@ export default {
|
||||
Object.values(_noteCache.inflightPost).forEach(abortRequest);
|
||||
if (_changeset.inflight) abortRequest(_changeset.inflight);
|
||||
|
||||
_tileCache = { loaded: {}, inflight: {}, seen: {}, rtree: rbush() };
|
||||
_noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
_tileCache = { toLoad: {}, loaded: {}, inflight: {}, seen: {}, rtree: rbush() };
|
||||
_noteCache = { toLoad: {}, loaded: {}, inflight: {}, inflightPost: {}, note: {}, closed: {}, rtree: rbush() };
|
||||
_userCache = { toLoad: {}, user: {} };
|
||||
_changeset = {};
|
||||
|
||||
@@ -774,51 +779,56 @@ export default {
|
||||
loadTiles: function(projection, callback) {
|
||||
if (_off) return;
|
||||
|
||||
var that = this;
|
||||
var path = '/api/0.6/map?bbox=';
|
||||
|
||||
// determine the needed tiles to cover the view
|
||||
var tiles = tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);
|
||||
|
||||
// abort inflight requests that are no longer needed
|
||||
var hadRequests = hasInflightRequests();
|
||||
var hadRequests = hasInflightRequests(_tileCache);
|
||||
abortUnwantedRequests(_tileCache, tiles);
|
||||
if (hadRequests && !hasInflightRequests()) {
|
||||
if (hadRequests && !hasInflightRequests(_tileCache)) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
|
||||
// issue new requests..
|
||||
tiles.forEach(function(tile) {
|
||||
if (_tileCache.loaded[tile.id] || _tileCache.inflight[tile.id]) return;
|
||||
if (!hasInflightRequests()) {
|
||||
dispatch.call('loading'); // start the spinner
|
||||
}
|
||||
this.loadTile(tile, callback);
|
||||
}, this);
|
||||
},
|
||||
|
||||
var options = { skipSeen: true };
|
||||
_tileCache.inflight[tile.id] = that.loadFromAPI(
|
||||
path + tile.extent.toParam(),
|
||||
function(err, parsed) {
|
||||
delete _tileCache.inflight[tile.id];
|
||||
if (!err) {
|
||||
_tileCache.loaded[tile.id] = true;
|
||||
var bbox = tile.extent.bbox();
|
||||
bbox.id = tile.id;
|
||||
_tileCache.rtree.insert(bbox);
|
||||
}
|
||||
if (callback) {
|
||||
callback(err, Object.assign({ data: parsed }, tile));
|
||||
}
|
||||
if (!hasInflightRequests()) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
});
|
||||
|
||||
function hasInflightRequests() {
|
||||
return Object.keys(_tileCache.inflight).length;
|
||||
// Load a single data tile
|
||||
// GET /api/0.6/map?bbox=
|
||||
loadTile: function(tile, callback) {
|
||||
if (_off) return;
|
||||
if (_tileCache.loaded[tile.id] || _tileCache.inflight[tile.id]) return;
|
||||
|
||||
if (!hasInflightRequests(_tileCache)) {
|
||||
dispatch.call('loading'); // start the spinner
|
||||
}
|
||||
|
||||
var path = '/api/0.6/map?bbox=';
|
||||
var options = { skipSeen: true };
|
||||
|
||||
_tileCache.inflight[tile.id] = this.loadFromAPI(
|
||||
path + tile.extent.toParam(),
|
||||
function(err, parsed) {
|
||||
delete _tileCache.inflight[tile.id];
|
||||
if (!err) {
|
||||
delete _tileCache.toLoad[tile.id];
|
||||
_tileCache.loaded[tile.id] = true;
|
||||
var bbox = tile.extent.bbox();
|
||||
bbox.id = tile.id;
|
||||
_tileCache.rtree.insert(bbox);
|
||||
}
|
||||
if (callback) {
|
||||
callback(err, Object.assign({ data: parsed }, tile));
|
||||
}
|
||||
if (!hasInflightRequests(_tileCache)) {
|
||||
dispatch.call('loaded'); // stop the spinner
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
@@ -828,6 +838,21 @@ export default {
|
||||
},
|
||||
|
||||
|
||||
// load the tile that covers the given `loc`
|
||||
loadTileAtLoc: function(loc, callback) {
|
||||
var k = geoZoomToScale(_tileZoom + 1);
|
||||
var offset = geoRawMercator().scale(k)(loc);
|
||||
var projection = geoRawMercator().transform({ k: k, x: -offset[0], y: -offset[1] });
|
||||
var tiles = tiler.zoomExtent([_tileZoom, _tileZoom]).getTiles(projection);
|
||||
|
||||
tiles.forEach(function(tile) {
|
||||
if (_tileCache.toLoad[tile.id]) return; // already in queue
|
||||
_tileCache.toLoad[tile.id] = true;
|
||||
this.loadTile(tile, callback);
|
||||
}, this);
|
||||
},
|
||||
|
||||
|
||||
// Load notes from the API in tiles
|
||||
// GET /api/0.6/notes?bbox=
|
||||
loadNotes: function(projection, noteOptions) {
|
||||
|
||||
@@ -590,8 +590,8 @@ describe('iD.serviceOsm', function () {
|
||||
describe('#caches', function() {
|
||||
it('loads reset caches', function (done) {
|
||||
var caches = connection.caches();
|
||||
expect(caches.tile).to.have.all.keys(['loaded','inflight','seen','rtree']);
|
||||
expect(caches.note).to.have.all.keys(['loaded','inflight','inflightPost','note','closed','rtree']);
|
||||
expect(caches.tile).to.have.all.keys(['toLoad','loaded','inflight','seen','rtree']);
|
||||
expect(caches.note).to.have.all.keys(['toLoad','loaded','inflight','inflightPost','note','closed','rtree']);
|
||||
expect(caches.user).to.have.all.keys(['toLoad','user']);
|
||||
done();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user