Dynamically check for existence of Mapillary service

This allows us to
1. enable specific parts of Mapillary only for certain browsers
2. remove the Mapillary service completely if desired (closes #2722)

To remove Mapillary, just set `iD.services.mapillary = null`
or remove the <script> tag that loads mapillary.js.
This commit is contained in:
Bryan Housel
2016-02-15 16:16:44 -05:00
parent ff5139fc58
commit 5957adb67d
6 changed files with 299 additions and 165 deletions
+4
View File
@@ -1797,6 +1797,10 @@ div.full-screen > button:hover {
color: #7092FF;
}
.layer-list:empty {
display: none;
}
.layer-list > li:first-child {
border-radius: 3px 3px 0 0;
}
+35 -10
View File
@@ -4,8 +4,8 @@ iD.Background = function(context) {
.projection(context.projection),
gpxLayer = iD.GpxLayer(context, dispatch)
.projection(context.projection),
mapillaryImageLayer = iD.MapillaryImageLayer(context),
mapillarySignLayer = iD.MapillarySignLayer(context),
mapillaryImageLayer,
mapillarySignLayer,
overlayLayers = [];
var backgroundSources;
@@ -86,21 +86,43 @@ iD.Background = function(context) {
gpx.call(gpxLayer);
var mapillary = iD.services.mapillary,
supportsMapillaryImages = !!mapillary,
supportsMapillarySigns = !!mapillary && mapillary().signsSupported();
var mapillaryImages = selection.selectAll('.layer-mapillary-images')
.data([0]);
.data(supportsMapillaryImages ? [0] : []);
mapillaryImages.enter().insert('div')
.attr('class', 'layer-layer layer-mapillary-images');
mapillaryImages.call(mapillaryImageLayer);
if (supportsMapillaryImages) {
if (!mapillaryImageLayer) { mapillaryImageLayer = iD.MapillaryImageLayer(context); }
mapillaryImages.call(mapillaryImageLayer);
} else {
mapillaryImageLayer = null;
}
mapillaryImages.exit()
.remove();
var mapillarySigns = selection.selectAll('.layer-mapillary-signs')
.data([0]);
.data(supportsMapillarySigns ? [0] : []);
mapillarySigns.enter().insert('div')
.attr('class', 'layer-layer layer-mapillary-signs');
mapillarySigns.call(mapillarySignLayer);
if (supportsMapillarySigns) {
if (!mapillarySignLayer) { mapillarySignLayer = iD.MapillarySignLayer(context); }
mapillarySigns.call(mapillarySignLayer);
} else {
mapillarySignLayer = null;
}
mapillarySigns.exit()
.remove();
}
background.sources = function(extent) {
@@ -112,8 +134,8 @@ iD.Background = function(context) {
background.dimensions = function(_) {
baseLayer.dimensions(_);
gpxLayer.dimensions(_);
mapillaryImageLayer.dimensions(_);
mapillarySignLayer.dimensions(_);
if (mapillaryImageLayer) mapillaryImageLayer.dimensions(_);
if (mapillarySignLayer) mapillarySignLayer.dimensions(_);
overlayLayers.forEach(function(layer) {
layer.dimensions(_);
@@ -183,22 +205,25 @@ iD.Background = function(context) {
};
background.showsMapillaryImageLayer = function() {
return mapillaryImageLayer.enable();
return mapillaryImageLayer && mapillaryImageLayer.enable();
};
background.showsMapillarySignLayer = function() {
return mapillarySignLayer.enable();
return mapillarySignLayer && mapillarySignLayer.enable();
};
background.toggleMapillaryImageLayer = function() {
if (!mapillaryImageLayer) return;
mapillaryImageLayer.enable(!mapillaryImageLayer.enable());
dispatch.change();
};
background.toggleMapillarySignLayer = function() {
if (!mapillarySignLayer) return;
mapillarySignLayer.enable(!mapillarySignLayer.enable());
dispatch.change();
};
background.showsLayer = function(d) {
return d === baseLayer.source() ||
(d.id === 'custom' && baseLayer.source().id === 'custom') ||
+42 -19
View File
@@ -1,12 +1,23 @@
iD.MapillaryImageLayer = function(context) {
var mapillary = iD.services.mapillary(),
debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
enabled = false,
minZoom = 12,
layer;
layer = d3.select(null),
_mapillary;
function getMapillary() {
if (iD.services.mapillary && !_mapillary) {
_mapillary = iD.services.mapillary().on('loadedImages', debouncedRedraw);
} else if (!iD.services.mapillary && _mapillary) {
_mapillary = null;
}
return _mapillary;
}
function showThumbnail(image) {
var mapillary = getMapillary();
if (!mapillary) return;
var thumb = mapillary.selectedThumbnail(),
posX = context.projection(image.loc)[0],
width = layer.dimensions()[0],
@@ -24,7 +35,10 @@ iD.MapillaryImageLayer = function(context) {
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
.classed('selected', false);
mapillary.hideThumbnail();
var mapillary = getMapillary();
if (mapillary) {
mapillary.hideThumbnail();
}
}
function showLayer() {
@@ -63,7 +77,8 @@ iD.MapillaryImageLayer = function(context) {
}
function drawMarkers() {
var data = mapillary.images(context);
var mapillary = getMapillary(),
data = (mapillary ? mapillary.images(context) : []);
var markers = layer.selectAll('.viewfield-group')
.data(data, function(d) { return d.key; });
@@ -83,24 +98,28 @@ iD.MapillaryImageLayer = function(context) {
.attr('dy', '0')
.attr('r', '6');
// Update
markers
.attr('transform', transform);
// Exit
markers.exit()
.remove();
// Update
markers
.attr('transform', transform);
}
function render(selection) {
layer = selection.selectAll('svg')
.data([0]);
var mapillary = getMapillary();
layer = selection.selectAll('svg')
.data(mapillary ? [0] : []);
// Enter
layer.enter()
.append('svg')
.style('display', enabled ? 'block' : 'none')
.dimensions(context.map().dimensions())
.on('click', function() { // deselect/select
var mapillary = getMapillary();
if (!mapillary) return;
var d = d3.event.target.__data__,
thumb = mapillary.selectedThumbnail();
if (thumb && thumb.key === d.key) {
@@ -112,9 +131,13 @@ iD.MapillaryImageLayer = function(context) {
}
})
.on('mouseover', function() {
var mapillary = getMapillary();
if (!mapillary) return;
showThumbnail(d3.event.target.__data__);
})
.on('mouseout', function() {
var mapillary = getMapillary();
if (!mapillary) return;
var thumb = mapillary.selectedThumbnail();
if (thumb) {
showThumbnail(thumb);
@@ -123,13 +146,16 @@ iD.MapillaryImageLayer = function(context) {
}
});
layer.exit()
.remove();
if (enabled) {
if (~~context.map().zoom() < minZoom) {
editOff();
} else {
if (mapillary && ~~context.map().zoom() >= minZoom) {
editOn();
drawMarkers();
mapillary.loadImages(context.projection, layer.dimensions());
} else {
editOff();
}
}
}
@@ -146,14 +172,11 @@ iD.MapillaryImageLayer = function(context) {
};
render.dimensions = function(_) {
if (layer.empty()) return null;
if (!arguments.length) return layer.dimensions();
layer.dimensions(_);
return render;
};
mapillary
.on('loadedImages', debouncedRedraw);
return render;
};
+40 -18
View File
@@ -1,12 +1,23 @@
iD.MapillarySignLayer = function(context) {
var mapillary = iD.services.mapillary(),
debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
var debouncedRedraw = _.debounce(function () { context.pan([0,0]); }, 1000),
enabled = false,
minZoom = 12,
layer;
layer = d3.select(null),
_mapillary;
function getMapillary() {
if (iD.services.mapillary && !_mapillary) {
_mapillary = iD.services.mapillary().on('loadedSigns', debouncedRedraw);
} else if (!iD.services.mapillary && _mapillary) {
_mapillary = null;
}
return _mapillary;
}
function showThumbnail(image) {
var mapillary = getMapillary();
if (!mapillary) return;
var thumb = mapillary.selectedThumbnail(),
posX = context.projection(image.loc)[0],
width = layer.dimensions()[0],
@@ -24,7 +35,10 @@ iD.MapillarySignLayer = function(context) {
d3.selectAll('.layer-mapillary-images .viewfield-group, .layer-mapillary-signs .icon-sign')
.classed('selected', false);
mapillary.hideThumbnail();
var mapillary = getMapillary();
if (mapillary) {
mapillary.hideThumbnail();
}
}
function showLayer() {
@@ -48,7 +62,8 @@ iD.MapillarySignLayer = function(context) {
}
function drawSigns() {
var data = mapillary.signs(context);
var mapillary = getMapillary(),
data = (mapillary ? mapillary.signs(context) : []);
var signs = layer.select('.mapillary-sign-offset')
.selectAll('.icon-sign')
@@ -67,6 +82,8 @@ iD.MapillarySignLayer = function(context) {
enter
.on('click', function(d) { // deselect/select
var mapillary = getMapillary();
if (!mapillary) return;
var thumb = mapillary.selectedThumbnail();
if (thumb && thumb.key === d.key) {
hideThumbnail();
@@ -78,6 +95,8 @@ iD.MapillarySignLayer = function(context) {
})
.on('mouseover', showThumbnail)
.on('mouseout', function() {
var mapillary = getMapillary();
if (!mapillary) return;
var thumb = mapillary.selectedThumbnail();
if (thumb) {
showThumbnail(thumb);
@@ -86,33 +105,39 @@ iD.MapillarySignLayer = function(context) {
}
});
// Update
signs
.attr('transform', iD.svg.PointTransform(context.projection));
// Exit
signs.exit()
.remove();
// Update
signs
.attr('transform', iD.svg.PointTransform(context.projection));
}
function render(selection) {
var mapillary = getMapillary();
layer = selection.selectAll('svg')
.data([0]);
.data(mapillary ? [0] : []);
layer.enter()
.append('svg')
.style('display', enabled ? 'block' : 'none')
.dimensions(context.map().dimensions())
.append('g')
.attr('class', 'mapillary-sign-offset')
.attr('transform', 'translate(-16, -16)'); // center signs on loc
layer.exit()
.remove();
if (enabled) {
if (~~context.map().zoom() < minZoom) {
hideLayer();
} else {
layer.style('display', 'block');
if (mapillary && ~~context.map().zoom() >= minZoom) {
editOn();
drawSigns();
mapillary.loadSigns(context, context.projection, layer.dimensions());
} else {
editOff();
}
}
}
@@ -129,14 +154,11 @@ iD.MapillarySignLayer = function(context) {
};
render.dimensions = function(_) {
if (layer.empty()) return null;
if (!arguments.length) return layer.dimensions();
layer.dimensions(_);
return render;
};
mapillary
.on('loadedSigns', debouncedRedraw);
return render;
};
+10 -2
View File
@@ -169,6 +169,11 @@ iD.services.mapillary = function() {
return searchLimited(psize, limit, context, iD.services.mapillary.cache.signs.rtree);
};
mapillary.signsSupported = function() {
var detected = iD.detect();
return (!(detected.ie || detected.browser.toLowerCase() === 'safari'));
};
mapillary.signHTML = function(d) {
if (!iD.services.mapillary.sign_defs) return;
@@ -179,7 +184,7 @@ iD.services.mapillary = function() {
};
mapillary.showThumbnail = function(imageKey, position) {
if (!imageKey) return
if (!imageKey) return;
var positionClass = {
'ar': (position !== 'left'),
@@ -228,7 +233,9 @@ iD.services.mapillary = function() {
};
mapillary.hideThumbnail = function() {
iD.services.mapillary.thumb = null;
if (iD.services.mapillary) {
iD.services.mapillary.thumb = null;
}
d3.select('#content').selectAll('.mapillary-image')
.transition()
.duration(200)
@@ -237,6 +244,7 @@ iD.services.mapillary = function() {
};
mapillary.selectedThumbnail = function(d) {
if (!iD.services.mapillary) return null;
if (!arguments.length) return iD.services.mapillary.thumb;
iD.services.mapillary.thumb = d;
};
+168 -116
View File
@@ -5,6 +5,7 @@ iD.ui.MapData = function(context) {
fillDefault = context.storage('area-fill') || 'partial',
fillSelected = fillDefault;
function map_data(selection) {
function showsFeature(d) {
@@ -46,16 +47,164 @@ iD.ui.MapData = function(context) {
context.background().toggleMapillaryImageLayer();
update();
}
function clickMapillarySigns() {
context.background().toggleMapillarySignLayer();
update();
}
function drawMapillaryItems(selection) {
var mapillary = iD.services.mapillary,
supportsMapillaryImages = !!mapillary,
supportsMapillarySigns = !!mapillary && mapillary().signsSupported();
var mapillaryList = selection
.selectAll('.mapillary-list')
.data([0]);
// Enter
mapillaryList
.enter()
.append('ul')
.attr('class', 'layer-list mapillary-list');
var mapillaryImageLayerItem = mapillaryList
.selectAll('.item-mapillary-images')
.data(supportsMapillaryImages ? [0] : []);
var enterImages = mapillaryImageLayerItem.enter()
.append('li')
.attr('class', 'item-mapillary-images');
var labelImages = enterImages.append('label')
.call(bootstrap.tooltip()
.title(t('mapillary_images.tooltip'))
.placement('top'));
labelImages.append('input')
.attr('type', 'checkbox')
.on('change', clickMapillaryImages);
labelImages.append('span')
.text(t('mapillary_images.title'));
var mapillarySignLayerItem = mapillaryList
.selectAll('.item-mapillary-signs')
.data(supportsMapillarySigns ? [0] : []);
var enterSigns = mapillarySignLayerItem.enter()
.append('li')
.attr('class', 'item-mapillary-signs');
var labelSigns = enterSigns.append('label')
.call(bootstrap.tooltip()
.title(t('mapillary_signs.tooltip'))
.placement('top'));
labelSigns.append('input')
.attr('type', 'checkbox')
.on('change', clickMapillarySigns);
labelSigns.append('span')
.text(t('mapillary_signs.title'));
// Update
var showsMapillaryImages = supportsMapillaryImages && context.background().showsMapillaryImageLayer(),
showsMapillarySigns = supportsMapillarySigns && context.background().showsMapillarySignLayer();
mapillaryImageLayerItem
.classed('active', showsMapillaryImages)
.selectAll('input')
.property('checked', showsMapillaryImages);
mapillarySignLayerItem
.classed('active', showsMapillarySigns)
.selectAll('input')
.property('checked', showsMapillarySigns);
// Exit
mapillaryImageLayerItem.exit()
.remove();
mapillarySignLayerItem.exit()
.remove();
}
function drawGpxItem(selection) {
var supportsGpx = iD.detect().filedrop,
gpxLayerItem = selection
.selectAll('.layer-gpx')
.data(supportsGpx ? [0] : []);
// Enter
var enter = gpxLayerItem.enter()
.append('ul')
.attr('class', 'layer-list layer-gpx')
.append('li')
.classed('layer-toggle-gpx', true);
enter.append('button')
.attr('class', 'layer-extent')
.call(bootstrap.tooltip()
.title(t('gpx.zoom'))
.placement('left'))
.on('click', function() {
d3.event.preventDefault();
d3.event.stopPropagation();
context.background().zoomToGpxLayer();
})
.call(iD.svg.Icon('#icon-search'));
enter.append('button')
.attr('class', 'layer-browse')
.call(bootstrap.tooltip()
.title(t('gpx.browse'))
.placement('left'))
.on('click', function() {
d3.select(document.createElement('input'))
.attr('type', 'file')
.on('change', function() {
context.background().gpxLayerFiles(d3.event.target.files);
})
.node().click();
})
.call(iD.svg.Icon('#icon-geolocate'));
var labelGpx = enter.append('label')
.call(bootstrap.tooltip()
.title(t('gpx.drag_drop'))
.placement('top'));
labelGpx.append('input')
.attr('type', 'checkbox')
.on('change', clickGpx);
labelGpx.append('span')
.text(t('gpx.local_layer'));
// Update
var hasGpx = supportsGpx && context.background().hasGpxLayer(),
showsGpx = supportsGpx && context.background().showsGpxLayer();
gpxLayerItem
.classed('active', showsGpx)
.selectAll('input')
.property('disabled', !hasGpx)
.property('checked', showsGpx);
// Exit
gpxLayerItem.exit()
.remove();
}
function drawList(selection, data, type, name, change, active) {
var items = selection.selectAll('li')
.data(data);
//enter
// Enter
var enter = items.enter()
.append('li')
.attr('class', 'layer')
@@ -83,7 +232,7 @@ iD.ui.MapData = function(context) {
label.append('span')
.text(function(d) { return t(name + '.' + d + '.description'); });
//update
// Update
items
.classed('active', active)
.selectAll('input')
@@ -92,38 +241,24 @@ iD.ui.MapData = function(context) {
return (name === 'feature' && autoHiddenFeature(d));
});
//exit
// Exit
items.exit()
.remove();
}
function update() {
featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature);
dataLayerContainer.call(drawMapillaryItems);
dataLayerContainer.call(drawGpxItem);
fillList.call(drawList, fills, 'radio', 'area_fill', setFill, showsFill);
var hasGpx = context.background().hasGpxLayer(),
showsGpx = context.background().showsGpxLayer(),
showsMapillaryImages = context.background().showsMapillaryImageLayer(),
showsMapillarySigns = context.background().showsMapillarySignLayer();
gpxLayerItem
.classed('active', showsGpx)
.selectAll('input')
.property('disabled', !hasGpx)
.property('checked', showsGpx);
mapillaryImageLayerItem
.classed('active', showsMapillaryImages)
.selectAll('input')
.property('checked', showsMapillaryImages);
mapillarySignLayerItem
.classed('active', showsMapillarySigns)
.selectAll('input')
.property('checked', showsMapillarySigns);
featureList.call(drawList, features, 'checkbox', 'feature', clickFeature, showsFeature);
}
function hidePanel() { setVisible(false); }
function hidePanel() {
setVisible(false);
}
function togglePanel() {
if (d3.event) d3.event.preventDefault();
@@ -146,6 +281,7 @@ iD.ui.MapData = function(context) {
shown = show;
if (show) {
update();
selection.on('mousedown.map_data-inside', function() {
return d3.event.stopPropagation();
});
@@ -194,100 +330,16 @@ iD.ui.MapData = function(context) {
.classed('expanded', true)
.on('click', function() {
var exp = d3.select(this).classed('expanded');
layerContainer.style('display', exp ? 'none' : 'block');
dataLayerContainer.style('display', exp ? 'none' : 'block');
d3.select(this).classed('expanded', !exp);
d3.event.preventDefault();
});
var layerContainer = content.append('div')
.attr('class', 'filters')
var dataLayerContainer = content.append('div')
.attr('class', 'data-data-layers')
.style('display', 'block');
// Mapillary Image Layer
var mapillaryImageLayerItem = layerContainer.append('ul')
.attr('class', 'layer-list')
.append('li');
var labelImage = mapillaryImageLayerItem.append('label')
.call(bootstrap.tooltip()
.title(t('mapillary_images.tooltip'))
.placement('top'));
labelImage.append('input')
.attr('type', 'checkbox')
.on('change', clickMapillaryImages);
labelImage.append('span')
.text(t('mapillary_images.title'));
// Mapillary Sign Layer
var mapillarySignLayerItem = layerContainer.append('ul')
.attr('class', 'layer-list')
.append('li');
var labelSigns = mapillarySignLayerItem.append('label')
.call(bootstrap.tooltip()
.title(t('mapillary_signs.tooltip'))
.placement('top'));
labelSigns.append('input')
.attr('type', 'checkbox')
.on('change', clickMapillarySigns);
labelSigns.append('span')
.text(t('mapillary_signs.title'));
// GPX Layer
var gpxLayerItem = layerContainer.append('ul')
.style('display', iD.detect().filedrop ? 'block' : 'none')
.attr('class', 'layer-list')
.append('li')
.classed('layer-toggle-gpx', true);
gpxLayerItem.append('button')
.attr('class', 'layer-extent')
.call(bootstrap.tooltip()
.title(t('gpx.zoom'))
.placement('left'))
.on('click', function() {
d3.event.preventDefault();
d3.event.stopPropagation();
context.background().zoomToGpxLayer();
})
.call(iD.svg.Icon('#icon-search'));
gpxLayerItem.append('button')
.attr('class', 'layer-browse')
.call(bootstrap.tooltip()
.title(t('gpx.browse'))
.placement('left'))
.on('click', function() {
d3.select(document.createElement('input'))
.attr('type', 'file')
.on('change', function() {
context.background().gpxLayerFiles(d3.event.target.files);
})
.node().click();
})
.call(iD.svg.Icon('#icon-geolocate'));
var labelGpx = gpxLayerItem.append('label')
.call(bootstrap.tooltip()
.title(t('gpx.drag_drop'))
.placement('top'));
labelGpx.append('input')
.attr('type', 'checkbox')
.property('disabled', true)
.on('change', clickGpx);
labelGpx.append('span')
.text(t('gpx.local_layer'));
// area fills
content.append('a')
.text(t('map_data.fill_area'))
@@ -302,11 +354,11 @@ iD.ui.MapData = function(context) {
});
var fillContainer = content.append('div')
.attr('class', 'filters')
.attr('class', 'data-area-fills')
.style('display', 'none');
var fillList = fillContainer.append('ul')
.attr('class', 'layer-list');
.attr('class', 'layer-list layer-fill-list');
// feature filters
@@ -323,11 +375,11 @@ iD.ui.MapData = function(context) {
});
var featureContainer = content.append('div')
.attr('class', 'filters')
.attr('class', 'data-feature-filters')
.style('display', 'none');
var featureList = featureContainer.append('ul')
.attr('class', 'layer-list');
.attr('class', 'layer-list layer-feature-list');
context.features()