From 4d0a42344db8009ed29bd360c75e53ac41381d7b Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Mon, 4 Feb 2013 19:47:25 -0500 Subject: [PATCH 01/25] Start dynamic layers work in branch --- data/imagery.json | 45 ++++++++++++++++++++--------- data/imagery_convert.js | 37 +++++++++++++++++++++++- index.html | 7 +++-- js/id/id.js | 5 +++- js/id/renderer/background_source.js | 29 +++++++------------ js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 38 ++++++++---------------- 7 files changed, 100 insertions(+), 62 deletions(-) create mode 100644 js/id/renderer/layers.js diff --git a/data/imagery.json b/data/imagery.json index 2055c1cf5..44d62cb97 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -2,6 +2,12 @@ { "name": "Bing aerial imagery", "url": "http://ecn.t0.tiles.virtualearth.net/tiles/a{q}uadkey.jpeg?g=587&mkt=en-gb&n=z", + "description": "Satellite imagery.", + "scaleExtent": [ + 0, + 20 + ], + "default": "yes", "sourcetag": "Bing", "logo": "bing_maps.png", "logo_url": "http://www.bing.com/maps", @@ -10,6 +16,11 @@ { "name": "MapBox Satellite", "url": "http://{t}.tiles.mapbox.com/v3/openstreetmap.map-4wvf9l0l/{z}/{x}/{y}.png", + "description": "Satellite and aerial imagery", + "scaleExtent": [ + 0, + 16 + ], "subdomains": [ "a", "b", @@ -17,26 +28,19 @@ ] }, { - "name": "MapQuest Open Aerial", - "url": "http://oatile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" - }, - { - "name": "OSM - Mapnik", + "name": "OpenStreetMap", "url": "http://{t}.tile.openstreetmap.org/{z}/{x}/{y}.png", + "description": "The default OpenStreetMap layer.", + "scaleExtent": [ + 0, + 18 + ], "subdomains": [ "a", "b", "c" ] }, - { - "name": "OSM - OpenCycleMap", - "url": "http://tile.opencyclemap.org/cycle/{z}/{x}/{y}.png" - }, - { - "name": "OSM - MapQuest", - "url": "http://otile1.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg" - }, { "name": "OSM - Tiger Edited Map", "url": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", @@ -70,6 +74,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -85,6 +94,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", @@ -100,6 +114,11 @@ { "name": "OSM US TIGER 2012 Roads Overlay", "url": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", + "description": "Public domain road data from the US Government.", + "scaleExtent": [ + 0, + 17 + ], "subdomains": [ "a", "b", diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 16cc23317..a7253aee4 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -5,6 +5,32 @@ $ = cheerio.load(fs.readFileSync('imagery.xml')); var imagery = []; +// CENSORSHIP! No, these are just layers that essentially duplicate other layers +// or which have no clear use case. +var censor = { + 'MapQuest Open Aerial': true, + 'OSM - OpenCycleMap': true, + 'OSM - MapQuest': true +}; + +var replace = { + 'OSM - Mapnik': 'OpenStreetMap' +}; + +var description = { + 'MapBox Satellite': 'Satellite and aerial imagery', + 'OpenStreetMap': 'The default OpenStreetMap layer.', + 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', + 'Bing aerial imagery': 'Satellite imagery.' +}; + +var scaleExtent = { + 'MapBox Satellite': [0, 16], + 'OpenStreetMap': [0, 18], + 'OSM US TIGER 2012 Roads Overlay': [0, 17], + 'Bing aerial imagery': [0, 20] +}; + $('set').each(function(i) { var elem = $(this); @@ -13,6 +39,14 @@ $('set').each(function(i) { url: $(this).find('url').first().text() }; + if (censor[im.name]) return; + + if (replace[im.name]) im.name = replace[im.name]; + + if (description[im.name]) im.description = description[im.name]; + + if (scaleExtent[im.name]) im.scaleExtent = scaleExtent[im.name]; + var subdomains = []; im.url = im.url @@ -34,7 +68,7 @@ $('set').each(function(i) { +elem.attr('maxlon')]; } - ['sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { + ['default', 'sourcetag', 'logo', 'logo_url', 'terms_url'].forEach(function(a) { if (elem.find(a).length) { im[a] = elem.find(a).first().text(); } @@ -43,3 +77,4 @@ $('set').each(function(i) { }); fs.writeFileSync('imagery.json', JSON.stringify(imagery, null, 4)); +fs.writeFileSync('imagery.js', 'iD.data.imagery = ' + JSON.stringify(imagery, null, 4) + ';'); diff --git a/index.html b/index.html index c03ac29ae..3be283a1f 100644 --- a/index.html +++ b/index.html @@ -32,12 +32,17 @@ + + + + + @@ -135,8 +140,6 @@ - -
+ + + + + @@ -132,8 +137,6 @@ - - + diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js new file mode 100644 index 000000000..8ba2a2318 --- /dev/null +++ b/js/id/actions/merge.js @@ -0,0 +1,35 @@ +iD.actions.Merge = function(ids) { + function groupEntitiesByGeometry(graph) { + var entities = ids.map(function(id) { return graph.entity(id); }); + return _.extend({point: [], area: []}, _.groupBy(entities, function(entity) { return entity.geometry(graph); })); + } + + var action = function(graph) { + var geometries = groupEntitiesByGeometry(graph), + area = geometries['area'][0], + points = geometries['point']; + + points.forEach(function (point) { + area = area.mergeTags(point.tags); + + graph.parentRelations(point).forEach(function (parent) { + graph = graph.replace(parent.replaceMember(point, area)); + }); + + graph = graph.remove(point); + }); + + graph = graph.replace(area); + + return graph; + }; + + action.enabled = function(graph) { + var geometries = groupEntitiesByGeometry(graph); + return geometries['area'].length === 1 && + geometries['point'].length > 0 && + (geometries['area'].length + geometries['point'].length) === ids.length; + }; + + return action; +}; diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index b2073db52..6202f1b3f 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -1,18 +1,28 @@ iD.operations.Merge = function(selection, context) { - var action = iD.actions.Join(selection); + var join = iD.actions.Join(selection), + merge = iD.actions.Merge(selection); var operation = function() { var annotation = t('operations.merge.annotation', {n: selection.length}), - difference = context.perform(action, annotation); + action; + + if (join.enabled(context.graph())) { + action = join; + } else { + action = merge; + } + + var difference = context.perform(action, annotation); context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { - return selection.length > 1; + return selection.length >= 2; }; operation.enabled = function() { - return action.enabled(context.graph()); + return join.enabled(context.graph()) || + merge.enabled(context.graph()); }; operation.id = "merge"; diff --git a/test/index.html b/test/index.html index 3f2eba72e..ecad5ca75 100644 --- a/test/index.html +++ b/test/index.html @@ -80,6 +80,7 @@ + @@ -157,6 +158,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 8dd73d666..f85ac1f0b 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -42,6 +42,7 @@ + diff --git a/test/spec/actions/merge.js b/test/spec/actions/merge.js new file mode 100644 index 000000000..5ea0b3dea --- /dev/null +++ b/test/spec/actions/merge.js @@ -0,0 +1,20 @@ +describe("iD.actions.Merge", function () { + it("merges multiple points to an area", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a', tags: {a: 'a'}}), + 'b': iD.Node({id: 'b', tags: {b: 'b'}}), + 'w': iD.Way({id: 'w', tags: {area: 'yes'}}), + 'r': iD.Relation({id: 'r', members: [{id: 'a', role: 'r', type: 'node'}]}) + }), + action = iD.actions.Merge(['a', 'b', 'w']); + + expect(action.enabled(graph)).to.be.true; + + graph = action(graph); + + expect(graph.entity('a')).to.be.undefined; + expect(graph.entity('b')).to.be.undefined; + expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'}); + expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]); + }); +}); From 104b02eda110844c9b2c84359e2668367f30c0df Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:04:19 -0800 Subject: [PATCH 08/25] Move common setup to spec_helpers.js --- test/index.html | 9 --------- test/index_packaged.html | 9 --------- test/spec/spec_helpers.js | 9 +++++++++ 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/test/index.html b/test/index.html index ecad5ca75..87d799279 100644 --- a/test/index.html +++ b/test/index.html @@ -134,15 +134,6 @@ - - diff --git a/test/index_packaged.html b/test/index_packaged.html index f85ac1f0b..778231edd 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -18,15 +18,6 @@ - - diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index b8ebdf307..7a62e76b9 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -1,3 +1,12 @@ +iD.debug = true; + +mocha.setup({ + ui: 'bdd', + globals: ['__onresize.tail-size', '__onmousemove.zoom', '__onmouseup.zoom', '__onclick.draw'] +}); + +var expect = chai.expect; + chai.use(function (chai, utils) { var flag = utils.flag; From e868c071ac329accc8cfa19d4db800186660d536 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:16:00 -0500 Subject: [PATCH 09/25] Remove more unreliable or slow layers, select the right layer initially, fix null tooltips --- css/app.css | 6 +++ data/imagery.js | 77 ++++++--------------------------------- data/imagery.json | 77 ++++++--------------------------------- data/imagery_convert.js | 10 ++++- js/id/renderer/layers.js | 1 + js/id/ui/layerswitcher.js | 8 +++- 6 files changed, 44 insertions(+), 135 deletions(-) diff --git a/css/app.css b/css/app.css index a43133f3d..ef42bb28c 100644 --- a/css/app.css +++ b/css/app.css @@ -186,6 +186,8 @@ ul.toggle-list li a { border-top: 1px solid white; display:block; border-top: 1px solid rgba(0, 0, 0, .5); + text-wrap:no-wrap; + overflow:hidden; } ul.toggle-list li a:hover { background-color: #ececec;} @@ -687,6 +689,10 @@ a.selected:hover .toggle.icon { background-position: -40px -180px;} top:190px; } +.layerswitcher-control .map-overlay { + width:250px; +} + .nudge-container { margin-top: 10px; } diff --git a/data/imagery.js b/data/imagery.js index fb6f3f020..6c232b626 100644 --- a/data/imagery.js +++ b/data/imagery.js @@ -42,55 +42,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ iD.data.imagery = [ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ iD.data.imagery = [ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ iD.data.imagery = [ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ iD.data.imagery = [ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery.json b/data/imagery.json index e4497fc9d..ee9906d31 100644 --- a/data/imagery.json +++ b/data/imagery.json @@ -42,55 +42,8 @@ ] }, { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -124.81, - 24.055 - ], - [ - -66.865, - 49.386 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -179.754, - 50.858 - ], - [ - -129.899, - 71.463 - ] - ] - }, - { - "name": "OSM - Tiger Edited Map", - "template": "http://tiger-osm.mapquest.com/tiles/1.0.0/tiger/{z}/{x}/{y}.png", - "extent": [ - [ - -174.46, - 18.702 - ], - [ - -154.516, - 26.501 - ] - ] - }, - { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -108,13 +61,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -132,13 +80,8 @@ ] }, { - "name": "OSM US TIGER 2012 Roads Overlay", + "name": " TIGER 2012 Roads Overlay", "template": "http://{t}.tile.openstreetmap.us/tiger2012_roads_expanded/{z}/{x}/{y}.png", - "description": "Public domain road data from the US Government.", - "scaleExtent": [ - 0, - 17 - ], "subdomains": [ "a", "b", @@ -156,7 +99,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -175,7 +118,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -194,7 +137,7 @@ ] }, { - "name": "OSM US USGS Topographic Maps", + "name": " USGS Topographic Maps", "template": "http://{t}.tile.openstreetmap.us/usgs_scanned_topos/{z}/{x}/{y}.png", "subdomains": [ "a", @@ -213,7 +156,7 @@ ] }, { - "name": "OSM US USGS Large Scale Aerial Imagery", + "name": " USGS Large Scale Aerial Imagery", "template": "http://{t}.tile.openstreetmap.us/usgs_large_scale/{z}/{x}/{y}.jpg", "subdomains": [ "a", @@ -453,8 +396,9 @@ "sourcetag": "Haiti streetnames" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -125.8, @@ -468,8 +412,9 @@ "sourcetag": "NAIP" }, { - "name": "National Agriculture Imagery Program", + "name": "NAIP", "template": "http://cube.telascience.org/tilecache/tilecache.py/NAIP_ALL/{z}/{x}/{y}.png", + "description": "National Agriculture Imagery Program", "extent": [ [ -168.5, diff --git a/data/imagery_convert.js b/data/imagery_convert.js index 5dc36c502..abc5cbef6 100644 --- a/data/imagery_convert.js +++ b/data/imagery_convert.js @@ -14,14 +14,16 @@ var censor = { }; var replace = { - 'OSM - Mapnik': 'OpenStreetMap' + 'OSM - Mapnik': 'OpenStreetMap', + 'National Agriculture Imagery Program': 'NAIP' }; var description = { 'MapBox Satellite': 'Satellite and aerial imagery', 'OpenStreetMap': 'The default OpenStreetMap layer.', 'OSM US TIGER 2012 Roads Overlay': 'Public domain road data from the US Government.', - 'Bing aerial imagery': 'Satellite imagery.' + 'Bing aerial imagery': 'Satellite imagery.', + 'NAIP': 'National Agriculture Imagery Program' }; var scaleExtent = { @@ -39,8 +41,12 @@ $('set').each(function(i) { template: $(this).find('url').first().text() }; + // no luck with mapquest servers currently... + if (im.template.match(/mapquest/g)) return; if (censor[im.name]) return; + im.name = im.name.replace('OSM US', ''); + if (replace[im.name]) im.name = replace[im.name]; if (description[im.name]) im.description = description[im.name]; diff --git a/js/id/renderer/layers.js b/js/id/renderer/layers.js index 773e8011e..966e16bb8 100644 --- a/js/id/renderer/layers.js +++ b/js/id/renderer/layers.js @@ -4,6 +4,7 @@ iD.layers.push((function() { function custom() { var template = window.prompt('Enter a tile template. Valid tokens are {z}, {x}, {y} for Z/X/Y scheme and {u} for quadtile scheme.'); if (!template) return null; + if (template.match(/google/g)) return null; return iD.BackgroundSource.template({ template: template, name: 'Custom (customized)' diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 88ee45f49..f788322b1 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -122,10 +122,16 @@ iD.ui.layerswitcher = function(context) { .text(function(d) { return d.data.name; }) - .call(bootstrap.tooltip().placement('right')) + .each(function(d) { + // only set tooltips for layers with tooltips + if (d.data.description) { + d3.select(this).call(bootstrap.tooltip().placement('right')); + } + }) .on('click.set-source', clickSetSource) .insert('span') .attr('class','icon toggle'); + selectLayer(context.background().source()); } context.map().on('move.layerswitcher-update', _.debounce(update, 1000)); From f6e726bcd68dd2f0a43d14e407794eed0b213978 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 14:31:11 -0800 Subject: [PATCH 10/25] Join should run Reverse where necessary (fixes #652) --- js/id/actions/join.js | 6 ++++-- test/spec/actions/join.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/js/id/actions/join.js b/js/id/actions/join.js index 7bba4783c..267b6c75a 100644 --- a/js/id/actions/join.js +++ b/js/id/actions/join.js @@ -24,7 +24,8 @@ iD.actions.Join = function(ids) { // a <-- b ==> c // Expected result: // a <-- b <-- c - nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = b.nodes.slice().concat(a.nodes.slice(1)); } else if (a.first() === b.last()) { // a <-- b <== c @@ -42,7 +43,8 @@ iD.actions.Join = function(ids) { // a --> b <== c // Expected result: // a --> b --> c - nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + b = iD.actions.Reverse(idB)(graph).entity(idB); + nodes = a.nodes.concat(b.nodes.slice().slice(1)); } graph.parentRelations(b).forEach(function (parent) { diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js index 11a95bc2d..6951013d3 100644 --- a/test/spec/actions/join.js +++ b/test/spec/actions/join.js @@ -112,13 +112,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['b', 'a']}), - '=': iD.Way({id: '=', nodes: ['b', 'c']}) + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("joins a --> b <== c", function () { @@ -130,13 +131,14 @@ describe("iD.actions.Join", function () { 'b': iD.Node({id: 'b'}), 'c': iD.Node({id: 'c'}), '-': iD.Way({id: '-', nodes: ['a', 'b']}), - '=': iD.Way({id: '=', nodes: ['c', 'b']}) + '=': iD.Way({id: '=', nodes: ['c', 'b'], tags: {'lanes:forward': 2}}) }); graph = iD.actions.Join(['-', '='])(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('=')).to.be.undefined; + expect(graph.entity('-').tags).to.eql({'lanes:backward': 2}); }); it("merges tags", function () { From 4b5dcd054e13667a3a456a75a722087791fcd6fc Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 17:34:41 -0500 Subject: [PATCH 11/25] Fix sourcetag regression --- css/app.css | 1 - js/id/ui.js | 5 +++-- js/id/ui/layerswitcher.js | 24 ++++++++++++++++++------ 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/css/app.css b/css/app.css index ef42bb28c..65f98dfd6 100644 --- a/css/app.css +++ b/css/app.css @@ -93,7 +93,6 @@ a:hover { color:#597be7; } - textarea, input[type=text] { background-color: white; diff --git a/js/id/ui.js b/js/id/ui.js index ace66703d..747bccb70 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -161,8 +161,9 @@ iD.ui = function(context) { var imagery = linkList.append('li').attr('id', 'attribution'); imagery.append('span').text('imagery'); - imagery.append('a').attr('target', '_blank') - .attr('href', 'http://opengeodata.org/microsoft-imagery-details').text(' provided by bing'); + imagery + .append('span') + .attr('class', 'provided-by'); linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') .text('dev') diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index f788322b1..b73ede9d3 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -38,7 +38,7 @@ iD.ui.layerswitcher = function(context) { selection.on('click.layerswitcher-inside', function() { return d3.event.stopPropagation(); }); - d3.select('body').on('click.layerswitcher-outside', hide); + context.container().on('click.layerswitcher-outside', hide); } var opa = content @@ -76,16 +76,28 @@ iD.ui.layerswitcher = function(context) { .style('opacity', String); // Make sure there is an active selection by default - d3.select('.opacity-options li:nth-child(2)').classed('selected', true); + opa.select('.opacity-options li:nth-child(2)').classed('selected', true); function selectLayer(d) { + content.selectAll('a.layer') .classed('selected', function(d) { return d === context.background().source(); }); - d3.select('#attribution a') - .attr('href', d.data.link) - .text('provided by ' + d.data.name); + + var provided_by = context.container().select('#attribution .provided-by') + .html(''); + + if (d.data.terms_url) { + provided_by.append('a') + .attr('href', (d.data.terms_url || '')) + .classed('disabled', !d.data.terms_url) + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } else { + provided_by + .text(' provided by ' + (d.data.sourcetag || d.data.name)); + } + } function clickSetSource(d) { @@ -96,7 +108,7 @@ iD.ui.layerswitcher = function(context) { d = configured; } context.background().source(d); - context.history().imagery_used(d.name); + context.history().imagery_used(d.data.sourcetag || d.data.name); context.redraw(); selectLayer(d); } From 2ca4387f741164d2b205d87b0751aef65579c210 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 17:42:51 -0500 Subject: [PATCH 12/25] Fix snapping nodes to areas In the future it may be good to use shadow paths, but at this point there isn't really a reason to add thousands of additional paths. --- js/id/behavior/drag_node.js | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index acba7919d..e3028953d 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -84,7 +84,11 @@ iD.behavior.DragNode = function(context) { if (d.type === 'node' && d.id !== entity.id) { loc = d.loc; } else if (d.type === 'way') { - loc = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context).loc; + var point = d3.mouse(context.surface().node()), + index = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(index.loc)) < 10) { + loc = index.loc; + } } context.replace(iD.actions.MoveNode(entity.id, loc)); @@ -100,13 +104,18 @@ iD.behavior.DragNode = function(context) { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); - context.replace( - iD.actions.MoveNode(entity.id, choice.loc), - iD.actions.AddVertex(d.id, entity.id, choice.index), - connectAnnotation(d)); + var point = d3.mouse(context.surface().node()), + choice = iD.geo.chooseIndex(d, point, context); + if (iD.geo.dist(point, context.projection(choice.loc)) < 10) { + context.replace( + iD.actions.MoveNode(entity.id, choice.loc), + iD.actions.AddVertex(d.id, entity.id, choice.index), + connectAnnotation(d)); + return; + } + } - } else if (d.type === 'node' && d.id !== entity.id) { + if (d.type === 'node' && d.id !== entity.id) { context.replace( iD.actions.Connect([entity.id, d.id]), connectAnnotation(d)); From ce53a9233a09dd506fdced44c2ec0b430901a25d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Wed, 6 Feb 2013 18:11:50 -0500 Subject: [PATCH 13/25] Remove unscoped d3.select --- js/id/ui/save.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 905a43b11..38bba8518 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -30,7 +30,7 @@ iD.ui.save = function(context) { function click() { function commit(e) { - d3.select('.shaded').remove(); + context.container().select('.shaded').remove(); var l = iD.ui.loading(context.container(), t('uploading_changes'), true); connection.putChangeset(history.changes(), From 246481ad9262ea2a71316fa55ebd04cf24bc071b Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 18:53:46 -0500 Subject: [PATCH 14/25] Switch back to mouseup, and block following click --- js/id/behavior/draw.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index b0da771a4..6b1b7ff0a 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -27,13 +27,19 @@ iD.behavior.Draw = function(context) { target.on('mousemove.draw', null); - d3.select(window).on('click.draw', function() { + d3.select(window).on('mouseup.draw', function() { target.on('mousemove.draw', mousemove); if (iD.geo.dist(pos, point()) < closeTolerance || (iD.geo.dist(pos, point()) < tolerance && (+new Date() - time) < 500)) { click(); } + if (target.node() === d3.event.target) { + d3.select(window).on('click.draw', function() { + d3.select(window).on('click.draw', null); + d3.event.stopPropagation(); + }); + } }); } @@ -110,7 +116,7 @@ iD.behavior.Draw = function(context) { .on('mousedown.draw', null) .on('mousemove.draw', null); - d3.select(window).on('click.draw', null); + d3.select(window).on('mouseup.draw', null); d3.select(document) .call(keybinding.off) From 0af51a0ef6f542ea1b4443a3809bfb14a792e966 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 6 Feb 2013 15:58:13 -0800 Subject: [PATCH 15/25] Improvements to Split * Split a closed way at selected and antipode point (fixes #651) * Split an area into a multipolygon (fixes #572) --- js/id/actions/split.js | 69 +++++++++++++++++++++++++++----------- test/spec/actions/split.js | 60 +++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 24f2b4318..ea2100238 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -15,37 +15,55 @@ iD.actions.Split = function(nodeId, newWayId) { parents = graph.parentWays(node); return parents.filter(function (parent) { - return parent.first() !== nodeId && - parent.last() !== nodeId; + return parent.isClosed() || + (parent.first() !== nodeId && + parent.last() !== nodeId); }); } var action = function(graph) { - if (!action.enabled(graph)) - return graph; + var wayA = candidateWays(graph)[0], + wayB = iD.Way({id: newWayId, tags: wayA.tags}), + nodesA, + nodesB, + isArea = wayA.isArea(); - var way = candidateWays(graph)[0], - idx = _.indexOf(way.nodes, nodeId); + if (wayA.isClosed()) { + var nodes = wayA.nodes.slice(0, -1), + idxA = _.indexOf(nodes, nodeId), + idxB = idxA + Math.floor(nodes.length / 2); - // Create a 'b' way that contains all of the tags in the second - // half of this way - var newWay = iD.Way({id: newWayId, tags: way.tags, nodes: way.nodes.slice(idx)}); - graph = graph.replace(newWay); + if (idxB >= nodes.length) { + idxB %= nodes.length; + nodesA = nodes.slice(idxA).concat(nodes.slice(0, idxB + 1)); + nodesB = nodes.slice(idxB, idxA + 1); + } else { + nodesA = nodes.slice(idxA, idxB + 1); + nodesB = nodes.slice(idxB).concat(nodes.slice(0, idxA + 1)); + } + } else { + var idx = _.indexOf(wayA.nodes, nodeId); + nodesA = wayA.nodes.slice(0, idx + 1); + nodesB = wayA.nodes.slice(idx); + } - // Reduce the original way to only contain the first set of nodes - graph = graph.replace(way.update({nodes: way.nodes.slice(0, idx + 1)})); + wayA = wayA.update({nodes: nodesA}); + wayB = wayB.update({nodes: nodesB}); - graph.parentRelations(way).forEach(function(relation) { + graph = graph.replace(wayA); + graph = graph.replace(wayB); + + graph.parentRelations(wayA).forEach(function(relation) { if (relation.isRestriction()) { var via = relation.memberByRole('via'); - if (via && newWay.contains(via.id)) { - relation = relation.updateMember({id: newWay.id}, relation.memberById(way.id).index); + if (via && wayB.contains(via.id)) { + relation = relation.updateMember({id: wayB.id}, relation.memberById(wayA.id).index); graph = graph.replace(relation); } } else { - var role = relation.memberById(way.id).role, - last = newWay.last(), - i = relation.memberById(way.id).index, + var role = relation.memberById(wayA.id).role, + last = wayB.last(), + i = relation.memberById(wayA.id).index, j; for (j = 0; j < relation.members.length; j++) { @@ -55,11 +73,24 @@ iD.actions.Split = function(nodeId, newWayId) { } } - relation = relation.addMember({id: newWay.id, type: 'way', role: role}, i <= j ? i + 1 : i); + relation = relation.addMember({id: wayB.id, type: 'wayA', role: role}, i <= j ? i + 1 : i); graph = graph.replace(relation); } }); + if (isArea) { + var multipolygon = iD.Relation({ + tags: _.extend({}, wayA.tags, {type: 'multipolygon'}), + members: [ + {id: wayA.id, role: 'outer', type: 'way'}, + {id: wayB.id, role: 'outer', type: 'way'} + ]}); + + graph = graph.replace(multipolygon); + graph = graph.replace(wayA.update({tags: {}})); + graph = graph.replace(wayB.update({tags: {}})); + } + return graph; }; diff --git a/test/spec/actions/split.js b/test/spec/actions/split.js index 5ee68e67e..417525941 100644 --- a/test/spec/actions/split.js +++ b/test/spec/actions/split.js @@ -99,6 +99,66 @@ describe("iD.actions.Split", function () { expect(graph.entity('|').nodes).to.eql(['d', 'b']); }); + it("splits a closed way at the given point and its antipode", function () { + // Situation: + // a ---- b + // | | + // d ---- c + // + // Split at a. + // + // Expected result: + // a ---- b + // || | + // d ==== c + // + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + var g1 = iD.actions.Split('a', '=')(graph); + expect(g1.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(g1.entity('=').nodes).to.eql(['c', 'd', 'a']); + + var g2 = iD.actions.Split('b', '=')(graph); + expect(g2.entity('-').nodes).to.eql(['b', 'c', 'd']); + expect(g2.entity('=').nodes).to.eql(['d', 'a', 'b']); + + var g3 = iD.actions.Split('c', '=')(graph); + expect(g3.entity('-').nodes).to.eql(['c', 'd', 'a']); + expect(g3.entity('=').nodes).to.eql(['a', 'b', 'c']); + + var g4 = iD.actions.Split('d', '=')(graph); + expect(g4.entity('-').nodes).to.eql(['d', 'a', 'b']); + expect(g4.entity('=').nodes).to.eql(['b', 'c', 'd']); + }); + + it("splits an area by converting it to a multipolygon", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + 'd': iD.Node({id: 'd'}), + '-': iD.Way({id: '-', tags: {building: 'yes'}, nodes: ['a', 'b', 'c', 'd', 'a']}) + }); + + graph = iD.actions.Split('a', '=')(graph); + expect(graph.entity('-').tags).to.eql({}); + expect(graph.entity('=').tags).to.eql({}); + expect(graph.parentRelations(graph.entity('-'))).to.have.length(1); + + var relation = graph.parentRelations(graph.entity('-'))[0]; + expect(relation.tags).to.eql({type: 'multipolygon', building: 'yes'}); + expect(relation.members).to.eql([ + {id: '-', role: 'outer', type: 'way'}, + {id: '=', role: 'outer', type: 'way'} + ]); + }); + it("adds the new way to parent relations (no connections)", function () { // Situation: // a ---- b ---- c From 4f6637d58ba85a677eafa245b9cc8af559f9e6c9 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Wed, 6 Feb 2013 19:11:28 -0500 Subject: [PATCH 16/25] Fix add_point tests (switch back to mouseup) --- test/spec/modes/add_point.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5807a3c0b..62291f8ee 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -17,13 +17,13 @@ describe("iD.modes.AddPoint", function() { describe("clicking the map", function () { it("adds a node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.changes().created).to.have.length(1); }); it("selects the node", function() { happen.mousedown(context.surface().node(), {}); - happen.click(window, {}); + happen.mouseup(window, {}); expect(context.mode().id).to.equal('select'); expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); From ec152716fba81566d1a0e28647d3cd30955d2c6a Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 01:16:51 -0500 Subject: [PATCH 17/25] Fix calls to ui.flash --- js/id/renderer/map.js | 2 +- js/id/ui/geocoder.js | 2 +- js/id/ui/inspector.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5074abd92..946b29b70 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -106,7 +106,7 @@ iD.Map = function(context) { } if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .text('Cannot zoom out further in current mode.'); return map.zoom(16); diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ffb735305..8883dc934 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -12,7 +12,7 @@ iD.ui.geocoder = function() { if (err) return hide(); hide(); if (!resp.length) { - return iD.ui.flash() + return iD.ui.flash(context.container()) .select('.content') .append('h3') .text('No location found for "' + searchVal + '"'); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index bf6099823..35e03d231 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -163,7 +163,7 @@ iD.ui.inspector = function() { }) .call(iD.keyReference(context)); } else { - iD.ui.flash() + iD.ui.flash(context.container()) .select('.content') .append('h3') .text(t('inspector.no_documentation_key')); From cf96055781e08033e94e920c0466b1e385343c30 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 14:08:07 -0500 Subject: [PATCH 18/25] Fix scale calculation in orthogonalize --- js/id/actions/orthogonalize.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 068db55bd..b57b8ad0a 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -62,7 +62,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { p = subtractPoints(a, b), q = subtractPoints(c, b); - var scale = p.length + q.length; + var scale = iD.geo.dist(p, [0, 0]) + iD.geo.dist(q, [0, 0]); p = normalizePoint(p, 1.0); q = normalizePoint(q, 1.0); From 9a2bafac1be3e5e2b8cee00bca226545445e2efb Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:30:44 -0500 Subject: [PATCH 19/25] Power through less square iterations --- js/id/actions/orthogonalize.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index b57b8ad0a..09711364f 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -1,9 +1,14 @@ +/* + * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as + */ + iD.actions.Orthogonalize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), nodes = graph.childNodes(way), points = nodes.map(function(n) { return projection(n.loc); }), - quad_nodes = [], i, j; + quad_nodes = [], + best, i, j; var score = squareness(); for (i = 0; i < 1000; i++) { @@ -12,14 +17,15 @@ iD.actions.Orthogonalize = function(wayId, projection) { points[j] = addPoints(points[j],motions[j]); } var newScore = squareness(); - if (newScore > score) { - return graph; + if (newScore < score) { + best = _.clone(points); + score = newScore; } - score = newScore; if (score < 1.0e-8) { break; } } + points = best; for (i = 0; i < points.length - 1; i++) { quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); From b9d80538b1fa413914e94cbf04a02f4d6f39b029 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 15:41:14 -0500 Subject: [PATCH 20/25] jshinting --- js/id/actions/merge.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/id/actions/merge.js b/js/id/actions/merge.js index 8ba2a2318..5b515bcc5 100644 --- a/js/id/actions/merge.js +++ b/js/id/actions/merge.js @@ -6,8 +6,8 @@ iD.actions.Merge = function(ids) { var action = function(graph) { var geometries = groupEntitiesByGeometry(graph), - area = geometries['area'][0], - points = geometries['point']; + area = geometries.area[0], + points = geometries.point; points.forEach(function (point) { area = area.mergeTags(point.tags); @@ -26,9 +26,9 @@ iD.actions.Merge = function(ids) { action.enabled = function(graph) { var geometries = groupEntitiesByGeometry(graph); - return geometries['area'].length === 1 && - geometries['point'].length > 0 && - (geometries['area'].length + geometries['point'].length) === ids.length; + return geometries.area.length === 1 && + geometries.point.length > 0 && + (geometries.area.length + geometries.point.length) === ids.length; }; return action; From 02d4b8f1aaa3ad67eaa3843a6fc21652a2cf93a6 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Thu, 7 Feb 2013 15:48:32 -0500 Subject: [PATCH 21/25] Remove unnecessary code from orthogonalize --- js/id/actions/orthogonalize.js | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/js/id/actions/orthogonalize.js b/js/id/actions/orthogonalize.js index 09711364f..ada281a95 100644 --- a/js/id/actions/orthogonalize.js +++ b/js/id/actions/orthogonalize.js @@ -28,36 +28,7 @@ iD.actions.Orthogonalize = function(wayId, projection) { points = best; for (i = 0; i < points.length - 1; i++) { - quad_nodes.push(iD.Node({ loc: projection.invert(points[i]) })); - } - - for (i = 0; i < nodes.length; i++) { - if (graph.parentWays(nodes[i]).length > 1) { - var closest, closest_dist = Infinity, dist; - for (j = 0; j < quad_nodes.length; j++) { - dist = iD.geo.dist(quad_nodes[j].loc, nodes[i].loc); - if (dist < closest_dist) { - closest_dist = dist; - closest = j; - } - } - quad_nodes.splice(closest, 1, nodes[i]); - } - } - - for (i = 0; i < quad_nodes.length; i++) { - graph = graph.replace(quad_nodes[i]); - } - - var ids = _.pluck(quad_nodes, 'id'), - difference = _.difference(_.uniq(way.nodes), ids); - - ids.push(ids[0]); - - graph = graph.replace(way.update({nodes: ids})); - - for (i = 0; i < difference.length; i++) { - graph = iD.actions.DeleteNode(difference[i])(graph); + graph = graph.replace(graph.entity(nodes[i].id).move(projection.invert(points[i]))); } return graph; From f0b761b97923cf4dc3100443ce03a64b24f66c83 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:21:25 -0500 Subject: [PATCH 22/25] Lasso action --- css/app.css | 11 ++++--- index.html | 2 ++ js/id/behavior/lasso.js | 57 +++++++++++++++++++++++++++++++++++++ js/id/modes/browse.js | 1 + js/id/ui/lasso.js | 63 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 js/id/behavior/lasso.js create mode 100644 js/id/ui/lasso.js diff --git a/css/app.css b/css/app.css index 65f98dfd6..99904d953 100644 --- a/css/app.css +++ b/css/app.css @@ -1398,6 +1398,13 @@ a.success-action { border-radius: 4px; } +.lasso-box { + fill-opacity:0.2; + fill: #bde5aa; + stroke: #000; + stroke-width: 1; +} + /* Media Queries ------------------------------------------------------- */ @@ -1408,10 +1415,6 @@ a.success-action { .save .label, .apply .label, .cancel .label { display: block;} } - - - - div.combobox { width:155px; z-index: 9999; diff --git a/index.html b/index.html index 9f4c23757..31f06870a 100644 --- a/index.html +++ b/index.html @@ -75,6 +75,7 @@ + @@ -102,6 +103,7 @@ + diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js new file mode 100644 index 000000000..08dda8cd3 --- /dev/null +++ b/js/id/behavior/lasso.js @@ -0,0 +1,57 @@ +iD.behavior.Lasso = function(context) { + + var behavior = function(selection) { + + var timeout = null, + // the position of the first mousedown + pos = null, + lasso; + + function mousedown() { + if (d3.event.shiftKey === true) { + + lasso = iD.ui.lasso().a(d3.mouse(context.surface().node())); + + context.surface().call(lasso); + + selection + .on('mousemove.lasso', mousemove) + .on('mouseup.lasso', mouseup); + + d3.event.stopPropagation(); + d3.event.preventDefault(); + + } + } + + function mousemove() { + lasso.b(d3.mouse(context.surface().node())); + } + + function mouseup() { + var extent = iD.geo.Extent( + context.projection.invert(lasso.a()), + context.projection.invert(lasso.b())); + + lasso.close(); + + var selected = context.graph().intersects(extent); + + if (selected.length) { + context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); + } + + selection + .on('mousemove.lasso', null); + } + + selection + .on('mousedown.select', mousedown); + }; + + behavior.off = function(selection) { + selection.on('mousedown.lasso', null); + }; + + return behavior; +}; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 97f0d6935..5df94d69d 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -10,6 +10,7 @@ iD.modes.Browse = function(context) { var behaviors = [ iD.behavior.Hover(), iD.behavior.Select(context), + iD.behavior.Lasso(context), iD.behavior.DragNode(context)]; mode.enter = function() { diff --git a/js/id/ui/lasso.js b/js/id/ui/lasso.js new file mode 100644 index 000000000..1ca079fda --- /dev/null +++ b/js/id/ui/lasso.js @@ -0,0 +1,63 @@ +iD.ui.lasso = function() { + + var center, box, + group, + a = [0, 0], + b = [0, 0]; + + function lasso(selection) { + + group = selection.append('g') + .attr('class', 'lasso') + .attr('opacity', 0); + + box = group.append('rect') + .attr('class', 'lasso-box'); + + group.transition() + .style('opacity', 1); + + } + + // top-left + function topLeft(d) { + return 'translate(' + + [Math.min(d[0][0], d[1][0]), Math.min(d[0][1], d[1][1])] + ')'; + } + + function width(d) { return Math.abs(d[0][0] - d[1][0]); } + function height(d) { return Math.abs(d[0][1] - d[1][1]); } + + function draw() { + if (box) { + box.data([[a, b]]) + .attr('transform', topLeft) + .attr('width', width) + .attr('height', height); + } + } + + lasso.a = function(_) { + if (!arguments.length) return a; + a = _; + draw(); + return lasso; + }; + + lasso.b = function(_) { + if (!arguments.length) return b; + b = _; + draw(); + return lasso; + }; + + lasso.close = function(selection) { + if (group) { + group.transition() + .attr('opacity', 0) + .remove(); + } + }; + + return lasso; +}; From 843926009de92595a0ac996927fd47aea3c549b6 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:32:03 -0500 Subject: [PATCH 23/25] Normalize coordinates so that lasso can be dragged in any direction --- js/id/behavior/lasso.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/js/id/behavior/lasso.js b/js/id/behavior/lasso.js index 08dda8cd3..3986b5cf2 100644 --- a/js/id/behavior/lasso.js +++ b/js/id/behavior/lasso.js @@ -28,25 +28,32 @@ iD.behavior.Lasso = function(context) { lasso.b(d3.mouse(context.surface().node())); } + function normalize(a, b) { + return [ + [Math.min(a[0], b[0]), Math.min(a[1], b[1])], + [Math.max(a[0], b[0]), Math.max(a[1], b[1])]]; + } + function mouseup() { var extent = iD.geo.Extent( - context.projection.invert(lasso.a()), - context.projection.invert(lasso.b())); + normalize(context.projection.invert(lasso.a()), + context.projection.invert(lasso.b()))); lasso.close(); var selected = context.graph().intersects(extent); + selection + .on('mousemove.lasso', null) + .on('mouseup.lasso', null); + if (selected.length) { context.enter(iD.modes.Select(context, _.pluck(selected, 'id'))); } - - selection - .on('mousemove.lasso', null); } selection - .on('mousedown.select', mousedown); + .on('mousedown.lasso', mousedown); }; behavior.off = function(selection) { From edc424367344ebb5f0c21628929c02c660a494ce Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 7 Feb 2013 16:36:07 -0500 Subject: [PATCH 24/25] Add includes to test, fixes tests --- test/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/index.html b/test/index.html index 7b90f7c09..1e5975595 100644 --- a/test/index.html +++ b/test/index.html @@ -67,6 +67,7 @@ + @@ -101,6 +102,7 @@ + From 5cad057212a2a93d76b3f144ef8370c289577255 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 7 Feb 2013 13:36:19 -0800 Subject: [PATCH 25/25] Clear selection on esc (fixes #643) --- js/id/modes/select.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 0bcfa8c0b..95eb3e78a 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -42,6 +42,10 @@ iD.modes.Select = function(context, selection, initial) { .filter(function(o) { return o.available(); }); operations.unshift(iD.operations.Delete(selection, context)); + keybinding.on('⎋', function() { + context.enter(iD.modes.Browse(context)); + }); + operations.forEach(function(operation) { keybinding.on(operation.key, function() { if (operation.enabled()) {