Merge pull request #6245 from openstreetmap/d3-v5

d3 v5
This commit is contained in:
Bryan Housel
2019-04-29 15:46:08 -04:00
committed by GitHub
33 changed files with 1103 additions and 857 deletions
+17 -14
View File
@@ -1,7 +1,7 @@
import _debounce from 'lodash-es/debounce';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { select as d3_select } from 'd3-selection';
import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
@@ -429,16 +429,16 @@ export function coreContext() {
context.loadLocale = function(callback) {
if (locale && locale !== 'en' && dataLocales.hasOwnProperty(locale)) {
localePath = localePath || context.asset('locales/' + locale + '.json');
d3_json(localePath, function(err, result) {
if (!err) {
d3_json(localePath)
.then(function(result) {
addTranslation(locale, result[locale]);
setLocale(locale);
utilDetect(true);
}
if (callback) {
callback(err);
}
});
if (callback) callback();
})
.catch(function(err) {
if (callback) callback(err.message);
});
} else {
if (locale) {
setLocale(locale);
@@ -515,13 +515,16 @@ export function coreContext() {
if (services.maprules && utilStringQs(window.location.hash).maprules) {
var maprules = utilStringQs(window.location.hash).maprules;
d3_json(maprules, function (err, mapcss) {
if (err) return;
services.maprules.init();
mapcss.forEach(function(mapcssSelector) {
return services.maprules.addRule(mapcssSelector);
d3_json(maprules)
.then(function(mapcss) {
services.maprules.init();
mapcss.forEach(function(mapcssSelector) {
return services.maprules.addRule(mapcssSelector);
});
})
.catch(function() {
/* ignore */
});
});
}
map = rendererMap(context);
+2
View File
@@ -1,3 +1,5 @@
import 'browser-polyfills';
import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
import * as iD from './index';
window.iD = iD;
+11 -9
View File
@@ -1,5 +1,5 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { data } from '../../data/index';
import { presetCategory } from './category';
@@ -251,15 +251,17 @@ export function presetIndex(context) {
all.fromExternal = function(external, done) {
all.reset();
d3_json(external, function(err, externalPresets) {
if (err) {
d3_json(external)
.then(function(externalPresets) {
all.build(data.presets, false); // make default presets hidden to begin
all.build(externalPresets, true); // make the external visible
})
.catch(function() {
all.init();
} else {
all.build(data.presets, false); // make default presets hidden to begin
all.build(externalPresets, true); // make the external visible
}
done(all);
});
})
.finally(function() {
done(all);
});
};
all.field = function(id) {
+83 -85
View File
@@ -1,9 +1,5 @@
import {
geoArea as d3_geoArea,
geoMercatorRaw as d3_geoMercatorRaw
} from 'd3-geo';
import { json as d3_json } from 'd3-request';
import { geoArea as d3_geoArea, geoMercatorRaw as d3_geoMercatorRaw } from 'd3-geo';
import { json as d3_json } from 'd3-fetch';
import { t } from '../util/locale';
import { geoExtent, geoSphericalDistance } from '../geo';
@@ -218,27 +214,29 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
var bing = rendererBackgroundSource(data);
var key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU'; // Same as P2 and JOSM
var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' +
key;
var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key;
var cache = {};
var inflight = {};
var providers = [];
d3_json(url, function(err, json) {
if (err) return;
providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) {
return {
attribution: provider.attribution,
areas: provider.coverageAreas.map(function(area) {
return {
zoom: [area.zoomMin, area.zoomMax],
extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]])
};
})
};
d3_json(url)
.then(function(json) {
providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) {
return {
attribution: provider.attribution,
areas: provider.coverageAreas.map(function(area) {
return {
zoom: [area.zoomMin, area.zoomMax],
extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]])
};
})
};
});
dispatch.call('change');
})
.catch(function() {
/* ignore */
});
dispatch.call('change');
});
bing.copyrightNotices = function(zoom, extent) {
@@ -256,34 +254,28 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
bing.getMetadata = function(center, tileCoord, callback) {
var tileId = tileCoord.slice(0, 3).join('/');
var tileID = tileCoord.slice(0, 3).join('/');
var zoom = Math.min(tileCoord[2], 21);
var centerPoint = center[1] + ',' + center[0]; // lat,lng
var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint +
'?zl=' + zoom + '&key=' + key;
if (inflight[tileId]) return;
if (inflight[tileID]) return;
if (!cache[tileId]) {
cache[tileId] = {};
if (!cache[tileID]) {
cache[tileID] = {};
}
if (cache[tileId] && cache[tileId].metadata) {
return callback(null, cache[tileId].metadata);
if (cache[tileID] && cache[tileID].metadata) {
return callback(null, cache[tileID].metadata);
}
inflight[tileId] = true;
d3_json(url, function(error, result) {
delete inflight[tileId];
var err;
if (error) {
err = error;
} else if (!result && 'Unknown Error') {
err = result.errorDetails;
}
if (err) {
return callback(err);
} else {
inflight[tileID] = true;
d3_json(url)
.then(function(result) {
delete inflight[tileID];
if (!result) {
throw new Error('Unknown Error');
}
var vintage = {
start: localeDateString(result.resourceSets[0].resources[0].vintageStart),
end: localeDateString(result.resourceSets[0].resources[0].vintageEnd)
@@ -291,10 +283,13 @@ rendererBackgroundSource.Bing = function(data, dispatch) {
vintage.range = vintageRange(vintage);
var metadata = { vintage: vintage };
cache[tileId].metadata = metadata;
return callback(null, metadata);
}
});
cache[tileID].metadata = metadata;
if (callback) callback(null, metadata);
})
.catch(function(err) {
delete inflight[tileID];
if (callback) callback(err.message);
});
};
@@ -338,25 +333,31 @@ rendererBackgroundSource.Esri = function(data) {
var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+\?blankTile=false/, 'tilemap') + '/' + z + '/' + y + '/' + x + '/8/8';
// make the request and introspect the response from the tilemap server
d3_json(tilemapUrl, function (err, tilemap) {
if (err || !tilemap) return;
var hasTiles = true;
for (var i = 0; i < tilemap.data.length; i++) {
// 0 means an individual tile in the grid doesn't exist
if (!tilemap.data[i]) {
hasTiles = false;
break;
d3_json(tilemapUrl)
.then(function(tilemap) {
if (!tilemap) {
throw new Error('Unknown Error');
}
var hasTiles = true;
for (var i = 0; i < tilemap.data.length; i++) {
// 0 means an individual tile in the grid doesn't exist
if (!tilemap.data[i]) {
hasTiles = false;
break;
}
}
}
// if any tiles are missing at level 20 we restrict maxZoom to 19
esri.zoomExtent[1] = (hasTiles ? 22 : 19);
});
// if any tiles are missing at level 20 we restrict maxZoom to 19
esri.zoomExtent[1] = (hasTiles ? 22 : 19);
})
.catch(function() {
/* ignore */
});
};
esri.getMetadata = function(center, tileCoord, callback) {
var tileId = tileCoord.slice(0, 3).join('/');
var tileID = tileCoord.slice(0, 3).join('/');
var zoom = Math.min(tileCoord[2], esri.zoomExtent[1]);
var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be)
var unknown = t('info_panels.background.unknown');
@@ -364,7 +365,7 @@ rendererBackgroundSource.Esri = function(data) {
var vintage = {};
var metadata = {};
if (inflight[tileId]) return;
if (inflight[tileID]) return;
switch (true) {
case (zoom >= 20 && esri.id === 'EsriWorldImageryClarity'):
@@ -393,11 +394,11 @@ rendererBackgroundSource.Esri = function(data) {
url += metadataLayer + '/query?returnGeometry=false&geometry=' + centerPoint + '&inSR=4326&geometryType=esriGeometryPoint&outFields=*&f=json';
if (!cache[tileId]) {
cache[tileId] = {};
if (!cache[tileID]) {
cache[tileID] = {};
}
if (cache[tileId] && cache[tileId].metadata) {
return callback(null, cache[tileId].metadata);
if (cache[tileID] && cache[tileID].metadata) {
return callback(null, cache[tileID].metadata);
}
// accurate metadata is only available >= 13
@@ -418,24 +419,18 @@ rendererBackgroundSource.Esri = function(data) {
callback(null, metadata);
} else {
inflight[tileId] = true;
d3_json(url, function(error, result) {
delete inflight[tileId];
inflight[tileID] = true;
d3_json(url)
.then(function(result) {
delete inflight[tileID];
if (!result) {
throw new Error('Unknown Error');
} else if (result.features && result.features.length < 1) {
throw new Error('No Results');
} else if (result.error && result.error.message) {
throw new Error(result.error.message);
}
var err;
if (error) {
err = error;
} else if (!result) {
err = 'Unknown Error';
} else if (result.features && result.features.length < 1) {
err = 'No Results';
} else if (result.error && result.error.message) {
err = result.error.message;
}
if (err) {
return callback(err);
} else {
// pass through the discrete capture date from metadata
var captureDate = localeDateString(result.features[0].attributes.SRC_DATE2);
vintage = {
@@ -459,10 +454,13 @@ rendererBackgroundSource.Esri = function(data) {
metadata.accuracy += ' m';
}
cache[tileId].metadata = metadata;
return callback(null, metadata);
}
});
cache[tileID].metadata = metadata;
if (callback) callback(null, metadata);
})
.catch(function(err) {
delete inflight[tileID];
if (callback) callback(err.message);
});
}
+53 -32
View File
@@ -1,8 +1,7 @@
import rbush from 'rbush';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { geoExtent, geoVecAdd, geoVecScale } from '../geo';
import { qaError } from '../osm';
@@ -24,9 +23,9 @@ var _impOsmUrls = {
};
function abortRequest(i) {
Object.values(i).forEach(function(v) {
if (v) {
v.abort();
Object.values(i).forEach(function(controller) {
if (controller) {
controller.abort();
}
});
}
@@ -177,12 +176,16 @@ export default {
);
var url = v + '/search?' + utilQsString(kParams);
requests[k] = d3_json(url,
function(err, data) {
delete _erCache.inflightTile[tile.id];
var controller = new AbortController();
requests[k] = controller;
if (err) return;
_erCache.loadedTile[tile.id] = true;
d3_json(url, { signal: controller.signal })
.then(function(data) {
delete _erCache.inflightTile[tile.id][k];
if (!Object.keys(_erCache.inflightTile[tile.id]).length) {
delete _erCache.inflightTile[tile.id];
_erCache.loadedTile[tile.id] = true;
}
// Road segments at high zoom == oneways
if (data.roadSegments) {
@@ -317,20 +320,29 @@ export default {
_erCache.data[d.id] = d;
_erCache.rtree.insert(encodeErrorRtree(d));
dispatch.call('loaded');
});
}
}
);
})
.catch(function() {
delete _erCache.inflightTile[tile.id][k];
if (!Object.keys(_erCache.inflightTile[tile.id]).length) {
delete _erCache.inflightTile[tile.id];
_erCache.loadedTile[tile.id] = true;
}
});
});
_erCache.inflightTile[tile.id] = requests;
dispatch.call('loaded');
});
},
getComments: function(d, callback) {
// If comments already retrieved no need to do so again
if (d.comments !== undefined) { return callback({}, d); }
if (d.comments !== undefined) {
if (callback) callback({}, d);
return;
}
var key = d.error_key;
var qParams = {};
@@ -347,15 +359,16 @@ export default {
var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams);
var that = this;
d3_json(url, function(err, data) {
// comments are served newest to oldest
var comments = data.comments ? data.comments.reverse() : [];
that.replaceError(d.update({
comments: comments
}));
return callback(err, d);
});
d3_json(url)
.then(function(data) {
// comments are served newest to oldest
var comments = data.comments ? data.comments.reverse() : [];
that.replaceError(d.update({ comments: comments }));
if (callback) callback(null, d);
})
.catch(function(err) {
if (callback) callback(err.message);
});
},
postUpdate: function(d, callback) {
@@ -399,13 +412,18 @@ export default {
payload.text = d.newComment;
}
_erCache.inflightPost[d.id] = d3_request(url)
.header('Content-Type', 'application/json')
.post(JSON.stringify(payload), function(err) {
delete _erCache.inflightPost[d.id];
var controller = new AbortController();
_erCache.inflightPost[d.id] = controller;
// Unsuccessful response status, keep issue open
if (err.status !== 200) { return callback(err, d); }
var options = {
method: 'POST',
signal: controller.signal,
body: JSON.stringify(payload)
};
d3_json(url, options)
.then(function() {
delete _erCache.inflightPost[d.id];
// Just a comment, update error in cache
if (d.newStatus === undefined) {
@@ -424,19 +442,22 @@ export default {
}));
} else {
that.removeError(d);
if (d.newStatus === 'SOLVED') {
// No pretty identifier, so we just use coordinates
var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5);
_erCache.closed[key + ':' + closedID] = true;
}
}
return callback(err, d);
if (callback) callback(null, d);
})
.catch(function(err) {
delete _erCache.inflightPost[d.id];
if (callback) callback(err.message);
});
}
},
// get all cached errors covering the viewport
getErrors: function(projection) {
var viewport = projection.clipExtent();
+32 -17
View File
@@ -1,8 +1,7 @@
import rbush from 'rbush';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { json as d3_json } from 'd3-request';
import { request as d3_request } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { geoExtent, geoVecAdd } from '../geo';
import { qaError } from '../osm';
@@ -30,9 +29,9 @@ var _krRuleset = [
];
function abortRequest(i) {
if (i) {
i.abort();
function abortRequest(controller) {
if (controller) {
controller.abort();
}
}
@@ -308,14 +307,16 @@ export default {
var params = Object.assign({}, options, { left: rect[0], bottom: rect[3], right: rect[2], top: rect[1] });
var url = _krUrlRoot + 'export.php?' + utilQsString(params) + '&ch=' + rules;
_krCache.inflightTile[tile.id] = d3_json(url,
function(err, data) {
var controller = new AbortController();
_krCache.inflightTile[tile.id] = controller;
d3_json(url, { signal: controller.signal })
.then(function(data) {
delete _krCache.inflightTile[tile.id];
if (err) return;
_krCache.loadedTile[tile.id] = true;
if (!data.features || !data.features.length) return;
if (!data || !data.features || !data.features.length) {
throw new Error('No Data');
}
data.features.forEach(function(feature) {
var loc = feature.geometry.coordinates;
@@ -396,8 +397,12 @@ export default {
});
dispatch.call('loaded');
}
);
})
.catch(function() {
delete _krCache.inflightTile[tile.id];
_krCache.loadedTile[tile.id] = true;
});
});
},
@@ -420,9 +425,16 @@ export default {
// NOTE: This throws a CORS err, but it seems successful.
// We don't care too much about the response, so this is fine.
var url = _krUrlRoot + 'comment.php?' + utilQsString(params);
_krCache.inflightPost[d.id] = d3_request(url)
.post(function(err) {
var controller = new AbortController();
_krCache.inflightPost[d.id] = controller;
fetch(url, { method: 'POST', signal: controller.signal })
.then(function(response) {
delete _krCache.inflightPost[d.id];
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
if (d.state === 'ignore') { // ignore permanently (false positive)
that.removeError(d);
@@ -439,9 +451,12 @@ export default {
}));
}
return callback(err, d);
if (callback) callback(null, d);
})
.catch(function(err) {
delete _krCache.inflightPost[d.id];
if (callback) callback(err.message);
});
},
+36 -22
View File
@@ -1,10 +1,6 @@
/* global Mapillary:false */
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import {
select as d3_select,
selectAll as d3_selectAll
} from 'd3-selection';
import { select as d3_select, selectAll as d3_selectAll } from 'd3-selection';
import rbush from 'rbush';
@@ -28,8 +24,8 @@ var _mlySelectedImage;
var _mlyViewer;
function abortRequest(i) {
i.abort();
function abortRequest(controller) {
controller.abort();
}
@@ -80,22 +76,36 @@ function loadNextTilePage(which, currZoom, url, tile) {
var id = tile.id + ',' + String(nextPage);
if (cache.loaded[id] || cache.inflight[id]) return;
cache.inflight[id] = d3_request(nextURL)
.mimeType('application/json')
.response(function(xhr) {
var linkHeader = xhr.getResponseHeader('Link');
var controller = new AbortController();
cache.inflight[id] = controller;
var options = {
method: 'GET',
signal: controller.signal,
headers: { 'Content-Type': 'application/json' }
};
fetch(nextURL, options)
.then(function(response) {
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
var linkHeader = response.headers.Link;
if (linkHeader) {
var pagination = parsePagination(xhr.getResponseHeader('Link'));
var pagination = parsePagination(linkHeader);
if (pagination.next) {
cache.nextURL[tile.id] = pagination.next;
}
}
return JSON.parse(xhr.responseText);
return response.json();
})
.get(function(err, data) {
.then(function(data) {
cache.loaded[id] = true;
delete cache.inflight[id];
if (err || !data.features || !data.features.length) return;
if (!data || !data.features || !data.features.length) {
throw new Error('No Data');
}
var features = data.features.map(function(feature) {
var loc = feature.geometry.coordinates;
@@ -170,18 +180,22 @@ function loadNextTilePage(which, currZoom, url, tile) {
cache.rtree.load(features);
}
if (which === 'images' || which === 'sequences') {
dispatch.call('loadedImages');
} else if (which === 'map_features') {
dispatch.call('loadedSigns');
}
if (data.features.length === maxResults) { // more pages to load
cache.nextPage[tile.id] = nextPage + 1;
loadNextTilePage(which, currZoom, url, tile);
} else {
cache.nextPage[tile.id] = Infinity; // no more pages to load
}
if (which === 'images' || which === 'sequences') {
dispatch.call('loadedImages');
} else if (which === 'map_features') {
dispatch.call('loadedSigns');
}
})
.catch(function() {
cache.loaded[id] = true;
delete cache.inflight[id];
});
}
@@ -312,7 +326,7 @@ export default {
},
loadSigns: function(context, projection) {
loadSigns: function(projection) {
// if we are looking at signs, we'll actually need to fetch images too
loadTiles('images', apibase + 'images?', projection);
loadTiles('map_features', apibase + 'map_features?layers=trafficsigns&min_nbr_image_detections=1&', projection);
+42 -25
View File
@@ -1,4 +1,4 @@
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import rbush from 'rbush';
import { geoExtent } from '../geo';
@@ -18,7 +18,7 @@ export default {
},
reset: function() {
Object.values(_inflight).forEach(function(req) { req.abort(); });
Object.values(_inflight).forEach(function(controller) { controller.abort(); });
_inflight = {};
_nominatimCache = rbush();
},
@@ -37,45 +37,62 @@ export default {
},
reverse: function (location, callback) {
reverse: function (loc, callback) {
var cached = _nominatimCache.search(
{ minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] }
{ minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1] }
);
if (cached.length > 0) {
return callback(null, cached[0].data);
if (callback) callback(null, cached[0].data);
return;
}
var params = { zoom: 13, format: 'json', addressdetails: 1, lat: location[1], lon: location[0] };
var params = { zoom: 13, format: 'json', addressdetails: 1, lat: loc[1], lon: loc[0] };
var url = apibase + 'reverse?' + utilQsString(params);
if (_inflight[url]) return;
var controller = new AbortController();
_inflight[url] = controller;
_inflight[url] = d3_json(url, function(err, result) {
delete _inflight[url];
if (err) {
return callback(err);
} else if (result && result.error) {
return callback(result.error);
}
var extent = geoExtent(location).padByMeters(200);
_nominatimCache.insert(Object.assign(extent.bbox(), {data: result}));
callback(null, result);
});
d3_json(url, { signal: controller.signal })
.then(function(result) {
delete _inflight[url];
if (result && result.error) {
throw new Error(result.error);
}
var extent = geoExtent(loc).padByMeters(200);
_nominatimCache.insert(Object.assign(extent.bbox(), {data: result}));
if (callback) callback(null, result);
})
.catch(function(err) {
delete _inflight[url];
if (err.name === 'AbortError') return;
if (callback) callback(err.message);
});
},
search: function (val, callback) {
var searchVal = encodeURIComponent(val);
var url = apibase + 'search/' + searchVal + '?limit=10&format=json';
if (_inflight[url]) return;
_inflight[url] = d3_json(url, function(err, result) {
delete _inflight[url];
callback(err, result);
});
if (_inflight[url]) return;
var controller = new AbortController();
_inflight[url] = controller;
d3_json(url, { signal: controller.signal })
.then(function(result) {
delete _inflight[url];
if (result && result.error) {
throw new Error(result.error);
}
if (callback) callback(null, result);
})
.catch(function(err) {
delete _inflight[url];
if (err.name === 'AbortError') return;
if (callback) callback(err.message);
});
}
};
+40 -34
View File
@@ -1,16 +1,7 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import {
event as d3_event,
select as d3_select,
selectAll as d3_selectAll
} from 'd3-selection';
import {
zoom as d3_zoom,
zoomIdentity as d3_zoomIdentity
} from 'd3-zoom';
import { json as d3_json } from 'd3-fetch';
import { event as d3_event, select as d3_select, selectAll as d3_selectAll } from 'd3-selection';
import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom';
import rbush from 'rbush';
@@ -33,8 +24,8 @@ var _oscCache;
var _oscSelectedImage;
function abortRequest(i) {
i.abort();
function abortRequest(controller) {
controller.abort();
}
@@ -86,22 +77,22 @@ function loadNextTilePage(which, currZoom, url, tile) {
var id = tile.id + ',' + String(nextPage);
if (cache.loaded[id] || cache.inflight[id]) return;
cache.inflight[id] = d3_request(url)
.mimeType('application/json')
.header('Content-type', 'application/x-www-form-urlencoded')
.response(function(xhr) { return JSON.parse(xhr.responseText); })
.post(params, function(err, data) {
var controller = new AbortController();
cache.inflight[id] = controller;
var options = {
method: 'POST',
signal: controller.signal,
body: params,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
};
d3_json(url, options)
.then(function(data) {
cache.loaded[id] = true;
delete cache.inflight[id];
if (err || !data.currentPageItems || !data.currentPageItems.length) return;
function localeDateString(s) {
if (!s) return null;
var detected = utilDetect();
var options = { day: 'numeric', month: 'short', year: 'numeric' };
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString(detected.locale, options);
if (!data || !data.currentPageItems || !data.currentPageItems.length) {
throw new Error('No Data');
}
var features = data.currentPageItems.map(function(item) {
@@ -113,7 +104,7 @@ function loadNextTilePage(which, currZoom, url, tile) {
loc: loc,
key: item.id,
ca: +item.heading,
captured_at: localeDateString(item.shot_date || item.date_added),
captured_at: (item.shot_date || item.date_added),
captured_by: item.username,
imagePath: item.lth_name,
sequence_id: item.sequence_id,
@@ -136,16 +127,20 @@ function loadNextTilePage(which, currZoom, url, tile) {
cache.rtree.load(features);
if (which === 'images') {
dispatch.call('loadedImages');
}
if (data.currentPageItems.length === maxResults) { // more pages to load
cache.nextPage[tile.id] = nextPage + 1;
loadNextTilePage(which, currZoom, url, tile);
} else {
cache.nextPage[tile.id] = Infinity; // no more pages to load
}
if (which === 'images') {
dispatch.call('loadedImages');
}
})
.catch(function() {
cache.loaded[id] = true;
delete cache.inflight[id];
});
}
@@ -435,7 +430,7 @@ export default {
attribution
.append('span')
.attr('class', 'captured_at')
.text(d.captured_at);
.text(localeDateString(d.captured_at));
attribution
.append('span')
@@ -449,7 +444,18 @@ export default {
.attr('href', 'https://openstreetcam.org/details/' + d.sequence_id + '/' + d.sequence_index)
.text('openstreetcam.org');
}
return this;
function localeDateString(s) {
if (!s) return null;
var detected = utilDetect();
var options = { day: 'numeric', month: 'short', year: 'numeric' };
var d = new Date(s);
if (isNaN(d.getTime())) return null;
return d.toLocaleDateString(detected.locale, options);
}
},
+30 -10
View File
@@ -1,7 +1,7 @@
import _throttle from 'lodash-es/throttle';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { xml as d3_xml } from 'd3-request';
import { xml as d3_xml } from 'd3-fetch';
import osmAuth from 'osm-auth';
import rbush from 'rbush';
@@ -51,9 +51,9 @@ function authDone() {
}
function abortRequest(i) {
if (i) {
i.abort();
function abortRequest(controllerOrXHR) {
if (controllerOrXHR) {
controllerOrXHR.abort();
}
}
@@ -440,7 +440,8 @@ export default {
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
// Logout and retry the request..
if (isAuthenticated && err && (err.status === 400 || err.status === 401 || err.status === 403)) {
if (isAuthenticated && err && err.status &&
(err.status === 400 || err.status === 401 || err.status === 403)) {
that.logout();
that.loadFromAPI(path, callback, options);
@@ -448,7 +449,7 @@ export default {
} else {
// 509 Bandwidth Limit Exceeded, 429 Too Many Requests
// Set the rateLimitError flag and trigger a warning..
if (!isAuthenticated && !_rateLimitError && err &&
if (!isAuthenticated && !_rateLimitError && err && err.status &&
(err.status === 509 || err.status === 429)) {
_rateLimitError = err;
dispatch.call('change');
@@ -468,7 +469,24 @@ export default {
return oauth.xhr({ method: 'GET', path: path }, done);
} else {
var url = urlroot + path;
return d3_xml(url).get(done);
var controller = new AbortController();
d3_xml(url, { signal: controller.signal })
.then(function(data) {
done(null, data);
})
.catch(function(err) {
if (err.name === 'AbortError') return;
// d3-fetch includes status in the error message,
// but we can't access the response itself
// https://github.com/d3/d3-fetch/issues/27
var match = err.message.match(/^\d{3}/);
if (match) {
done({ status: +match[0], statusText: err.message });
} else {
done(err.message);
}
});
return controller;
}
},
@@ -743,9 +761,11 @@ export default {
// Fetch the status of the OSM API
// GET /api/capabilities
status: function(callback) {
d3_xml(urlroot + '/api/capabilities').get(
wrapcb(this, done, _connectionID)
);
var url = urlroot + '/api/capabilities';
var errback = wrapcb(this, done, _connectionID);
d3_xml(url)
.then(function(data) { errback(null, data); })
.catch(function(err) { errback(err.message); });
function done(err, xml) {
if (err) { return callback(err); }
+14 -6
View File
@@ -1,6 +1,6 @@
import _debounce from 'lodash-es/debounce';
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { utilDetect } from '../util/detect';
import { utilQsString } from '../util';
@@ -16,11 +16,19 @@ var debouncedRequest = _debounce(request, 500, { leading: false });
function request(url, callback) {
if (_inflight[url]) return;
var controller = new AbortController();
_inflight[url] = controller;
_inflight[url] = d3_json(url, function (err, data) {
delete _inflight[url];
callback(err, data);
});
d3_json(url, { signal: controller.signal })
.then(function(result) {
delete _inflight[url];
if (callback) callback(null, result);
})
.catch(function(err) {
delete _inflight[url];
if (err.name === 'AbortError') return;
if (callback) callback(err.message);
});
}
@@ -50,7 +58,7 @@ export default {
reset: function() {
Object.values(_inflight).forEach(function(req) { req.abort(); });
Object.values(_inflight).forEach(function(controller) { controller.abort(); });
_inflight = {};
},
+15 -6
View File
@@ -1,6 +1,6 @@
import _debounce from 'lodash-es/debounce';
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { utilObjectOmit, utilQsString } from '../util';
import { currentLocale } from '../util/locale';
@@ -142,10 +142,19 @@ function request(url, params, exactMatch, callback, loaded) {
if (checkCache(url, params, exactMatch, callback)) return;
_inflight[url] = d3_json(url, function (err, data) {
delete _inflight[url];
loaded(err, data);
});
var controller = new AbortController();
_inflight[url] = controller;
d3_json(url, { signal: controller.signal })
.then(function(result) {
delete _inflight[url];
if (loaded) loaded(null, result);
})
.catch(function(err) {
delete _inflight[url];
if (err.name === 'AbortError') return;
if (loaded) loaded(err.message);
});
}
@@ -207,7 +216,7 @@ export default {
reset: function() {
Object.values(_inflight).forEach(function(request) { request.abort(); });
Object.values(_inflight).forEach(function(controller) { controller.abort(); });
_inflight = {};
},
+21 -7
View File
@@ -1,5 +1,4 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { request as d3_request } from 'd3-request';
import deepEqual from 'fast-deep-equal';
import turf_bboxClip from '@turf/bbox-clip';
@@ -17,8 +16,8 @@ var dispatch = d3_dispatch('loadedData');
var _vtCache;
function abortRequest(i) {
i.abort();
function abortRequest(controller) {
controller.abort();
}
@@ -105,12 +104,23 @@ function loadTile(source, tile) {
return subdomains[(tile.xyz[0] + tile.xyz[1]) % subdomains.length];
});
source.inflight[tile.id] = d3_request(url)
.responseType('arraybuffer')
.get(function(err, data) {
var controller = new AbortController();
source.inflight[tile.id] = controller;
fetch(url, { signal: controller.signal })
.then(function(response) {
if (!response.ok) {
throw new Error(response.status + ' ' + response.statusText);
}
source.loaded[tile.id] = [];
delete source.inflight[tile.id];
if (err || !data) return;
return response.arrayBuffer();
})
.then(function(data) {
if (!data) {
throw new Error('No Data');
}
var z = tile.xyz[2];
if (!source.canMerge[z]) {
@@ -119,6 +129,10 @@ function loadTile(source, tile) {
source.loaded[tile.id] = vtToGeoJSON(data, tile, source.canMerge[z]);
dispatch.call('loadedData');
})
.catch(function() {
source.loaded[tile.id] = [];
delete source.inflight[tile.id];
});
}
+44 -42
View File
@@ -1,4 +1,4 @@
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { utilArrayUniq, utilQsString } from '../util';
import { currentLocale } from '../util/locale';
@@ -19,11 +19,11 @@ export default {
// Search for Wikidata items matching the query
itemsForSearchQuery: function(query, callback) {
if (!query) {
callback('No query', {});
if (callback) callback('No query', {});
return;
}
d3_json(apibase + utilQsString({
var url = apibase + utilQsString({
action: 'wbsearchentities',
format: 'json',
formatversion: 2,
@@ -32,30 +32,31 @@ export default {
language: this.languagesToQuery()[0],
limit: 10,
origin: '*'
}), function(err, data) {
if (data && data.error) {
err = data.error;
}
if (err) {
callback(err, {});
} else {
callback(null, data.search || {});
}
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
}
if (callback) callback(null, result.search || {});
})
.catch(function(err) {
if (callback) callback(err.message, {});
});
},
// Given a Wikipedia language and article title, return an array of
// corresponding Wikidata entities.
// Given a Wikipedia language and article title,
// return an array of corresponding Wikidata entities.
itemsByTitle: function(lang, title, callback) {
if (!title) {
callback('No title', {});
if (callback) callback('No title', {});
return;
}
lang = lang || 'en';
d3_json(apibase + utilQsString({
var url = apibase + utilQsString({
action: 'wbgetentities',
format: 'json',
formatversion: 2,
@@ -63,18 +64,21 @@ export default {
titles: title,
languages: 'en', // shrink response by filtering to one language
origin: '*'
}), function(err, data) {
if (data && data.error) {
err = data.error;
}
if (err) {
callback(err, {});
} else {
callback(null, data.entities || {});
}
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
}
if (callback) callback(null, result.entities || {});
})
.catch(function(err) {
if (callback) callback(err.message, {});
});
},
languagesToQuery: function() {
return utilArrayUniq([
currentLocale.toLowerCase(),
@@ -83,19 +87,19 @@ export default {
]);
},
entityByQID: function(qid, callback) {
if (!qid) {
callback('No qid', {});
return;
}
if (_wikidataCache[qid]) {
callback(null, _wikidataCache[qid]);
if (callback) callback(null, _wikidataCache[qid]);
return;
}
var langs = this.languagesToQuery();
d3_json(apibase + utilQsString({
var url = apibase + utilQsString({
action: 'wbgetentities',
format: 'json',
formatversion: 2,
@@ -105,17 +109,18 @@ export default {
languages: langs.join('|'),
languagefallback: 1,
origin: '*'
}), function(err, data) {
if (data && data.error) {
err = data.error;
}
if (err) {
callback(err, {});
} else {
_wikidataCache[qid] = data.entities[qid];
callback(null, data.entities[qid] || {});
}
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
}
if (callback) callback(null, result.entities[qid] || {});
})
.catch(function(err) {
if (callback) callback(err.message, {});
});
},
@@ -134,9 +139,7 @@ export default {
// }
//
getDocs: function(params, callback) {
var langs = this.languagesToQuery();
this.entityByQID(params.qid, function(err, entity) {
if (err || !entity) {
callback(err || 'No entity');
@@ -144,7 +147,6 @@ export default {
}
var i;
var description;
if (entity.descriptions && Object.keys(entity.descriptions).length > 0) {
description = entity.descriptions[Object.keys(entity.descriptions)[0]].value;
+56 -36
View File
@@ -1,4 +1,4 @@
import { json as d3_json } from 'd3-request';
import { json as d3_json } from 'd3-fetch';
import { utilQsString } from '../util';
@@ -13,12 +13,12 @@ export default {
search: function(lang, query, callback) {
if (!query) {
callback('', []);
if (callback) callback('No Query', []);
return;
}
lang = lang || 'en';
d3_json(endpoint.replace('en', lang) +
var url = endpoint.replace('en', lang) +
utilQsString({
action: 'query',
list: 'search',
@@ -27,26 +27,34 @@ export default {
format: 'json',
origin: '*',
srsearch: query
}), function(err, data) {
if (err || !data || !data.query || !data.query.search || data.error) {
callback('', []);
} else {
var results = data.query.search.map(function(d) { return d.title; });
callback(query, results);
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
} else if (!result || !result.query || !result.query.search) {
throw new Error('No Results');
}
}
);
if (callback) {
var titles = result.query.search.map(function(d) { return d.title; });
callback(null, titles);
}
})
.catch(function(err) {
if (callback) callback(err, []);
});
},
suggestions: function(lang, query, callback) {
if (!query) {
callback('', []);
if (callback) callback('', []);
return;
}
lang = lang || 'en';
d3_json(endpoint.replace('en', lang) +
var url = endpoint.replace('en', lang) +
utilQsString({
action: 'opensearch',
namespace: 0,
@@ -54,24 +62,30 @@ export default {
format: 'json',
origin: '*',
search: query
}), function(err, data) {
if (err || !data || data.error) {
callback('', []);
} else {
callback(data[0], data[1] || []);
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
} else if (!result || result.length < 2) {
throw new Error('No Results');
}
}
);
if (callback) callback(null, result[1] || []);
})
.catch(function(err) {
if (callback) callback(err.message, []);
});
},
translations: function(lang, title, callback) {
if (!title) {
callback({});
if (callback) callback('No Title');
return;
}
d3_json(endpoint.replace('en', lang) +
var url = endpoint.replace('en', lang) +
utilQsString({
action: 'query',
prop: 'langlinks',
@@ -79,21 +93,27 @@ export default {
origin: '*',
lllimit: 500,
titles: title
}), function(err, data) {
if (err || !data || !data.query || !data.query.pages || data.error) {
callback({});
} else {
var list = data.query.pages[Object.keys(data.query.pages)[0]],
translations = {};
if (list && list.langlinks) {
list.langlinks.forEach(function(d) {
translations[d.lang] = d['*'];
});
}
callback(translations);
});
d3_json(url)
.then(function(result) {
if (result && result.error) {
throw new Error(result.error);
} else if (!result || !result.query || !result.query.pages) {
throw new Error('No Results');
}
}
);
if (callback) {
var list = result.query.pages[Object.keys(result.query.pages)[0]];
var translations = {};
if (list && list.langlinks) {
list.langlinks.forEach(function(d) { translations[d.lang] = d['*']; });
}
callback(null, translations);
}
})
.catch(function(err) {
if (callback) callback(err.message);
});
}
};
+11 -15
View File
@@ -1,16 +1,8 @@
import _throttle from 'lodash-es/throttle';
import {
geoBounds as d3_geoBounds,
geoPath as d3_geoPath
} from 'd3-geo';
import { text as d3_text } from 'd3-request';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { geoBounds as d3_geoBounds, geoPath as d3_geoPath } from 'd3-geo';
import { text as d3_text } from 'd3-fetch';
import { event as d3_event, select as d3_select } from 'd3-selection';
import stringify from 'fast-json-stable-stringify';
import toGeoJSON from '@mapbox/togeojson';
@@ -480,10 +472,14 @@ export function svgData(projection, context, dispatch) {
var extension = getExtension(testUrl) || defaultExtension;
if (extension) {
_template = null;
d3_text(url, function(err, data) {
if (err) return;
drawData.setFile(extension, data);
});
d3_text(url)
.then(function(data) {
drawData.setFile(extension, data);
})
.catch(function() {
/* ignore */
});
} else {
drawData.template(url);
}
+7 -6
View File
@@ -1,4 +1,4 @@
import { request as d3_request } from 'd3-request';
import { svg as d3_svg } from 'd3-fetch';
import { select as d3_select } from 'd3-selection';
import { utilArrayUniq } from '../util';
@@ -191,11 +191,9 @@ export function svgDefs(context) {
.each(function(d) {
var url = context.imagePath(d + '.svg');
var node = d3_select(this).node();
d3_request(url)
.mimeType('image/svg+xml')
.response(function(xhr) { return xhr.responseXML; })
.get(function(err, svg) {
if (err) return;
d3_svg(url)
.then(function(svg) {
node.appendChild(
d3_select(svg.documentElement).attr('id', d).node()
);
@@ -203,6 +201,9 @@ export function svgDefs(context) {
d3_select(node).selectAll('path')
.attr('fill', 'currentColor');
}
})
.catch(function() {
/* ignore */
});
});
};
+1 -1
View File
@@ -142,7 +142,7 @@ export function svgMapillarySigns(projection, context, dispatch) {
if (service && ~~context.map().zoom() >= minZoom) {
editOn();
update();
service.loadSigns(context, projection);
service.loadSigns(projection);
} else {
editOff();
}
+6 -13
View File
@@ -1,12 +1,5 @@
import {
dispatch as d3_dispatch
} from 'd3-dispatch';
import {
event as d3_event,
select as d3_select
} from 'd3-selection';
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { event as d3_event, select as d3_select } from 'd3-selection';
import { utilGetSetValue, utilRebind, utilTriggerEvent } from '../util';
@@ -107,7 +100,7 @@ export function uiCombobox(context, klass) {
var tOrig = _tDown;
window.setTimeout(function() {
if (tOrig !== _tDown) return; // exit if user double clicked
fetch('', function() {
fetchComboData('', function() {
show();
render();
});
@@ -120,7 +113,7 @@ export function uiCombobox(context, klass) {
function focus() {
fetch(''); // prefetch values (may warm taginfo cache)
fetchComboData(''); // prefetch values (may warm taginfo cache)
}
@@ -225,7 +218,7 @@ export function uiCombobox(context, klass) {
// Called whenever the input value is changed (e.g. on typing)
function change() {
fetch(value(), function() {
fetchComboData(value(), function() {
_selected = null;
var val = input.property('value');
@@ -310,7 +303,7 @@ export function uiCombobox(context, klass) {
}
function fetch(v, cb) {
function fetchComboData(v, cb) {
_cancelFetch = false;
_fetcher.call(input, v, function(results) {
+4 -1
View File
@@ -480,7 +480,10 @@ export function uiFieldLocalized(field, context) {
_wikiTitles = {};
var wm = tags.wikipedia.match(/([^:]+):(.+)/);
if (wm && wm[0] && wm[1]) {
wikipedia.translations(wm[1], wm[2], function(d) { _wikiTitles = d; });
wikipedia.translations(wm[1], wm[2], function(err, d) {
if (err || !d) return;
_wikiTitles = d;
});
}
}