diff --git a/css/app.css b/css/app.css
index b280730c6..cd55957d1 100644
--- a/css/app.css
+++ b/css/app.css
@@ -717,48 +717,41 @@ a:hover .icon.out-link { background-position: -500px -14px;}
bottom: 0;
}
-#mapillaryImage {
+.mapillary-image {
position: absolute;
right: 0;
bottom: 30px;
+ width: 330px;
+ height: 250px;
padding: 5px;
background-color: #fff;
}
-#mapillaryImage > div {
- position: relative;
-}
-#mapillaryImage > a {
+.mapillary-image a {
display: block;
- width: 100%;
- height: auto;
- color: white;
- position: relative;
-}
-
-#mapillaryImage .link {
- background-color: rgba(0,0,0,.5);
position: absolute;
+ height: auto;
+ background-color: rgba(0,0,0,.5);
bottom: 0;
right: 0;
padding: 5px 10px;
}
-#mapillaryImage img {
+.mapillary-image img {
width: 100%;
height: auto;
display: block;
}
-#mapillaryImage.hidden {
- visibility: hidden;
+.mapillary-image.hidden {
+ visibility: hidden;
}
-#mapillaryImage.temp button {
+.mapillary-image.temp button {
display: none;
}
-#mapillaryImage button {
+.mapillary-image button {
border-radius: 0;
padding: 5px;
position: absolute;
diff --git a/css/map.css b/css/map.css
index 7e5ffe792..1329cbf44 100644
--- a/css/map.css
+++ b/css/map.css
@@ -1140,32 +1140,34 @@ text.gpx {
fill:#FF26D4;
}
-/* Mapillary Sequences */
+/* Mapillary Layer */
-g.image.point {
- cursor: pointer; /* Opera */
- cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
+.layer-mapillary {
+ pointer-events: none;
}
-g.image.point path,
-g.image.point circle {
- stroke-width: 2;
- stroke: #ffc600;
- fill: #ffc600;
+.layer-mapillary g {
+ pointer-events: visible;
+ cursor: pointer; /* Opera */
+ cursor: url(img/cursor-select-mapillary.png) 6 1, pointer; /* FF */
}
-g.image.point:hover circle,
-g.image.point:hover path {
- fill: #ff9900;
- stroke-width: 2;
- stroke: #ff9900;
+.layer-mapillary g * {
+ stroke-width: 2;
+ stroke: #ffc600;
+ fill: #ffc600;
}
-g.image.point.selected circle,
-g.image.point.selected path {
- fill: #ff5800;
- stroke-width: 4;
- stroke: #ff5800;
+.layer-mapillary g:hover * {
+ stroke-width: 2;
+ stroke: #ff9900;
+ fill: #ff9900;
+}
+
+.layer-mapillary g.selected * {
+ stroke-width: 4;
+ stroke: #ff5800;
+ fill: #ff5800;
}
/* Modes */
diff --git a/data/core.yaml b/data/core.yaml
index d1118ef48..efb948d32 100644
--- a/data/core.yaml
+++ b/data/core.yaml
@@ -19,9 +19,6 @@ en:
tail: Click to add nodes to your area. Click the first node to finish the area.
draw_line:
tail: "Click to add more nodes to the line. Click on other lines to connect to them, and double-click to end the line."
- selectImage:
- title: Photos
- description: "Choose and fix a Mapillary image for mapping. Shortcut: 'm'"
operations:
add:
annotation:
@@ -296,11 +293,9 @@ en:
zoom: "Zoom to GPX track"
browse: "Browse for a .gpx file"
mapillary:
- tooltip: "Mapillary street photos"
- title: "Photo overlay"
+ tooltip: "Street-level photos from Mapillary"
+ title: "Photo Overlay (Mapillary)"
view_on_mapillary: "View this image on Mapillary"
- no_image_found: |
- No image found on Mapillary. Go and take some!
help:
title: "Help"
help: |
diff --git a/dist/locales/en.json b/dist/locales/en.json
index 164deac2f..7df4440a2 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -24,10 +24,6 @@
},
"draw_line": {
"tail": "Click to add more nodes to the line. Click on other lines to connect to them, and double-click to end the line."
- },
- "selectImage": {
- "title": "Photos",
- "description": "Choose and fix a Mapillary image for mapping. Shortcut: 'm'"
}
},
"operations": {
@@ -359,10 +355,9 @@
"browse": "Browse for a .gpx file"
},
"mapillary": {
- "tooltip": "Mapillary street photos",
- "title": "Photo overlay",
- "view_on_mapillary": "View this image on Mapillary",
- "no_image_found": "No image found on Mapillary. Go and take some!\n"
+ "tooltip": "Street-level photos from Mapillary",
+ "title": "Photo Overlay (Mapillary)",
+ "view_on_mapillary": "View this image on Mapillary"
},
"help": {
"title": "Help",
diff --git a/index.html b/index.html
index f267f5c3b..b50fa5e49 100644
--- a/index.html
+++ b/index.html
@@ -58,6 +58,7 @@
+
@@ -66,7 +67,6 @@
-
@@ -77,7 +77,6 @@
-
@@ -190,7 +189,6 @@
-
diff --git a/js/id/id.js b/js/id/id.js
index 0fe52e675..3d135de22 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -214,13 +214,6 @@ window.iD = function () {
context.zoomIn = map.zoomIn;
context.zoomOut = map.zoomOut;
-
- /* Mapillary image view */
- var imageView = iD.ui.ImageView(context);
- context.imageView = function() {
- return imageView;
- };
-
context.surfaceRect = function() {
// Work around a bug in Firefox.
// http://stackoverflow.com/questions/18153989/
diff --git a/js/id/modes/select_image.js b/js/id/modes/select_image.js
deleted file mode 100644
index 13230dfc4..000000000
--- a/js/id/modes/select_image.js
+++ /dev/null
@@ -1,97 +0,0 @@
-iD.modes.SelectImage = function (context) {
- var mode = {
- button: 'selectImage',
- id: 'selectImage',
- title: t('modes.selectImage.title'),
- description: t('modes.selectImage.description'),
- key: 'm'
- }, imageView, currentImage;
-
- function click() {
- var datum = d3.event.target.__data__;
- if (isImage(datum)) {
- if (currentImage === datum) {
- context.surface().selectAll('.image.point')
- .classed('selected', false);
- currentImage = undefined;
- } else {
- currentImage = datum;
- context.surface().selectAll('.image.point')
- .classed('selected', function(d) {
- return d === datum;
- });
- context.container()
- .select('#mapillaryImage')
- .classed('temp', false);
- imageView.show(currentImage);
- }
- }
- }
-
- function isImage(datum) {
- return datum &&
- datum.properties !== undefined &&
- datum.properties.entityType === 'image';
- }
-
- mode.enter = function () {
- context.map().enableSequences(true);
- context.container()
- .select('#select_image_checkbox')
- .attr('checked','checked');
-
- // Get focus on the body.
- if (document.activeElement && document.activeElement.blur) {
- document.activeElement.blur();
- }
-
- imageView = context.imageView();
- imageView.showEmpty();
-
- context.surface()
- .on('click.image', click)
- .on('mouseover.image', function () {
- var datum = d3.event.target.__data__;
- if (isImage(datum)) {
- imageView.show(datum);
- if (currentImage !== datum) {
- context.container()
- .select('#mapillaryImage')
- .classed('temp', true);
- }
- }
- })
- .on('mouseout.image', function () {
- var datum = d3.event.target.__data__;
- if (isImage(datum)) {
- if (currentImage) {
- imageView.show(currentImage);
- } else {
- imageView.showEmpty();
- }
- }
- });
- };
-
- mode.exit = function () {
- context.map().enableSequences(false);
- context.container().select('#select_image_checkbox')
- .attr('checked', null);
-
- if (!currentImage) {
- context.container()
- .select('#mapillaryImage')
- .classed('hidden', true)
- .classed('temp', false);
- }
-
- context.surface().select('defs').selectAll('marker.arrow')
- .remove();
- context.surface().select('.layer-hit').selectAll('g.image')
- .remove();
- context.surface().select('.layer-hit').selectAll('g.sequence')
- .remove();
- };
-
- return mode;
-};
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index f548d3606..c1f182b6b 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -4,6 +4,7 @@ iD.Background = function(context) {
.projection(context.projection),
gpxLayer = iD.GpxLayer(context, dispatch)
.projection(context.projection),
+ mapillaryLayer = iD.MapillaryLayer(context),
overlayLayers = [];
var backgroundSources = iD.data.imagery.map(function(source) {
@@ -91,6 +92,14 @@ iD.Background = function(context) {
overlays.exit()
.remove();
+
+ var mapillary = selection.selectAll('.layer-mapillary')
+ .data([0]);
+
+ mapillary.enter().insert('div')
+ .attr('class', 'layer-layer layer-mapillary');
+
+ mapillary.call(mapillaryLayer);
}
background.sources = function(extent) {
@@ -102,6 +111,7 @@ iD.Background = function(context) {
background.dimensions = function(_) {
baseLayer.dimensions(_);
gpxLayer.dimensions(_);
+ mapillaryLayer.dimensions(_);
overlayLayers.forEach(function(layer) {
layer.dimensions(_);
@@ -166,6 +176,15 @@ iD.Background = function(context) {
dispatch.change();
};
+ background.showsMapillaryLayer = function() {
+ return mapillaryLayer.enable();
+ };
+
+ background.toggleMapillaryLayer = function() {
+ mapillaryLayer.enable(!mapillaryLayer.enable());
+ dispatch.change();
+ };
+
background.showsLayer = function(d) {
return d === baseLayer.source() ||
(d.id === 'custom' && baseLayer.source().id === 'custom') ||
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 3ebc18354..bf88b9491 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -18,7 +18,6 @@ iD.Map = function(context) {
areas = iD.svg.Areas(projection),
midpoints = iD.svg.Midpoints(roundedProjection, context),
labels = iD.svg.Labels(projection, context),
- sequences = iD.svg.Sequences(projection, context),
supersurface, surface,
mouse,
mousemove;
@@ -34,8 +33,6 @@ iD.Map = function(context) {
supersurface = selection.append('div')
.attr('id', 'supersurface');
- supersurface.call(context.background());
-
// Need a wrapper div because Opera can't cope with an absolutely positioned
// SVG element: http://bl.ocks.org/jfirebaugh/6fbfbd922552bf776c16
var dataLayer = supersurface.append('div')
@@ -53,6 +50,8 @@ iD.Map = function(context) {
.attr('id', 'surface')
.call(iD.svg.Surface(context));
+ supersurface.call(context.background());
+
surface.on('mousemove.map', function() {
mousemove = d3.event;
});
@@ -114,7 +113,6 @@ iD.Map = function(context) {
.call(vertices, graph, all, filter, map.extent(), map.zoom())
.call(lines, graph, all, filter)
.call(areas, graph, all, filter)
- .call(sequences, surface)
.call(midpoints, graph, all, filter, map.trimmedExtent())
.call(labels, graph, all, filter, dimensions, !difference && !extent);
@@ -402,9 +400,5 @@ iD.Map = function(context) {
return map;
};
- map.enableSequences = function (enable) {
- sequences.enable(enable);
- };
-
return d3.rebind(map, dispatch, 'on');
};
diff --git a/js/id/renderer/mapillary_layer.js b/js/id/renderer/mapillary_layer.js
new file mode 100644
index 000000000..4213fc8f7
--- /dev/null
+++ b/js/id/renderer/mapillary_layer.js
@@ -0,0 +1,159 @@
+iD.MapillaryLayer = function (context) {
+ var enable = false,
+ currentImage,
+ svg, div, request;
+
+ function show(image) {
+ svg.selectAll('g')
+ .classed('selected', function(d) {
+ return currentImage && d.key === currentImage.key;
+ });
+
+ div.classed('hidden', false)
+ .classed('temp', image !== currentImage);
+
+ div.selectAll('img')
+ .attr('src', 'https://d1cuyjsrcm0gby.cloudfront.net/' + image.key + '/thumb-320.jpg');
+
+ div.selectAll('a')
+ .attr('href', 'http://mapillary.com/map/im/' + image.key);
+ }
+
+ function hide() {
+ currentImage = undefined;
+
+ svg.selectAll('g')
+ .classed('selected', false);
+
+ div.classed('hidden', true);
+ }
+
+ function transform(image) {
+ var t = 'translate(' + context.projection(image.loc) + ')';
+ if (image.ca) t += 'rotate(' + image.ca + ',0,0)';
+ return t;
+ }
+
+ function render(selection) {
+ svg = selection.selectAll('svg')
+ .data([0]);
+
+ svg.enter().append('svg')
+ .on('click', function() {
+ var image = d3.event.target.__data__;
+ if (currentImage === image) {
+ hide();
+ } else {
+ currentImage = image;
+ show(image);
+ }
+ })
+ .on('mouseover', function() {
+ show(d3.event.target.__data__);
+ })
+ .on('mouseout', function() {
+ if (currentImage) {
+ show(currentImage);
+ } else {
+ hide();
+ }
+ });
+
+ svg.style('display', enable ? 'block' : 'none');
+
+ div = context.container().selectAll('.mapillary-image')
+ .data([0]);
+
+ var enter = div.enter().append('div')
+ .attr('class', 'mapillary-image');
+
+ enter.append('button')
+ .on('click', hide)
+ .append('div')
+ .attr('class', 'icon close');
+
+ enter.append('img');
+
+ var link = enter.append('a')
+ .attr('class', 'link')
+ .attr('target', '_blank');
+
+ link.append('span')
+ .attr('class', 'icon icon-pre-text out-link');
+
+ link.append('span')
+ .text(t('mapillary.view_on_mapillary'));
+
+ if (!enable) {
+ hide();
+
+ svg.selectAll('g')
+ .remove();
+
+ return;
+ }
+
+ // 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('https://mapillary-read-api.herokuapp.com/v1/s/search?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',
+ function (error, data) {
+ if (error) return;
+
+ var images = [];
+
+ for (var i = 0; i < data.features.length; i++) {
+ var sequence = data.features[i];
+ for (var j = 0; j < sequence.geometry.coordinates.length; j++) {
+ images.push({
+ key: sequence.properties.keys[j],
+ ca: sequence.properties.cas[j],
+ loc: sequence.geometry.coordinates[j]
+ });
+ if (images.length >= 1000) break;
+ }
+ }
+
+ var g = svg.selectAll('g')
+ .data(images, function(d) { return d.key; });
+
+ var enter = g.enter().append('g')
+ .attr('class', 'image');
+
+ enter.append('path')
+ .attr('d', 'M 0,-5 l 0,-20 l -5,30 l 10,0 l -5,-30');
+
+ enter.append('circle')
+ .attr('dx', '0')
+ .attr('dy', '0')
+ .attr('r', '8');
+
+ g.attr('transform', transform);
+
+ g.exit()
+ .remove();
+ });
+ }
+
+ render.enable = function(_) {
+ if (!arguments.length) return enable;
+ enable = _;
+ return render;
+ };
+
+ render.dimensions = function(_) {
+ if (!arguments.length) return svg.dimensions();
+ svg.dimensions(_);
+ return render;
+ };
+
+ return render;
+};
diff --git a/js/id/svg/sequences.js b/js/id/svg/sequences.js
deleted file mode 100644
index 29b620c04..000000000
--- a/js/id/svg/sequences.js
+++ /dev/null
@@ -1,100 +0,0 @@
-iD.svg.Sequences = function (projection, context) {
- var surface, enabled = false;
-
- function drawSequences(_surface) {
- surface = _surface;
-
- if (enabled) {
- drawSequences.reloadMapillaryImages();
- } else {
- drawSequences.removeAll();
- }
- }
-
- drawSequences.removeAll = function () {
- var hit_layer = surface.select('.layer-hit');
- if (hit_layer) {
- hit_layer.selectAll('g.image').remove();
- hit_layer.selectAll('g.sequence').remove();
- }
- };
-
- drawSequences.enable = function (enable) {
- enabled = enable;
- drawSequences(surface);
- };
-
- drawSequences.plotSequences = function (surface, context, sequences) {
- var imagePoints = drawSequences.images(sequences, 1000);
- var images = surface.select('.layer-hit').selectAll('g.image')
- .data(imagePoints);
- var pointTransform = iD.svg.PointTransform(context.projection);
-
- var image = images.enter()
- .append('g')
- .attr('class', 'image point')
- .attr('transform', function (d) {
- var translate = pointTransform({ loc: d.geometry.coordinates });
- if (d.properties.ca) {
- return translate + 'rotate(' + d.properties.ca + ',0,0)';
- }
- return translate;
- });
-
- image.append('path')
- .call(drawSequences.markerPath, 'stroke');
-
- image.append('circle')
- .attr('dx', '0')
- .attr('dy', '0')
- .attr('r', '8');
-
- // Selecting the following implicitly
- // sets the data (point entity) on the element
- images.select('.shadow');
- images.select('.stroke');
- };
-
- drawSequences.reloadMapillaryImages = function () {
- var extent = context.map().extent();
- d3.json('https://mapillary-read-api.herokuapp.com/v1/s/search?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', function (error, data) {
- drawSequences.plotSequences(context.surface(), context, data);
- });
- };
-
- drawSequences.images = function (sequences, limit) {
- var images = [];
-
- for (var i = 0; i < sequences.features.length; i++) {
- var sequence = sequences.features[i];
- for (var j = 0; j < sequence.geometry.coordinates.length; j++) {
- images.push({
- geometry: {
- type: 'Point',
- coordinates: sequence.geometry.coordinates[j]
- },
- properties: {
- key: sequence.properties.keys[j],
- ca: sequence.properties.cas[j],
- entityType: 'image'
- }
- });
- if (limit && images.length >= limit) break;
- }
- }
-
- return images;
- };
-
- drawSequences.markerPath = function (selection, klass) {
- selection
- .attr('class', klass)
- .attr('transform', 'translate(0, 0)')
- .attr('d', 'M 0,-5 l 0,-20 l -5,30 l 10,0 l -5,-30');
- };
-
- return drawSequences;
-};
diff --git a/js/id/ui.js b/js/id/ui.js
index 614858cf5..eacf19f8f 100644
--- a/js/id/ui.js
+++ b/js/id/ui.js
@@ -31,10 +31,6 @@ iD.ui = function(context) {
var m = content.append('div')
.attr('id', 'map')
.call(map);
- content.append('div')
- .attr('id', 'mapillaryImage')
- .classed('hidden', true)
- .call(iD.ui.ImageView(context));
bar.append('div')
.attr('class', 'spacer col4');
diff --git a/js/id/ui/background.js b/js/id/ui/background.js
index 2cf0b2c4d..9e88acef7 100644
--- a/js/id/ui/background.js
+++ b/js/id/ui/background.js
@@ -77,6 +77,11 @@ iD.ui.Background = function(context) {
update();
}
+ function clickMapillary() {
+ context.background().toggleMapillaryLayer();
+ update();
+ }
+
function drawList(layerList, type, change, filter) {
var sources = context.background()
.sources(context.map().extent())
@@ -124,6 +129,13 @@ iD.ui.Background = function(context) {
.property('disabled', !hasGpx)
.property('checked', showsGpx);
+ var showsMapillary = context.background().showsMapillaryLayer();
+
+ mapillaryLayerItem
+ .classed('active', showsMapillary)
+ .selectAll('input')
+ .property('checked', showsMapillary);
+
selectLayer();
var source = context.background().baseLayerSource();
@@ -266,26 +278,16 @@ iD.ui.Background = function(context) {
var mapillaryLayerItem = overlayList.append('li');
- var mapillaryLabel = mapillaryLayerItem.append('label')
+ label = mapillaryLayerItem.append('label')
.call(bootstrap.tooltip()
- .title(t('modes.selectImage.description'))
+ .title(t('mapillary.tooltip'))
.placement('top'));
- mapillaryLabel.append('input')
+ label.append('input')
.attr('type', 'checkbox')
- .attr('id', 'select_image_checkbox')
- .on('change', function(){
- if (this.checked) {
- mapillaryLayerItem.classed('active',true);
- context.enter(iD.modes.SelectImage(context));
- } else {
- mapillaryLayerItem.classed('active',false);
- context.enter(iD.modes.Browse(context));
- }
- update();
- });
+ .on('change', clickMapillary);
- mapillaryLabel.append('span')
+ label.append('span')
.text(t('mapillary.title'));
var gpxLayerItem = content.append('ul')
diff --git a/js/id/ui/image_view.js b/js/id/ui/image_view.js
deleted file mode 100644
index a8a67b20d..000000000
--- a/js/id/ui/image_view.js
+++ /dev/null
@@ -1,50 +0,0 @@
-iD.ui.ImageView = function (context) {
- function imageView() { }
-
- imageView.showEmpty = function () {
- var imageWrapper = context.container()
- .select('#mapillaryImage');
-
- imageWrapper.html('');
-
- var content = imageWrapper
- .append('div');
-
- content.append('div')
- .on('click', function(){
- imageWrapper.classed('hidden', true);
- });
-
- content.append('div')
- .html(marked(t('mapillary.no_image_found')));
- };
-
- imageView.show = function (imageToShow) {
- var key = imageToShow.properties.key;
- var imageWrapper = context.container().select('#mapillaryImage');
- imageWrapper.classed('hidden', false);
- imageWrapper.html('');
- var content = imageWrapper
- .append('div');
- content.append('button')
- .on('click', function(){
- imageWrapper.classed('hidden', true);
- })
- .append('div')
- .attr('class', 'icon close');
- var wrap = content.append('div');
- wrap.append('div')
- .append('img')
- .attr('src', 'https://d1cuyjsrcm0gby.cloudfront.net/KEY/thumb-320.jpg'.replace('KEY', key));
- var wrapLink = wrap.append('a')
- .attr('class', 'link')
- .attr('target', '_blank')
- .attr('src', 'http://mapillary.com/map/im/KEY'.replace('KEY', key));
- wrapLink.append('span')
- .attr('class','icon icon-pre-text out-link');
- wrapLink.append('span')
- .text(t('mapillary.view_on_mapillary'));
- };
-
- return imageView;
-};
diff --git a/test/index.html b/test/index.html
index 02acf487d..81cc2ae67 100644
--- a/test/index.html
+++ b/test/index.html
@@ -55,12 +55,12 @@
+
-
@@ -72,7 +72,6 @@
-
@@ -166,7 +165,6 @@
-
diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js
index 82177f034..707c1687d 100644
--- a/test/spec/behavior/hash.js
+++ b/test/spec/behavior/hash.js
@@ -5,6 +5,7 @@ describe('iD.behavior.Hash', function () {
beforeEach(function () {
context = iD();
+ context.container(d3.select(document.createElement('div')));
// Neuter connection
context.connection().loadTiles = function () {};
diff --git a/test/spec/behavior/lasso.js b/test/spec/behavior/lasso.js
index bf92f04d9..c0bf77a74 100644
--- a/test/spec/behavior/lasso.js
+++ b/test/spec/behavior/lasso.js
@@ -3,6 +3,7 @@ describe("iD.behavior.Lasso", function () {
beforeEach(function () {
context = iD();
+ context.container(d3.select(document.createElement('div')));
// Neuter connection
context.connection().loadTiles = function () {};
diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js
index f00ce1f28..663030246 100644
--- a/test/spec/renderer/map.js
+++ b/test/spec/renderer/map.js
@@ -1,8 +1,10 @@
describe('iD.Map', function() {
- var map;
+ var context, map;
beforeEach(function() {
- map = iD().map();
+ context = iD();
+ context.container(d3.select(document.createElement('div')));
+ map = context.map();
d3.select(document.createElement('div'))
.call(map);
});