diff --git a/js/id/renderer/mapillary_image_layer.js b/js/id/renderer/mapillary_image_layer.js index c46d22e50..b41bfb669 100644 --- a/js/id/renderer/mapillary_image_layer.js +++ b/js/id/renderer/mapillary_image_layer.js @@ -1,8 +1,13 @@ -iD.MapillaryImageLayer = function (context) { - var mapillary = iD.services.mapillary(), +iD.MapillaryImageLayer = function(context) { + var mapillary = iD.services.mapillary() + .on('loadedImages.imageLayer', imagesLoaded), + imageData = rbush(), + urlImage = 'http://mapillary.com/map/im/', + urlThumb = 'https://d1cuyjsrcm0gby.cloudfront.net/', enable = false, currentImage, - svg, div, request; + svg, thumbnail; + function show(image) { svg.selectAll('g') @@ -10,13 +15,13 @@ iD.MapillaryImageLayer = function (context) { return currentImage && d.key === currentImage.key; }); - div.classed('hidden', false) + thumbnail.classed('hidden', false) .classed('temp', image !== currentImage); - div.selectAll('img') + thumbnail.selectAll('img') .attr('src', urlThumb + image.key + '/thumb-320.jpg'); - div.selectAll('a') + thumbnail.selectAll('a') .attr('href', urlImage + image.key); } @@ -26,7 +31,7 @@ iD.MapillaryImageLayer = function (context) { svg.selectAll('g') .classed('selected', false); - div.classed('hidden', true); + thumbnail.classed('hidden', true); } function transform(d) { @@ -35,22 +40,30 @@ iD.MapillaryImageLayer = function (context) { return t; } - function render(err, data) { - if (err) return; + function imagesLoaded(data) { + var images = [], + sequence, loc; - var images = []; for (var i = 0; i < data.features.length; i++) { - var sequence = data.features[i]; + sequence = data.features[i]; for (var j = 0; j < sequence.geometry.coordinates.length; j++) { - images.push({ + loc = sequence.geometry.coordinates[j]; + images.push([loc[0], loc[1], loc[0], loc[1], { key: sequence.properties.keys[j], ca: sequence.properties.cas[j], loc: sequence.geometry.coordinates[j] - }); - if (images.length >= 1000) break; + }]); } } + imageData.load(images); + } + + function render() { + var images = imageData + .search(context.map().extent().rectangle()) + .map(function(d) { return d[4]; }); + var g = svg.selectAll('g') .data(images, function(d) { return d.key; }); @@ -73,6 +86,7 @@ iD.MapillaryImageLayer = function (context) { .remove(); } + function layer(selection) { svg = selection.selectAll('svg') .data([0]); @@ -100,49 +114,36 @@ iD.MapillaryImageLayer = function (context) { svg.style('display', enable ? 'block' : 'none'); - div = context.container().selectAll('.mapillary-image') + thumbnail = context.container().selectAll('.mapillary-image') .data([0]); - var enter = div.enter().append('div') + var enter = thumbnail.enter().append('div') .attr('class', 'mapillary-image'); enter.append('button') .on('click', hide) .append('div') - .call(iD.svg.Icon('#icon-close')); + .attr('class', 'icon close'); enter.append('img'); - enter - .append('a') + var link = enter.append('a') .attr('class', 'link') - .attr('target', '_blank') - .call(iD.svg.Icon('#icon-out-link', 'inline')) - .append('span') + .attr('target', '_blank'); + + link.append('span') + .attr('class', 'icon icon-pre-text out-link'); + + link.append('span') .text(t('mapillary_images.view_on_mapillary')); if (!enable) { hide(); - - svg.selectAll('g') - .remove(); - - return; + svg.selectAll('g').remove(); + } else { + render(); + mapillary.loadImages(context.projection, svg.dimensions()); } - - // Update existing images while waiting for new ones to load. - svg.selectAll('g') - .attr('transform', transform); - - var extent = context.map().extent(); - - if (request) - request.abort(); - - request = d3.json(urlSearch + '?client_id=' + clientId + '&min_lat=' + - extent[0][1] + '&max_lat=' + extent[1][1] + '&min_lon=' + - extent[0][0] + '&max_lon=' + extent[1][0] + '&max_results=100&geojson=true', - ); } layer.enable = function(_) { diff --git a/js/id/services/mapillary.js b/js/id/services/mapillary.js index 5a0b572d4..c3e6ebbdc 100644 --- a/js/id/services/mapillary.js +++ b/js/id/services/mapillary.js @@ -1,18 +1,107 @@ iD.services.mapillary = function() { var mapillary = {}, - apiBase = 'https://a.mapillary.com/v2/', - urlSearch = 'search/s/geojson', + dispatch = d3.dispatch('loadedImages', 'loadedSigns', 'loadedThumbnail'), + endpoint = 'https://a.mapillary.com/v2/', urlImage = 'https://www.mapillary.com/map/im/', urlThumb = 'https://d1cuyjsrcm0gby.cloudfront.net/', - clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi'; + clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi', + tileZoom = 17; - mapillary.images = function(location, callback) { - var cache = iD.services.mapillary.cache; + function abortRequest(i) { + i.abort(); + } + + function getTiles(projection, dimensions) { + var s = projection.scale() * 2 * Math.PI, + z = Math.max(Math.log(s) / Math.log(2) - 8, 0), + ts = 256 * Math.pow(2, z - tileZoom), + origin = [ + s / 2 - projection.translate()[0], + s / 2 - projection.translate()[1]]; + + return d3.geo.tile() + .scaleExtent([tileZoom, tileZoom]) + .scale(s) + .size(dimensions) + .translate(projection.translate())() + .map(function(tile) { + var x = tile[0] * ts - origin[0], + y = tile[1] * ts - origin[1]; + + return { + id: tile.toString(), + extent: iD.geo.Extent( + projection.invert([x, y + ts]), + projection.invert([x + ts, y])) + }; + }); + } + + + function loadTiles(which, url, projection, dimensions) { + var cache = iD.services.mapillary.cache, + tiles = getTiles(projection, dimensions); + + _.filter(which.inflight, function(v, k) { + var wanted = _.find(tiles, function(tile) { return k === tile.id; }); + if (!wanted) delete which.inflight[k]; + return !wanted; + }).map(abortRequest); + + tiles.forEach(function(tile) { + var id = tile.id, + extent = tile.extent; + + if (which.loaded[id] || which.inflight[id]) return; + + which.inflight[id] = d3.json(url + + iD.util.qsString({ + geojson: 'true', + client_id: clientId, + min_lat: extent[0][1], + max_lat: extent[1][1], + min_lon: extent[0][0], + max_lon: extent[1][0] + }), function(err, data) { + which.loaded[id] = true; + delete which.inflight[id]; + if (err) return; + + if (which === cache.images) + dispatch.loadedImages(data); + else if (which === cache.signs) + dispatch.loadedSigns(data); + } + ); + }); + } + + mapillary.loadImages = function(projection, dimensions) { + var cache = iD.services.mapillary.cache, + url = endpoint + 'search/s/geojson?'; + loadTiles(cache.images, url, projection, dimensions); + }; + + mapillary.loadSigns = function(projection, dimensions) { + var cache = iD.services.mapillary.cache, + url = endpoint + 'search/im/geojson/or?'; + loadTiles(cache.signs, url, projection, dimensions); }; mapillary.reset = function() { - iD.services.mapillary.cache = rbush(); + var cache = iD.services.mapillary.cache; + + if (cache) { + _.forEach(cache.images.inflight, abortRequest); + _.forEach(cache.signs.inflight, abortRequest); + } + + iD.services.mapillary.cache = { + images: { inflight: {}, loaded: {}, rbush: rbush() }, + signs: { inflight: {}, loaded: {}, rbush: rbush() } + }; + return mapillary; }; @@ -21,5 +110,6 @@ iD.services.mapillary = function() { mapillary.reset(); } - return mapillary; + return d3.rebind(mapillary, dispatch, 'on'); }; +