diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 2c7bedf3c..1bc9a3f9d 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -25,24 +25,25 @@ import rbush from 'rbush'; import { d3geoTile as d3_geoTile } from '../lib/d3.geo.tile'; import { geoExtent } from '../geo'; +import { svgDefs } from '../svg'; import { utilDetect } from '../util/detect'; import { utilQsString, utilRebind } from '../util'; -var apibase = 'https://a.mapillary.com/v3/', - viewercss = 'mapillary-js/mapillary.min.css', - viewerjs = 'mapillary-js/mapillary.min.js', - clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi', - maxResults = 1000, - tileZoom = 14, - dispatch = d3_dispatch('loadedImages', 'loadedSigns'), - _mlyFallback = false, - _mlyCache, - _mlyClicks, - _mlySelectedImage, - _mlySignDefs, - _mlySignSprite, - _mlyViewer; +var apibase = 'https://a.mapillary.com/v3/'; +var viewercss = 'mapillary-js/mapillary.min.css'; +var viewerjs = 'mapillary-js/mapillary.min.js'; +var clientId = 'NzNRM2otQkR2SHJzaXJmNmdQWVQ0dzo1ZWYyMmYwNjdmNDdlNmVi'; +var maxResults = 1000; +var tileZoom = 14; +var dispatch = d3_dispatch('loadedImages', 'loadedSigns'); +var _mlyFallback = false; +var _mlyCache; +var _mlyClicks; +var _mlySelectedImage; +// var _mlySignDefs; +var _mlySignSprite; +var _mlyViewer; function abortRequest(i) { @@ -52,10 +53,10 @@ function abortRequest(i) { function nearNullIsland(x, y, z) { if (z >= 7) { - var center = Math.pow(2, z - 1), - width = Math.pow(2, z - 6), - min = center - (width / 2), - max = center + (width / 2) - 1; + var center = Math.pow(2, z - 1); + var width = Math.pow(2, z - 6); + var min = center - (width / 2); + var max = center + (width / 2) - 1; return x >= min && x <= max && y >= min && y <= max; } return false; @@ -87,12 +88,13 @@ function localeTimestamp(s) { function getTiles(projection) { - 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]]; + var s = projection.scale() * 2 * Math.PI; + var z = Math.max(Math.log(s) / Math.log(2) - 8, 0); + var ts = 256 * Math.pow(2, z - tileZoom); + var origin = [ + s / 2 - projection.translate()[0], + s / 2 - projection.translate()[1] + ]; return d3_geoTile() .scaleExtent([tileZoom, tileZoom]) @@ -100,8 +102,8 @@ function getTiles(projection) { .size(projection.clipExtent()[1]) .translate(projection.translate())() .map(function(tile) { - var x = tile[0] * ts - origin[0], - y = tile[1] * ts - origin[1]; + var x = tile[0] * ts - origin[0]; + var y = tile[1] * ts - origin[1]; return { id: tile.toString(), @@ -116,12 +118,12 @@ function getTiles(projection) { function loadTiles(which, url, projection) { - var s = projection.scale() * 2 * Math.PI, - currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0)); + var s = projection.scale() * 2 * Math.PI; + var currZoom = Math.floor(Math.max(Math.log(s) / Math.log(2) - 8, 0)); var tiles = getTiles(projection).filter(function(t) { - return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]); - }); + return !nearNullIsland(t.xyz[0], t.xyz[1], t.xyz[2]); + }); _filter(which.inflight, function(v, k) { var wanted = _find(tiles, function(tile) { return k === (tile.id + ',0'); }); @@ -136,17 +138,17 @@ function loadTiles(which, url, projection) { function loadNextTilePage(which, currZoom, url, tile) { - var cache = _mlyCache[which], - rect = tile.extent.rectangle(), - maxPages = maxPageAtZoom(currZoom), - nextPage = cache.nextPage[tile.id] || 0, - nextURL = cache.nextURL[tile.id] || url + - utilQsString({ - per_page: maxResults, - page: nextPage, - client_id: clientId, - bbox: [rect[0], rect[1], rect[2], rect[3]].join(','), - }); + var cache = _mlyCache[which]; + var rect = tile.extent.rectangle(); + var maxPages = maxPageAtZoom(currZoom); + var nextPage = cache.nextPage[tile.id] || 0; + var nextURL = cache.nextURL[tile.id] || url + + utilQsString({ + per_page: maxResults, + page: nextPage, + client_id: clientId, + bbox: [rect[0], rect[1], rect[2], rect[3]].join(','), + }); if (nextPage > maxPages) return; @@ -170,8 +172,8 @@ function loadNextTilePage(which, currZoom, url, tile) { if (err || !data.features || !data.features.length) return; var features = data.features.map(function(feature) { - var loc = feature.geometry.coordinates, - d; + var loc = feature.geometry.coordinates; + var d; if (which === 'images') { d = { @@ -260,14 +262,14 @@ function parsePagination(links) { function partitionViewport(psize, projection) { var dimensions = projection.clipExtent()[1]; psize = psize || 16; - var cols = d3_range(0, dimensions[0], psize), - rows = d3_range(0, dimensions[1], psize), - partitions = []; + var cols = d3_range(0, dimensions[0], psize); + var rows = d3_range(0, dimensions[1], psize); + var partitions = []; rows.forEach(function(y) { cols.forEach(function(x) { - var min = [x, y + psize], - max = [x + psize, y]; + var min = [x, y + psize]; + var max = [x + psize, y]; partitions.push( geoExtent(projection.invert(min), projection.invert(max))); }); @@ -384,28 +386,28 @@ export default { signsSupported: function() { - var detected = utilDetect(); - if (detected.ie) return false; - if ((detected.browser.toLowerCase() === 'safari') && (parseFloat(detected.version) < 10)) return false; + // var detected = utilDetect(); + // if (detected.ie) return false; + // if ((detected.browser.toLowerCase() === 'safari') && (parseFloat(detected.version) < 10)) return false; return true; }, - signHTML: function(d) { - if (!_mlySignDefs || !_mlySignSprite) return; - var position = _mlySignDefs[d.value]; - if (!position) return '
'; - var iconStyle = [ - 'background-image:url(' + _mlySignSprite + ')', - 'background-repeat:no-repeat', - 'height:' + position.height + 'px', - 'width:' + position.width + 'px', - 'background-position-x:-' + position.x + 'px', - 'background-position-y:-' + position.y + 'px', - ]; + // signHTML: function(d) { + // if (!_mlySignDefs || !_mlySignSprite) return; + // var position = _mlySignDefs[d.value]; + // if (!position) return ''; + // var iconStyle = [ + // 'background-image:url(' + _mlySignSprite + ')', + // 'background-repeat:no-repeat', + // 'height:' + position.height + 'px', + // 'width:' + position.width + 'px', + // 'background-position-x:-' + position.x + 'px', + // 'background-position-y:-' + position.y + 'px', + // ]; - return ''; - }, + // return ''; + // }, loadImages: function(projection) { @@ -420,14 +422,15 @@ export default { loadTiles('objects', apibase + 'objects?', projection); // load traffic sign defs - if (!_mlySignDefs) { - _mlySignSprite = context.asset('img/traffic-signs/traffic-signs.png'); - _mlySignDefs = {}; - d3_json(context.asset('img/traffic-signs/traffic-signs.json'), function(err, data) { - if (err) return; - _mlySignDefs = data; - }); - } + + // if (!_mlySignDefs) { + // _mlySignSprite = context.asset('img/traffic-signs/traffic-signs.png'); + // _mlySignDefs = {}; + // d3_json(context.asset('img/traffic-signs/traffic-signs.json'), function(err, data) { + // if (err) return; + // _mlySignDefs = data; + // }); + // } }, @@ -463,6 +466,10 @@ export default { .append('script') .attr('id', 'mapillary-viewerjs') .attr('src', context.asset(viewerjs)); + + // load mapillary signs sprite + var defs = context.container().select('defs'); + defs.call(svgDefs(context).addSprites, ['mapillary-sprite']); }, @@ -739,10 +746,8 @@ export default { function loadDetection(detectionKey) { - var url = apibase + 'detections/'+ - detectionKey + '?' + utilQsString({ - client_id: clientId, - }); + var url = apibase + 'detections/' + + detectionKey + '?' + utilQsString({ client_id: clientId }); d3_request(url) .mimeType('application/json') @@ -816,13 +821,13 @@ export default { cache: function() { return _mlyCache; - }, - - - signDefs: function(_) { - if (!arguments.length) return _mlySignDefs; - _mlySignDefs = _; - return this; } + + // signDefs: function(_) { + // if (!arguments.length) return _mlySignDefs; + // _mlySignDefs = _; + // return this; + // } + }; diff --git a/modules/svg/defs.js b/modules/svg/defs.js index a522ac107..4de82e4ad 100644 --- a/modules/svg/defs.js +++ b/modules/svg/defs.js @@ -1,3 +1,5 @@ +import _uniq from 'lodash-es/uniq'; + import { request as d3_request } from 'd3-request'; import { select as d3_select } from 'd3-selection'; @@ -8,10 +10,10 @@ import { select as d3_select } from 'd3-selection'; */ export function svgDefs(context) { - return function drawDefs(selection) { + function drawDefs(selection) { var defs = selection.append('defs'); - // markers + // add markers defs .append('marker') .attr('id', 'oneway-marker') @@ -66,7 +68,7 @@ export function svgDefs(context) { .attr('stroke-width', '0.5px') .attr('stroke-opacity', '0.75'); - // patterns + // add patterns var patterns = defs.selectAll('pattern') .data([ // pattern name, pattern image name @@ -104,7 +106,7 @@ export function svgDefs(context) { return context.imagePath('pattern/' + d[1] + '.png'); }); - // clip paths + // add clip paths defs.selectAll('clipPath') .data([12, 18, 20, 32, 45]) .enter() @@ -116,33 +118,39 @@ export function svgDefs(context) { .attr('width', function (d) { return d; }) .attr('height', function (d) { return d; }); - // symbol spritesheets - defs.selectAll('.spritesheet') - .data([ - 'iD-sprite', - 'maki-sprite', - 'temaki-sprite', - 'fa-sprite', - 'community-sprite' - ]) + // add symbol spritesheets + defs + .call(drawDefs.addSprites, [ + 'iD-sprite', 'maki-sprite', 'temaki-sprite', 'fa-sprite', 'community-sprite' + ]); + } + + + drawDefs.addSprites = function(selection, ids) { + var spritesheets = selection.selectAll('.spritesheet'); + var currData = spritesheets.data(); + var data = _uniq(currData.concat(ids)); + + spritesheets + .data(data) .enter() .append('g') - .attr('class', function (d) { return 'spritesheet spritesheet-' + d; }) - .each(loadSprite); - - - function loadSprite(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; - node.appendChild( - d3_select(svg.documentElement).attr('id', d).node() - ); - }); - } + .attr('class', function(d) { return 'spritesheet spritesheet-' + d; }) + .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; + node.appendChild( + d3_select(svg.documentElement).attr('id', d).node() + ); + }); + }); }; + + + return drawDefs; } diff --git a/modules/svg/mapillary_signs.js b/modules/svg/mapillary_signs.js index 4b6095cd2..a9049345d 100644 --- a/modules/svg/mapillary_signs.js +++ b/modules/svg/mapillary_signs.js @@ -1,6 +1,7 @@ import _some from 'lodash-es/some'; import _throttle from 'lodash-es/throttle'; import { select as d3_select } from 'd3-selection'; +import { svgPointTransform } from './index'; import { services } from '../services'; @@ -86,18 +87,24 @@ export function svgMapillarySigns(projection, context, dispatch) { var viewer = d3_select('#photoviewer'); var selected = viewer.empty() ? undefined : viewer.datum(); var selectedImageKey = selected && selected.key; + var transform = svgPointTransform(projection); var signs = layer.selectAll('.icon-sign') .data(data, function(d) { return d.key; }); + // exit signs.exit() .remove(); + // enter var enter = signs.enter() - .append('foreignObject') + .append('use') .attr('class', 'icon-sign') - .attr('width', '24px') // for Firefox - .attr('height', '24px') // for Firefox + .attr('width', '24px') + .attr('height', '24px') + .attr('x', '-12px') + .attr('y', '-12px') + .attr('xlink:href', function(d) { return '#' + d.value; }) .classed('selected', function(d) { return _some(d.detections, function(detection) { return detection.image_key === selectedImageKey; @@ -105,21 +112,40 @@ export function svgMapillarySigns(projection, context, dispatch) { }) .on('click', click); - enter - .append('xhtml:body') - .attr('class', 'icon-sign-body') - .html(service.signHTML); + // var enter = signs.enter() + // .append('foreignObject') + // .attr('class', 'icon-sign') + // .attr('width', '24px') // for Firefox + // .attr('height', '24px') // for Firefox + // .classed('selected', function(d) { + // return _some(d.detections, function(detection) { + // return detection.image_key === selectedImageKey; + // }); + // }) + // .on('click', click); + // enter + // .append('xhtml:body') + // .attr('class', 'icon-sign-body') + // .html(service.signHTML); + + // update signs .merge(enter) - .attr('x', function(d) { return projection(d.loc)[0] - 12; }) // offset by -12px to - .attr('y', function(d) { return projection(d.loc)[1] - 12; }); // center signs on loc + .sort(function(a, b) { + return (a === selected) ? 1 + : (b === selected) ? -1 + : b.loc[1] - a.loc[1]; // sort Y + }) + .attr('transform', transform); + // .attr('x', function(d) { return projection(d.loc)[0] - 12; }) // offset by -12px to + // .attr('y', function(d) { return projection(d.loc)[1] - 12; }); // center signs on loc } function drawSigns(selection) { - var enabled = svgMapillarySigns.enabled, - service = getService(); + var enabled = svgMapillarySigns.enabled; + var service = getService(); layer = selection.selectAll('.layer-mapillary-signs') .data(service ? [0] : []); diff --git a/test/spec/services/mapillary.js b/test/spec/services/mapillary.js index 26d674bc6..ceb9f9fa1 100644 --- a/test/spec/services/mapillary.js +++ b/test/spec/services/mapillary.js @@ -156,30 +156,30 @@ describe('iD.serviceMapillary', function() { }); describe('#loadSigns', function() { - it('loads sign_defs', function() { - mapillary.loadSigns(context, context.projection); + // it('loads sign_defs', function() { + // mapillary.loadSigns(context, context.projection); - var sign = 'regulatory--maximum-speed-limit-65--g1', - match = /img\/traffic-signs\/traffic-signs.json/; + // var sign = 'regulatory--maximum-speed-limit-65--g1', + // match = /img\/traffic-signs\/traffic-signs.json/; - server.respondWith('GET', match, function (xhr) { - xhr.respond(200, { 'Content-Type': 'application/json' }, - '{ "' + sign + '": { "height": 24, "pixelRatio": 1, "width": 24, "x": 576, "y": 528} }'); - }); - server.respond(); + // server.respondWith('GET', match, function (xhr) { + // xhr.respond(200, { 'Content-Type': 'application/json' }, + // '{ "' + sign + '": { "height": 24, "pixelRatio": 1, "width": 24, "x": 576, "y": 528} }'); + // }); + // server.respond(); - var sign_defs = mapillary.signDefs(); + // var sign_defs = mapillary.signDefs(); - expect(sign_defs).to.have.property('regulatory--maximum-speed-limit-65--g1') - .that.is.an('object') - .that.deep.equals({ - height: 24, - pixelRatio: 1, - width: 24, - x: 576, - y: 528 - }); - }); + // expect(sign_defs).to.have.property('regulatory--maximum-speed-limit-65--g1') + // .that.is.an('object') + // .that.deep.equals({ + // height: 24, + // pixelRatio: 1, + // width: 24, + // x: 576, + // y: 528 + // }); + // }); it('fires loadedSigns when signs are loaded', function() { var spy = sinon.spy(); @@ -387,48 +387,48 @@ describe('iD.serviceMapillary', function() { }); - describe('#signsSupported', function() { - it('returns false for Internet Explorer', function() { - ua = 'Trident/7.0; rv:11.0'; - iD.Detect(true); // force redetection - expect(mapillary.signsSupported()).to.be.false; - }); + // describe('#signsSupported', function() { + // it('returns false for Internet Explorer', function() { + // ua = 'Trident/7.0; rv:11.0'; + // iD.Detect(true); // force redetection + // expect(mapillary.signsSupported()).to.be.false; + // }); - it('returns false for Safari 9', function() { - ua = 'Version/9.1 Safari/601'; - iD.Detect(true); // force redetection - expect(mapillary.signsSupported()).to.be.false; - }); + // it('returns false for Safari 9', function() { + // ua = 'Version/9.1 Safari/601'; + // iD.Detect(true); // force redetection + // expect(mapillary.signsSupported()).to.be.false; + // }); - it('returns true for Safari 10', function() { - ua = 'Version/10.0 Safari/602'; - iD.Detect(true); // force redetection - expect(mapillary.signsSupported()).to.be.true; - }); - }); + // it('returns true for Safari 10', function() { + // ua = 'Version/10.0 Safari/602'; + // iD.Detect(true); // force redetection + // expect(mapillary.signsSupported()).to.be.true; + // }); + // }); - describe('#signHTML', function() { - it('returns sign HTML', function() { - mapillary.signDefs({ - 'regulatory--maximum-speed-limit-65--g1': { - 'height': 24, - 'pixelRatio': 1, - 'width': 24, - 'x': 576, - 'y': 528, - }, - }); + // describe('#signHTML', function() { + // it('returns sign HTML', function() { + // mapillary.signDefs({ + // 'regulatory--maximum-speed-limit-65--g1': { + // 'height': 24, + // 'pixelRatio': 1, + // 'width': 24, + // 'x': 576, + // 'y': 528, + // }, + // }); - var signdata = { - key: '0', - loc: [10,0], - value: 'regulatory--maximum-speed-limit-65--g1', - }; + // var signdata = { + // key: '0', + // loc: [10,0], + // value: 'regulatory--maximum-speed-limit-65--g1', + // }; - var sprite = context.asset('img/traffic-signs/traffic-signs.png'); - expect(mapillary.signHTML(signdata)).to.eql(''); - }); - }); + // var sprite = context.asset('img/traffic-signs/traffic-signs.png'); + // expect(mapillary.signHTML(signdata)).to.eql(''); + // }); + // }); describe('#selectImage', function() { it('gets and sets the selected image', function() {