Begin replacing mapillary signs html with svg spritesheet

(re: #4145)
This commit is contained in:
Bryan Housel
2018-06-06 23:16:49 -04:00
parent 22613947b3
commit b7997d3c64
4 changed files with 221 additions and 182 deletions

View File

@@ -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 '<div></div>';
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 '<div></div>';
// 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 '<div style="' + iconStyle.join(';') +'"></div>';
},
// return '<div style="' + iconStyle.join(';') +'"></div>';
// },
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;
// }
};

View File

@@ -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;
}

View File

@@ -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] : []);

View File

@@ -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('<div style="background-image:url(' + sprite + ');background-repeat:no-repeat;height:24px;width:24px;background-position-x:-576px;background-position-y:-528px"></div>');
});
});
// var sprite = context.asset('img/traffic-signs/traffic-signs.png');
// expect(mapillary.signHTML(signdata)).to.eql('<div style="background-image:url(' + sprite + ');background-repeat:no-repeat;height:24px;width:24px;background-position-x:-576px;background-position-y:-528px"></div>');
// });
// });
describe('#selectImage', function() {
it('gets and sets the selected image', function() {