From 8eb04ddb4d301fa22647ff4a1c6701fcbfbed05d Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Thu, 31 Jan 2013 19:41:52 -0500 Subject: [PATCH 01/50] combobox --- combobox.html | 170 ++++++++++++++++++++++++++++++++++++++++++ css/app.css | 43 +++++++++++ js/lib/d3.combobox.js | 147 ++++++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 combobox.html create mode 100644 js/lib/d3.combobox.js diff --git a/combobox.html b/combobox.html new file mode 100644 index 000000000..0d97698b0 --- /dev/null +++ b/combobox.html @@ -0,0 +1,170 @@ + + + + + iD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + diff --git a/css/app.css b/css/app.css index 4c149843d..2be1b819b 100644 --- a/css/app.css +++ b/css/app.css @@ -1267,3 +1267,46 @@ a.success-action { .icon.icon-pre-text { margin-right: 0px;} .save .label, .apply .label { display: block;} } + + + + + +div.combobox { + width:155px; + z-index: 9999; + display: none; + box-shadow: 0 5px 10px 0 rgba(0,0,0,.2); + margin-top: -1px; + background: white; + max-height: 180px; + overflow: auto; + border: 1px solid #ccc; +} + +div.combobox a { + height: 25px; + line-height: 25px; + cursor: pointer; + display: block; + border-top:1px solid #ccc; + background-color: #fff; + padding:1px 4px; + white-space: nowrap; +} + +div.combobox a:hover, +div.combobox a.selected { + background: #e1e8ff; + color: #154dff; +} + +div.combobox a:first-child { + border-top: 0; +} + +div.combobox-carat { + cursor: pointer; + padding:0 5px; + vertical-align:middle; +} diff --git a/js/lib/d3.combobox.js b/js/lib/d3.combobox.js new file mode 100644 index 000000000..165456563 --- /dev/null +++ b/js/lib/d3.combobox.js @@ -0,0 +1,147 @@ +d3.combobox = function() { + var event = d3.dispatch('accept'), + autohighlight = false, + autofilter = false, + input, + container, + data; + + var typeahead = function(selection) { + var hidden, idx = autohighlight ? 0 : -1; + + var rect = selection.select('input').node().getBoundingClientRect(); + + input = selection.select('input'); + + container = selection + .insert('div', ':first-child') + .attr('class', 'combobox') + .style({ + position: 'absolute', + display: 'none', + left: '0px', + width: rect.width + 'px', + top: rect.height + 'px' + }); + + carat = selection + .insert('div', ':first-child') + .attr('class', 'combobox-carat') + .text('+') + .style({ + position: 'absolute', + left: (rect.width - 20) + 'px', + top: '0px' + }) + .on('click', function() { + update(); + show(); + }); + + selection + .on('keyup.typeahead', key); + + hidden = false; + + function hide() { + idx = autohighlight ? 0 : -1; + hidden = true; + } + + function show() { + container.style('display', 'block'); + } + + function slowHide() { + if (autohighlight && container.select('a.selected').node()) { + select(container.select('a.selected').datum()); + event.accept(); + } + window.setTimeout(hide, 150); + } + + selection + .on('focus.typeahead', show) + .on('blur.typeahead', slowHide); + + function key() { + var len = container.selectAll('a').data().length; + if (d3.event.keyCode === 40) { + idx = Math.min(idx + 1, len - 1); + return highlight(); + } else if (d3.event.keyCode === 38) { + idx = Math.max(idx - 1, 0); + return highlight(); + } else if (d3.event.keyCode === 13) { + if (container.select('a.selected').node()) { + select(container.select('a.selected').datum()); + } + event.accept(); + hide(); + } else { + update(); + } + } + + function highlight() { + container + .selectAll('a') + .classed('selected', function(d, i) { return i == idx; }); + } + + function update() { + + function run(data) { + container.style('display', function() { + return data.length ? 'block' : 'none'; + }); + + var options = container + .selectAll('a') + .data(data, function(d) { return d.value; }); + + options.enter() + .append('a') + .text(function(d) { return d.value; }) + .attr('title', function(d) { return d.title; }) + .on('click', select); + + options.exit().remove(); + + options + .classed('selected', function(d, i) { return i == idx; }); + } + + if (typeof data === 'function') data(selection, run); + else run(data); + } + + function select(d) { + input + .property('value', d.value) + .trigger('change'); + container.style('display', 'none'); + } + + }; + + typeahead.data = function(_) { + if (!arguments.length) return data; + data = _; + return typeahead; + }; + + typeahead.autofilter = function(_) { + if (!arguments.length) return autofilter; + autofilter = _; + return typeahead; + }; + + typeahead.autohighlight = function(_) { + if (!arguments.length) return autohighlight; + autohighlight = _; + return typeahead; + }; + + return d3.rebind(typeahead, event, 'on'); +}; From 9494d8d468fd188bae5687703e8036488bc01c7c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 10:16:18 -0500 Subject: [PATCH 02/50] Remove unused --- js/id/renderer/map.js | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index a2c4781ef..c02ff65cb 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -369,24 +369,6 @@ iD.Map = function() { return map; }; - map.hint = function (_) { - if (_ === false) { - d3.select('div.inspector-wrap') - .style('opacity', 0) - .style('display', 'none'); - } else { - d3.select('div.inspector-wrap') - .html('') - .style('display', 'block') - .transition() - .style('opacity', 1); - d3.select('div.inspector-wrap') - .append('div') - .attr('class','inspector-inner') - .text(_); - } - }; - map.editable = function() { return map.zoom() >= 16; }; From 559f3c9037f1e87ce87c7edbd5a30fdb4f04abec Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Fri, 1 Feb 2013 11:00:55 -0500 Subject: [PATCH 03/50] Remove unecessary line --- js/id/renderer/map.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c02ff65cb..cd99de738 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -55,7 +55,6 @@ iD.Map = function() { function pxCenter() { return [dimensions[0] / 2, dimensions[1] / 2]; } function drawVector(difference) { - if (surface.style(transformProp) != 'none') return; var filter, all, extent = map.extent(), graph = history.graph(); From ab7290a86595c2c3a440c2921200bb08413e9392 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 11:31:31 -0500 Subject: [PATCH 04/50] Fix re-requesting failed tiles. Fixes #594 --- js/id/renderer/background.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js index 54e3835ee..cae44ca09 100644 --- a/js/id/renderer/background.js +++ b/js/id/renderer/background.js @@ -83,7 +83,10 @@ iD.Background = function() { } }); - requests = uniqueBy(requests, 3); + requests = uniqueBy(requests, 3).filter(function(r) { + // don't re-request tiles which have failed in the past + return cache[r[3]] !== false; + }); function load(d) { cache[d[3]] = true; @@ -125,7 +128,7 @@ iD.Background = function() { .attr('src', function(d) { return d[3]; }) .on('error', error) .on('load', load); - + image.style(transformProp, imageTransform); if (Object.keys(cache).length > 100) cache = {}; From 7e68e8e114020da318e33537cce9d57d1698cfeb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 15:50:00 -0500 Subject: [PATCH 05/50] Add iD.Context This is a facade interface that ties together a bunch of different internal objects and will make it easier to write tests for behaviors, modes, and operations. --- Makefile | 1 + index.html | 3 +- js/id/behavior/add_way.js | 18 ++++---- js/id/behavior/drag_midpoint.js | 15 +++--- js/id/behavior/drag_node.js | 22 ++++----- js/id/behavior/draw.js | 24 +++++----- js/id/behavior/draw_way.js | 69 ++++++++++++++-------------- js/id/behavior/hash.js | 29 ++++++------ js/id/behavior/select.js | 8 ++-- js/id/context.js | 44 ++++++++++++++++++ js/id/controller.js | 6 +-- js/id/id.js | 39 ++++++++++------ js/id/modes/add_area.js | 38 +++++++--------- js/id/modes/add_line.js | 42 ++++++++--------- js/id/modes/add_point.js | 25 ++++------ js/id/modes/browse.js | 22 ++++----- js/id/modes/draw_area.js | 12 ++--- js/id/modes/draw_line.js | 12 ++--- js/id/modes/move_way.js | 47 +++++++++---------- js/id/modes/select.js | 81 ++++++++++++++++----------------- js/id/operations/circularize.js | 20 +++----- js/id/operations/delete.js | 15 +++--- js/id/operations/move.js | 10 ++-- js/id/operations/reverse.js | 11 ++--- js/id/operations/split.js | 12 ++--- js/id/operations/unjoin.js | 12 ++--- js/id/ui/save.js | 29 +++--------- test/index.html | 11 +++-- test/index_packaged.html | 10 ++-- test/spec/behavior/hash.js | 70 ++++++++++++++-------------- test/spec/modes/add_point.js | 33 ++++++-------- test/spec/ui/confirm.js | 3 ++ test/spec/ui/modal.js | 1 + 33 files changed, 384 insertions(+), 410 deletions(-) create mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 1db4f2efb..598bce278 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ + js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index c8ddd7562..306575804 100644 --- a/index.html +++ b/index.html @@ -122,9 +122,10 @@ + + - diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 89ba7f1cd..207c88566 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -1,8 +1,6 @@ -iD.behavior.AddWay = function(mode) { - var map = mode.map, - controller = mode.controller, - event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), - draw = iD.behavior.Draw(map); +iD.behavior.AddWay = function(context) { + var event = d3.dispatch('start', 'startFromWay', 'startFromNode', 'startFromMidpoint'), + draw = iD.behavior.Draw(context); var addWay = function(surface) { draw.on('click', event.start) @@ -12,7 +10,8 @@ iD.behavior.AddWay = function(mode) { .on('cancel', addWay.cancel) .on('finish', addWay.cancel); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -20,19 +19,20 @@ iD.behavior.AddWay = function(mode) { }; addWay.off = function(surface) { - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off); }; addWay.cancel = function() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(addWay, event, 'on'); diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js index 6041946cd..42099963c 100644 --- a/js/id/behavior/drag_midpoint.js +++ b/js/id/behavior/drag_midpoint.js @@ -1,16 +1,13 @@ -iD.behavior.DragMidpoint = function(mode) { - var history = mode.history, - projection = mode.map.projection; - +iD.behavior.DragMidpoint = function(context) { var behavior = iD.behavior.drag() .delegate(".midpoint") .origin(function(d) { - return projection(d.loc); + return context.projection(d.loc); }) .on('start', function(d) { var node = iD.Node(); - history.perform(iD.actions.AddMidpoint(d, node)); + context.perform(iD.actions.AddMidpoint(d, node)); var vertex = d3.selectAll('.vertex') .filter(function(data) { return data.id === node.id; }); @@ -19,11 +16,11 @@ iD.behavior.DragMidpoint = function(mode) { }) .on('move', function(d) { d3.event.sourceEvent.stopPropagation(); - history.replace( - iD.actions.MoveNode(d.id, projection.invert(d3.event.point))); + context.replace( + iD.actions.MoveNode(d.id, context.projection.invert(d3.event.point))); }) .on('end', function() { - history.replace( + context.replace( iD.actions.Noop(), t('operations.add.annotation.vertex')); }); diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js index b9846617d..b62162e94 100644 --- a/js/id/behavior/drag_node.js +++ b/js/id/behavior/drag_node.js @@ -1,8 +1,6 @@ -iD.behavior.DragNode = function(mode) { - var history = mode.history, - size = mode.map.size(), - nudgeInterval, - projection = mode.map.projection; +iD.behavior.DragNode = function(context) { + var size = context.map().size(), + nudgeInterval; function edge(point) { var pad = [30, 100, 30, 100]; @@ -16,7 +14,7 @@ iD.behavior.DragNode = function(mode) { function startNudge(nudge) { if (nudgeInterval) window.clearInterval(nudgeInterval); nudgeInterval = window.setInterval(function() { - mode.map.pan(nudge).redraw(); + context.map().pan(nudge).redraw(); }, 50); } @@ -26,16 +24,16 @@ iD.behavior.DragNode = function(mode) { } function annotation(entity) { - return t('operations.move.annotation.' + entity.geometry(mode.history.graph())); + return t('operations.move.annotation.' + entity.geometry(context.graph())); } return iD.behavior.drag() .delegate(".node") .origin(function(entity) { - return projection(entity.loc); + return context.projection(entity.loc); }) .on('start', function() { - history.perform( + context.perform( iD.actions.Noop()); }) .on('move', function(entity) { @@ -45,13 +43,13 @@ iD.behavior.DragNode = function(mode) { if (nudge) startNudge(nudge); else stopNudge(); - history.replace( - iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)), + context.replace( + iD.actions.MoveNode(entity.id, context.projection.invert(d3.event.point)), annotation(entity)); }) .on('end', function(entity) { stopNudge(); - history.replace( + context.replace( iD.actions.Noop(), annotation(entity)); }); diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index bce4470b6..8405ac341 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,7 +1,8 @@ -iD.behavior.Draw = function(map) { +iD.behavior.Draw = function(context) { var event = d3.dispatch('move', 'click', 'clickWay', 'clickNode', 'clickMidpoint', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - down, surface, hover; + hover = iD.behavior.Hover(), + down; function datum() { if (d3.event.altKey) { @@ -28,7 +29,7 @@ iD.behavior.Draw = function(map) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(map.surface.node()), map); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { @@ -38,19 +39,19 @@ iD.behavior.Draw = function(map) { event.clickMidpoint(d); } else { - event.click(map.mouseCoordinates()); + event.click(context.map().mouseCoordinates()); } } function keydown() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover.off); + context.uninstall(hover); } } function keyup() { if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { - surface.call(hover); + context.install(hover); } } @@ -70,8 +71,7 @@ iD.behavior.Draw = function(map) { } function draw(selection) { - surface = selection; - hover = iD.behavior.Hover(); + context.install(hover); keybinding .on('⌫', backspace) @@ -83,8 +83,7 @@ iD.behavior.Draw = function(map) { .on('mousedown.draw', mousedown) .on('mouseup.draw', mouseup) .on('mousemove.draw', mousemove) - .on('click.draw', click) - .call(hover); + .on('click.draw', click); d3.select(document) .call(keybinding) @@ -95,12 +94,13 @@ iD.behavior.Draw = function(map) { } draw.off = function(selection) { + context.uninstall(hover); + selection .on('mousedown.draw', null) .on('mouseup.draw', null) .on('mousemove.draw', null) - .on('click.draw', null) - .call(hover.off); + .on('click.draw', null); d3.select(document) .call(keybinding.off) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 1cc62b755..28a8fbb38 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -1,35 +1,32 @@ -iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { - var map = mode.map, - history = mode.history, - controller = mode.controller, - way = history.graph().entity(wayId), +iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { + var way = context.entity(wayId), finished = false, annotation = t((way.isDegenerate() ? 'operations.start.annotation.' : - 'operations.continue.annotation.') + way.geometry(history.graph())), - draw = iD.behavior.Draw(map); + 'operations.continue.annotation.') + context.geometry(wayId)), + draw = iD.behavior.Draw(context); - var node = iD.Node({loc: map.mouseCoordinates()}), + var node = iD.Node({loc: context.map().mouseCoordinates()}), nodeId = node.id; - history[way.isDegenerate() ? 'replace' : 'perform']( + context[way.isDegenerate() ? 'replace' : 'perform']( iD.actions.AddEntity(node), iD.actions.AddVertex(wayId, node.id, index)); function move(datum) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; } - history.replace(iD.actions.MoveNode(nodeId, loc)); + context.replace(iD.actions.MoveNode(nodeId, loc)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } var drawWay = function(surface) { @@ -38,11 +35,12 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) .on('clickMidpoint', drawWay.addMidpoint) - .on('undo', history.undo) + .on('undo', context.undo) .on('cancel', drawWay.cancel) .on('finish', drawWay.finish); - map.fastEnable(false) + context.map() + .fastEnable(false) .minzoom(16) .dblclickEnable(false); @@ -51,26 +49,29 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { .filter(function (d) { return d.id === wayId || d.id === nodeId; }) .classed('active', true); - history.on('undone.draw', undone); + context.history() + .on('undone.draw', undone); }; drawWay.off = function(surface) { if (!finished) - history.pop(); + context.pop(); - map.fastEnable(true) + context.map() + .fastEnable(true) .minzoom(0) .tail(false); window.setTimeout(function() { - map.dblclickEnable(true); + context.map().dblclickEnable(true); }, 1000); surface.call(draw.off) .selectAll('.way, .node') .classed('active', false); - history.on('undone.draw', null); + context.history() + .on('undone.draw', null); }; function ReplaceTemporaryNode(newNode) { @@ -85,74 +86,74 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) { drawWay.add = function(loc) { var newNode = iD.Node({loc: loc}); - history.replace( + context.replace( iD.actions.AddEntity(newNode), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing way. drawWay.addWay = function(way, loc, wayIndex) { var newNode = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(newNode), iD.actions.AddVertex(way.id, newNode.id, wayIndex), ReplaceTemporaryNode(newNode), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Connect the way to an existing node and continue drawing. drawWay.addNode = function(node) { - history.perform( + context.perform( ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Add a midpoint, connect the way to it, and continue drawing. drawWay.addMidpoint = function(midpoint) { var node = iD.Node(); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), ReplaceTemporaryNode(node), annotation); finished = true; - controller.enter(mode); + context.enter(mode); }; // Finish the draw operation, removing the temporary node. If the way has enough // nodes to be valid, it's selected. Otherwise, return to browse mode. drawWay.finish = function() { - history.pop(); + context.pop(); finished = true; - var way = history.graph().entity(wayId); + var way = context.entity(wayId); if (way) { - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [way.id], true)); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } }; // Cancel the draw operation and return to browse, deleting everything drawn. drawWay.cancel = function() { - history.perform( + context.perform( d3.functor(baseGraph), t('operations.cancel_draw.annotation')); finished = true; - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); }; return d3.rebind(drawWay, event, 'on'); diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 65e748e28..871b73a35 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -1,4 +1,4 @@ -iD.behavior.Hash = function(controller, map) { +iD.behavior.Hash = function(context) { var s0 = null, // cached location.hash lat = 90 - 1e-8; // allowable latitude range @@ -27,13 +27,13 @@ iD.behavior.Hash = function(controller, map) { }; var move = _.throttle(function() { - var s1 = formatter(map); + var s1 = formatter(context.map()); if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map! }, 100); function hashchange() { if (location.hash === s0) return; // ignore spurious hashchange events - if (parser(map, (s0 = location.hash).substring(1))) { + if (parser(context.map(), (s0 = location.hash).substring(1))) { move(); // replace bogus hash } } @@ -42,24 +42,24 @@ iD.behavior.Hash = function(controller, map) { // do so before any features are loaded. thus wait for the feature to // be loaded and then select function willselect(id) { - map.on('drawn.hash', function() { - var entity = map.history().graph().entity(id); - if (entity === undefined) return; - else selectoff(); - controller.enter(iD.modes.Select([entity.id])); - map.on('drawn.hash', null); + context.map().on('drawn.hash', function() { + if (!context.entity(id)) return; + selectoff(); + context.enter(iD.modes.Select([id])); }); - controller.on('enter.hash', function() { - if (controller.mode.id !== 'browse') selectoff(); + + context.controller().on('enter.hash', function() { + if (context.mode().id !== 'browse') selectoff(); }); } function selectoff() { - map.on('drawn.hash', null); + context.map().on('drawn.hash', null); } function hash() { - map.on('move.hash', move); + context.map() + .on('move.hash', move); d3.select(window) .on('hashchange.hash', hashchange); @@ -75,7 +75,8 @@ iD.behavior.Hash = function(controller, map) { } hash.off = function() { - map.on('move.hash', null); + context.map() + .on('move.hash', null); d3.select(window) .on('hashchange.hash', null); diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index cee2cc940..e078fdbf1 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -1,12 +1,10 @@ -iD.behavior.Select = function(mode) { - var controller = mode.controller; - +iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - controller.enter(iD.modes.Select([datum.id])); + context.enter(iD.modes.Select(context, [datum.id])); } else { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/context.js b/js/id/context.js new file mode 100644 index 000000000..60f878b32 --- /dev/null +++ b/js/id/context.js @@ -0,0 +1,44 @@ +iD.Context = function() { + var history = iD.History(), + connection = iD.Connection(), + controller = iD.Controller(), + container, + map = iD.Map().connection(connection).history(history); + + var context = {}; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; + + context.connection = function () { return connection; }; + + context.history = function () { return history; }; + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; + + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.controller = function () { return controller; }; + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.map = function () { return map; }; + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + return context; +}; diff --git a/js/id/controller.js b/js/id/controller.js index 6c4e0fd1e..af1e7c388 100644 --- a/js/id/controller.js +++ b/js/id/controller.js @@ -1,14 +1,10 @@ // A controller holds a single action at a time and calls `.enter` and `.exit` // to bind and unbind actions. -iD.Controller = function(map, history) { +iD.Controller = function() { var event = d3.dispatch('enter', 'exit'); var controller = { mode: null }; controller.enter = function(mode) { - mode.controller = controller; - mode.history = history; - mode.map = map; - if (controller.mode) { controller.mode.exit(); event.exit(controller.mode); diff --git a/js/id/id.js b/js/id/id.js index 3e8aabcc3..3c2d0c482 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,17 +2,22 @@ window.iD = function(container) { // the reported, displayed version of iD. var version = '0.0.0-alpha1'; - var connection = iD.Connection() - .version(version), - history = iD.History(), - map = iD.Map() - .connection(connection) - .history(history), - controller = iD.Controller(map, history); + var context = iD.Context(); - map.background.source(iD.BackgroundSource.Bing); + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + context.connection() + .version(version); + + context.background() + .source(iD.BackgroundSource.Bing); function editor(container) { + context.container(container); + if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + 'and Internet Explorer 9 and above. Please upgrade your browser ' + @@ -39,8 +44,14 @@ window.iD = function(container) { var buttons_joined = limiter.append('div') .attr('class', 'button-wrap joined col4'); + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + var buttons = buttons_joined.selectAll('button.add-button') - .data([iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()]) + .data(modes) .enter().append('button') .attr('tabindex', -1) .attr('class', function (mode) { return mode.title + ' add-button col3'; }) @@ -57,7 +68,7 @@ window.iD = function(container) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); } } @@ -106,7 +117,7 @@ window.iD = function(container) { var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') - .call(iD.ui.save().map(map).controller(controller)); + .call(iD.ui.save(context)); var zoom = container.append('div') .attr('class', 'zoombuttons map-control') @@ -227,14 +238,14 @@ window.iD = function(container) { .on('⌃+⇧+Z', function() { history.redo(); }) .on('⌫', function() { d3.event.preventDefault(); }); - [iD.modes.Browse(), iD.modes.AddPoint(), iD.modes.AddLine(), iD.modes.AddArea()].forEach(function(m) { + modes.forEach(function(m) { keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); }); d3.select(document) .call(keybinding); - var hash = iD.behavior.Hash(controller, map); + var hash = iD.behavior.Hash(context); hash(); @@ -246,7 +257,7 @@ window.iD = function(container) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse()); + controller.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index bda4e0daa..ae2e008ee 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -1,4 +1,4 @@ -iD.modes.AddArea = function() { +iD.modes.AddArea = function(context) { var mode = { id: 'add-area', button: 'area', @@ -11,77 +11,73 @@ iD.modes.AddArea = function() { defaultTags = {area: 'yes'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawArea(way.id, graph)); + context.enter(iD.modes.DrawArea(context, way.id, graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_area.tail')); + context.install(behavior); + context.tail(t('modes.add_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 92e9a64d7..79c5f3031 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -1,4 +1,4 @@ -iD.modes.AddLine = function() { +iD.modes.AddLine = function(context) { var mode = { id: 'add-line', button: 'line', @@ -11,84 +11,80 @@ iD.modes.AddLine = function() { defaultTags = {highway: 'residential'}; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function start(loc) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromWay(other, loc, index) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node({loc: loc}), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id), iD.actions.AddVertex(other.id, node.id, index)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } function startFromNode(node) { - var graph = history.graph(), + var graph = context.graph(), parent = graph.parentWays(node)[0], isLine = parent && parent.geometry(graph) === 'line'; if (isLine && parent.first() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'backward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); } else if (isLine && parent.last() === node.id) { - controller.enter(iD.modes.DrawLine(parent.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); } else { var way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } } function startFromMidpoint(midpoint) { - var graph = history.graph(), + var graph = context.graph(), node = iD.Node(), way = iD.Way({tags: defaultTags}); - history.perform( + context.perform( iD.actions.AddMidpoint(midpoint, node), iD.actions.AddEntity(way), iD.actions.AddVertex(way.id, node.id)); - controller.enter(iD.modes.DrawLine(way.id, 'forward', graph)); + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); } - behavior = iD.behavior.AddWay(mode) + behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) .on('startFromMidpoint', startFromMidpoint); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_line.tail')); + context.install(behavior); + context.tail(t('modes.add_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index 6dd524bb5..c3dc7f075 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -1,4 +1,4 @@ -iD.modes.AddPoint = function() { +iD.modes.AddPoint = function(context) { var mode = { id: 'add-point', title: t('modes.add_point.title'), @@ -9,18 +9,14 @@ iD.modes.AddPoint = function() { var behavior; mode.enter = function() { - var map = mode.map, - history = mode.history, - controller = mode.controller; - function add(loc) { var node = iD.Node({loc: loc}); - history.perform( + context.perform( iD.actions.AddEntity(node), t('operations.add.annotation.point')); - controller.enter(iD.modes.Select([node.id], true)); + context.enter(iD.modes.Select(context, [node.id], true)); } function addWay(way, loc, index) { @@ -32,10 +28,10 @@ iD.modes.AddPoint = function() { } function cancel() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - behavior = iD.behavior.Draw(map) + behavior = iD.behavior.Draw(context) .on('click', add) .on('clickWay', addWay) .on('clickNode', addNode) @@ -43,16 +39,13 @@ iD.modes.AddPoint = function() { .on('cancel', cancel) .on('finish', cancel); - mode.map.surface.call(behavior); - mode.map.tail(t('modes.add_point.tail')); + context.install(behavior); + context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - var map = mode.map, - surface = map.surface; - - map.tail(false); - behavior.off(surface); + context.tail(false); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js index 7652576ea..33add1262 100644 --- a/js/id/modes/browse.js +++ b/js/id/modes/browse.js @@ -1,4 +1,4 @@ -iD.modes.Browse = function() { +iD.modes.Browse = function(context) { var mode = { button: 'browse', id: 'browse', @@ -7,27 +7,21 @@ iD.modes.Browse = function() { key: t('modes.browse.key') }; - var behaviors; + var behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)]; mode.enter = function() { - var surface = mode.map.surface; - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; - behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); }; mode.exit = function() { - var surface = mode.map.surface; - behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); }; diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js index 9801a092c..aee2f153c 100644 --- a/js/id/modes/draw_area.js +++ b/js/id/modes/draw_area.js @@ -1,4 +1,4 @@ -iD.modes.DrawArea = function(wayId, baseGraph) { +iD.modes.DrawArea = function(context, wayId, baseGraph) { var mode = { button: 'area', id: 'draw-area' @@ -7,11 +7,11 @@ iD.modes.DrawArea = function(wayId, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), headId = way.nodes[way.nodes.length - 2], tailId = way.first(); - behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, -1, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawArea = function(wayId, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_area.tail')); + context.install(behavior); + context.tail(t('modes.draw_area.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js index 5ae8c2703..9fd07c937 100644 --- a/js/id/modes/draw_line.js +++ b/js/id/modes/draw_line.js @@ -1,4 +1,4 @@ -iD.modes.DrawLine = function(wayId, direction, baseGraph) { +iD.modes.DrawLine = function(context, wayId, direction, baseGraph) { var mode = { button: 'line', id: 'draw-line' @@ -7,11 +7,11 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { var behavior; mode.enter = function() { - var way = mode.history.graph().entity(wayId), + var way = context.entity(wayId), index = (direction === 'forward') ? undefined : 0, headId = (direction === 'forward') ? way.last() : way.first(); - behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph); + behavior = iD.behavior.DrawWay(context, wayId, index, mode, baseGraph); var addNode = behavior.addNode; @@ -23,12 +23,12 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) { } }; - mode.map.surface.call(behavior); - mode.map.tail(t('modes.draw_line.tail')); + context.install(behavior); + context.tail(t('modes.draw_line.tail')); }; mode.exit = function() { - mode.map.surface.call(behavior.off); + context.uninstall(behavior); }; return mode; diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 5c262e259..30c473406 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -1,4 +1,4 @@ -iD.modes.MoveWay = function(wayId) { +iD.modes.MoveWay = function(context, wayId) { var mode = { id: 'move-way' }; @@ -6,55 +6,53 @@ iD.modes.MoveWay = function(wayId) { var keybinding = d3.keybinding('move-way'); mode.enter = function() { - var map = mode.map, - history = mode.history, - graph = history.graph(), - selection = map.surface, - controller = mode.controller, - projection = map.projection, - way = graph.entity(wayId), - origin = d3.mouse(selection.node()), - annotation = t('operations.move.annotation.' + way.geometry(graph)); + var origin = point(), + annotation = t('operations.move.annotation.' + context.geometry(wayId)); // If intiated via keyboard if (!origin[0] && !origin[1]) origin = null; - history.perform( + context.perform( iD.actions.Noop(), annotation); + function point() { + return d3.mouse(context.surface().node()); + } + function move() { - var p = d3.mouse(selection.node()), + var p = point(), delta = origin ? [p[0] - origin[0], p[1] - origin[1]] : [0, 0]; origin = p; - history.replace( - iD.actions.MoveWay(wayId, delta, projection), + context.replace( + iD.actions.MoveWay(wayId, delta, context.projection), annotation); } function finish() { d3.event.stopPropagation(); - controller.enter(iD.modes.Select([way.id], true)); + context.enter(iD.modes.Select(context, [wayId], true)); } function cancel() { - history.pop(); - controller.enter(iD.modes.Select([way.id], true)); + context.pop(); + context.enter(iD.modes.Select(context, [wayId], true)); } function undone() { - controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } - selection + context.selection() .on('mousemove.move-way', move) .on('click.move-way', finish); - history.on('undone.move-way', undone); + context.history() + .on('undone.move-way', undone); keybinding .on('⎋', cancel) @@ -65,15 +63,12 @@ iD.modes.MoveWay = function(wayId) { }; mode.exit = function() { - var map = mode.map, - history = mode.history, - selection = map.surface; - - selection + context.selection() .on('mousemove.move-way', null) .on('click.move-way', null); - history.on('undone.move-way', null); + context.history() + .on('undone.move-way', null); keybinding.off(); }; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 8d29450d6..df445862d 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -1,4 +1,4 @@ -iD.modes.Select = function(selection, initial) { +iD.modes.Select = function(context, selection, initial) { var mode = { id: 'select', button: 'browse' @@ -6,12 +6,16 @@ iD.modes.Select = function(selection, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - behaviors, + behaviors = [ + iD.behavior.Hover(), + iD.behavior.Select(context), + iD.behavior.DragNode(context), + iD.behavior.DragMidpoint(context)], radialMenu; function changeTags(d, tags) { if (!_.isEqual(singular().tags, tags)) { - mode.history.perform( + context.perform( iD.actions.ChangeTags(d.id, tags), t('operations.change_tags.annotation')); } @@ -19,7 +23,7 @@ iD.modes.Select = function(selection, initial) { function singular() { if (selection.length === 1) { - return mode.map.history().graph().entity(selection[0]); + return context.entity(selection[0]); } } @@ -28,24 +32,14 @@ iD.modes.Select = function(selection, initial) { }; mode.enter = function() { - var map = mode.map, - history = map.history(), - graph = history.graph(), - surface = map.surface, - entity = singular(); - - behaviors = [ - iD.behavior.Hover(), - iD.behavior.Select(mode), - iD.behavior.DragNode(mode), - iD.behavior.DragMidpoint(mode)]; + var entity = singular(); behaviors.forEach(function(behavior) { - behavior(surface); + context.install(behavior); }); var operations = d3.values(iD.operations) - .map(function (o) { return o(selection, mode); }) + .map(function (o) { return o(selection, context); }) .filter(function (o) { return o.available(); }); operations.forEach(function(operation) { @@ -62,9 +56,10 @@ iD.modes.Select = function(selection, initial) { }), true)); if (entity) { - inspector.graph(graph); + inspector.graph(context.graph()); - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'block') .style('opacity', 1) .datum(entity) @@ -73,38 +68,38 @@ iD.modes.Select = function(selection, initial) { if (d3.event) { // Pan the map if the clicked feature intersects with the position // of the inspector - var inspector_size = d3.select('.inspector-wrap').size(), - map_size = mode.map.size(), + var inspector_size = context.container().select('.inspector-wrap').size(), + map_size = context.map().size(), offset = 50, shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset, center = (map_size[0] / 2) + shift_left + offset; if (shift_left > 0 && inspector_size[1] > d3.event.y) { - mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2])); + context.map().centerEase(context.projection.invert([center, map_size[1]/2])); } } inspector .on('changeTags', changeTags) - .on('close', function() { mode.controller.enter(iD.modes.Browse()); }); + .on('close', function() { context.enter(iD.modes.Browse(context)); }); - history.on('change.select', function() { + context.history().on('change.select', function() { // Exit mode if selected entity gets undone var oldEntity = entity, - newEntity = history.graph().entity(selection[0]); + newEntity = context.entity(selection[0]); if (!newEntity) { - mode.controller.enter(iD.modes.Browse()); + context.enter(iD.modes.Browse(context)); } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - surface.call(radialMenu.close); + context.surface().call(radialMenu.close); }); } - map.on('move.select', function() { - surface.call(radialMenu.close); + context.map().on('move.select', function() { + context.surface().call(radialMenu.close); }); function dblclick() { @@ -113,10 +108,10 @@ iD.modes.Select = function(selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(mode.map.surface.node()), mode.map), + d3.mouse(context.surface().node()), context.map()), node = iD.Node({ loc: choice.loc }); - history.perform( + context.perform( iD.actions.AddEntity(node), iD.actions.AddVertex(datum.id, node.id, choice.index), t('operations.add.annotation.vertex')); @@ -129,7 +124,8 @@ iD.modes.Select = function(selection, initial) { d3.select(document) .call(keybinding); - surface.on('dblclick.select', dblclick) + context.surface() + .on('dblclick.select', dblclick) .selectAll("*") .filter(function (d) { return d && selection.indexOf(d.id) >= 0; }) .classed('selected', true); @@ -137,25 +133,23 @@ iD.modes.Select = function(selection, initial) { radialMenu = iD.ui.RadialMenu(operations); if (d3.event && !initial) { - var loc = map.mouseCoordinates(); + var loc = context.map().mouseCoordinates(); if (entity && entity.type === 'node') { loc = entity.loc; } - surface.call(radialMenu, map.projection(loc)); + context.surface().call(radialMenu, context.projection(loc)); } }; mode.exit = function () { - var surface = mode.map.surface, - history = mode.history; - if (singular()) { changeTags(singular(), inspector.tags()); } - d3.select('.inspector-wrap') + context.container() + .select('.inspector-wrap') .style('display', 'none') .html(''); @@ -164,7 +158,7 @@ iD.modes.Select = function(selection, initial) { d3.selectAll('div.typeahead').remove(); behaviors.forEach(function(behavior) { - behavior.off(surface); + context.uninstall(behavior); }); var q = iD.util.stringQs(location.hash.substring(1)); @@ -172,13 +166,14 @@ iD.modes.Select = function(selection, initial) { keybinding.off(); - history.on('change.select', null); + context.history() + .on('change.select', null); - surface.on('dblclick.select', null) + context.surface() + .call(radialMenu.close) + .on('dblclick.select', null) .selectAll(".selected") .classed('selected', false); - - surface.call(radialMenu.close); }; return mode; diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 4b0f27bf9..419a6e9eb 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,25 +1,19 @@ -iD.operations.Circularize = function(selection, mode) { +iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), - action = iD.actions.Circularize(entityId, mode.map); + action = iD.actions.Circularize(entityId, context.map()); var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), - annotation = t('operations.circularize.annotation.' + entity.geometry(graph)); - - history.perform(action, annotation); + var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); + context.perform(action, annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); - return selection.length === 1 && entity.type === 'way'; + return selection.length === 1 && + context.entity(entityId).type === 'way'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "circularize"; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 12df22a09..d631a2ed7 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,21 +1,18 @@ -iD.operations.Delete = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Delete = function(selection, context) { + var entityId = selection[0]; var operation = function() { - var graph = history.graph(), - entity = graph.entity(entityId), + var entity = context.entity(entityId), action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], - annotation = t('operations.delete.annotation.' + entity.geometry(graph)); + annotation = t('operations.delete.annotation.' + context.geometry(entityId)); - history.perform( + context.perform( action(entityId), annotation); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); + var entity = context.entity(entityId); return selection.length === 1 && (entity.type === 'way' || entity.type === 'node'); }; diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 10405cc53..d9da39f64 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -1,15 +1,13 @@ -iD.operations.Move = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Move = function(selection, context) { + var entityId = selection[0]; var operation = function() { - mode.controller.enter(iD.modes.MoveWay(entityId)); + context.enter(iD.modes.MoveWay(context, entityId)); }; operation.available = function() { - var graph = history.graph(); return selection.length === 1 && - graph.entity(entityId).type === 'way'; + context.entity(entityId).type === 'way'; }; operation.enabled = function() { diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 78b8789dd..77bacc64e 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -1,18 +1,15 @@ -iD.operations.Reverse = function(selection, mode) { - var entityId = selection[0], - history = mode.map.history(); +iD.operations.Reverse = function(selection, context) { + var entityId = selection[0]; var operation = function() { - history.perform( + context.perform( iD.actions.ReverseWay(entityId), t('operations.reverse.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'line'; + context.geometry(entityId) === 'line'; }; operation.enabled = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 4274fc41b..0d7ff6ae4 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,22 +1,18 @@ -iD.operations.Split = function(selection, mode) { +iD.operations.Split = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.SplitWay(entityId); var operation = function() { - history.perform(action, t('operations.split.annotation')); + context.perform(action, t('operations.split.annotation')); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "split"; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js index 2a40ec067..d4fcc1426 100644 --- a/js/id/operations/unjoin.js +++ b/js/id/operations/unjoin.js @@ -1,22 +1,18 @@ -iD.operations.Unjoin = function(selection, mode) { +iD.operations.Unjoin = function(selection, context) { var entityId = selection[0], - history = mode.map.history(), action = iD.actions.UnjoinNode(entityId); var operation = function() { - history.perform(action, 'Unjoined lines.'); + context.perform(action, 'Unjoined lines.'); }; operation.available = function() { - var graph = history.graph(), - entity = graph.entity(entityId); return selection.length === 1 && - entity.geometry(graph) === 'vertex'; + context.geometry(entityId) === 'vertex'; }; operation.enabled = function() { - var graph = history.graph(); - return action.enabled(graph); + return action.enabled(context.graph()); }; operation.id = "unjoin"; diff --git a/js/id/ui/save.js b/js/id/ui/save.js index b8085e21a..6098519da 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,11 +1,8 @@ -iD.ui.save = function() { - - var map, controller; - - function save(selection) { - - var history = map.history(), - connection = map.connection(), +iD.ui.save = function(context) { + return function (selection) { + var map = context.map(), + history = context.history(), + connection = context.connection(), tooltip = bootstrap.tooltip() .placement('bottom'); @@ -60,7 +57,7 @@ iD.ui.save = function() { .on('fix', function(d) { map.extent(d.entity.extent(map.history().graph())); if (map.zoom() > 19) map.zoom(19); - controller.enter(iD.modes.Select([d.entity.id])); + context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); }) .on('save', commit)); @@ -88,19 +85,5 @@ iD.ui.save = function() { selection.call(tooltip.hide); } }); - } - - save.map = function(_) { - if (!arguments.length) return map; - map = _; - return save; }; - - save.controller = function(_) { - if (!arguments.length) return controller; - controller = _; - return save; - }; - - return save; }; diff --git a/test/index.html b/test/index.html index b85eb1b56..b56645ee7 100644 --- a/test/index.html +++ b/test/index.html @@ -119,6 +119,7 @@ + @@ -150,9 +151,6 @@ - - - @@ -162,8 +160,6 @@ - - @@ -189,6 +185,11 @@ + + + + + diff --git a/test/index_packaged.html b/test/index_packaged.html index 60979e236..11f8368f4 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,9 +44,6 @@ - - - @@ -56,8 +53,6 @@ - - @@ -83,6 +78,11 @@ + + + + + diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 7616bf536..5aceb53fc 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -1,19 +1,18 @@ describe("iD.behavior.Hash", function () { - var hash, map, controller; + mocha.globals('__onhashchange.hash'); + + var hash, context; beforeEach(function () { - map = { - on: function () { return map; }, - zoom: function () { return arguments.length ? map : 0; }, - center: function () { return arguments.length ? map : [0, 0]; }, - centerZoom: function () { return arguments.length ? map : [0, 0]; } - }; + context = iD.Context(); - controller = { - on: function () { return controller; } - }; + // Neuter connection + context.connection().loadTiles = function () {}; - hash = iD.behavior.Hash(controller, map); + hash = iD.behavior.Hash(context); + + d3.select(document.createElement('div')) + .call(context.map()); }); afterEach(function () { @@ -22,44 +21,41 @@ describe("iD.behavior.Hash", function () { it("sets hadHash if location.hash is present", function () { location.hash = "map=20.00/38.87952/-77.02405"; + hash(); + expect(hash.hadHash).to.be.true; }); it("centerZooms map to requested level", function () { location.hash = "map=20.00/38.87952/-77.02405"; - sinon.spy(map, 'centerZoom'); + hash(); - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); + + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); }); - describe("on window hashchange events", function () { - beforeEach(function () { - hash(); + it("centerZooms map at requested coordinates on hash change", function (done) { + hash(); + + d3.select(window).one('hashchange', function () { + expect(context.map().center()[0]).to.be.closeTo(-77.02405, 0.1); + expect(context.map().center()[1]).to.be.closeTo(38.87952, 0.1); + expect(context.map().zoom()).to.equal(20.0); + done(); }); - function onhashchange(fn) { - d3.select(window).one("hashchange", fn); - } - - it("centerZooms map at requested coordinates", function (done) { - onhashchange(function () { - expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0); - done(); - }); - - sinon.spy(map, 'centerZoom'); - location.hash = "#map=20.00/38.87952/-77.02405"; - }); + location.hash = "#map=20.00/38.87952/-77.02405"; }); - describe("on map move events", function () { - it("stores the current zoom and coordinates in location.hash", function () { - sinon.stub(map, 'on') - .withArgs("move.hash", sinon.match.instanceOf(Function)) - .yields(); - hash(); - expect(location.hash).to.equal("#map=0.00/0/0"); - }); + it("stores the current zoom and coordinates in location.hash on map move events", function () { + hash(); + + context.map().center([38.9, -77.0]); + context.map().zoom(2.0); + + expect(location.hash).to.equal("#map=2.00/-77.0/38.9"); }); }); diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index a284cf682..5c544c0c5 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -1,41 +1,36 @@ describe("iD.modes.AddPoint", function () { - var container, map, history, controller, mode; + var context; beforeEach(function () { - container = d3.select('body').append('div'); - history = iD.History(); - map = iD.Map().history(history); - controller = iD.Controller(map, history); + var container = d3.select(document.createElement('div')); - container.call(map); - container.append('div') + context = iD.Context() + .container(container); + + container.call(context.map()) + .append('div') .attr('class', 'inspector-wrap'); - mode = iD.modes.AddPoint(); - controller.enter(mode); - }); - - afterEach(function() { - container.remove(); + context.enter(iD.modes.AddPoint(context)); }); describe("clicking the map", function () { it("adds a node", function () { - happen.click(map.surface.node(), {}); - expect(history.changes().created).to.have.length(1); + happen.click(context.surface().node(), {}); + expect(context.changes().created).to.have.length(1); }); it("selects the node", function () { - happen.click(map.surface.node(), {}); - expect(controller.mode.id).to.equal('select'); - expect(controller.mode.selection()).to.eql([history.changes().created[0].id]); + happen.click(context.surface().node(), {}); + expect(context.mode().id).to.equal('select'); + expect(context.mode().selection()).to.eql([context.changes().created[0].id]); }); }); describe("pressing ⎋", function () { it("exits to browse mode", function () { happen.keydown(document, {keyCode: 27}); - expect(controller.mode.id).to.equal('browse'); + expect(context.mode().id).to.equal('browse'); }); }); }); diff --git a/test/spec/ui/confirm.js b/test/spec/ui/confirm.js index a9380e1cc..428a8aa6c 100644 --- a/test/spec/ui/confirm.js +++ b/test/spec/ui/confirm.js @@ -2,10 +2,13 @@ describe("iD.ui.confirm", function () { it('can be instantiated', function () { var confirm = iD.ui.confirm(); expect(confirm).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); + it('can be dismissed', function () { var confirm = iD.ui.confirm(); happen.click(confirm.select('button').node()); expect(confirm.node().parentNode).to.be.null; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); diff --git a/test/spec/ui/modal.js b/test/spec/ui/modal.js index 32a42878a..d7f818d80 100644 --- a/test/spec/ui/modal.js +++ b/test/spec/ui/modal.js @@ -4,5 +4,6 @@ describe("iD.ui.modal", function () { .select('.content') .text('foo'); expect(modal).to.be.ok; + happen.keydown(document, {keyCode: 27}); // dismiss }); }); From 579d6325636938443f34837fc05dd856db65539f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Thu, 31 Jan 2013 17:19:01 -0500 Subject: [PATCH 06/50] Hoist functions up a scope --- js/id/modes/add_area.js | 120 +++++++++++++++++------------------ js/id/modes/add_line.js | 134 +++++++++++++++++++-------------------- js/id/modes/add_point.js | 62 +++++++++--------- 3 files changed, 155 insertions(+), 161 deletions(-) diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js index ae2e008ee..0a9496bb6 100644 --- a/js/id/modes/add_area.js +++ b/js/id/modes/add_area.js @@ -7,71 +7,69 @@ iD.modes.AddArea = function(context) { key: t('modes.add_area.key') }; - var behavior, - defaultTags = {area: 'yes'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawArea(context, way.id, graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {area: 'yes'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawArea(context, way.id, graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_area.tail')); }; diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js index 79c5f3031..7845e6e6f 100644 --- a/js/id/modes/add_line.js +++ b/js/id/modes/add_line.js @@ -7,78 +7,76 @@ iD.modes.AddLine = function(context) { key: t('modes.add_line.key') }; - var behavior, - defaultTags = {highway: 'residential'}; - - mode.enter = function() { - function start(loc) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromWay(other, loc, index) { - var graph = context.graph(), - node = iD.Node({loc: loc}), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id), - iD.actions.AddVertex(other.id, node.id, index)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - function startFromNode(node) { - var graph = context.graph(), - parent = graph.parentWays(node)[0], - isLine = parent && parent.geometry(graph) === 'line'; - - if (isLine && parent.first() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); - - } else if (isLine && parent.last() === node.id) { - context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); - - } else { - var way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - } - - function startFromMidpoint(midpoint) { - var graph = context.graph(), - node = iD.Node(), - way = iD.Way({tags: defaultTags}); - - context.perform( - iD.actions.AddMidpoint(midpoint, node), - iD.actions.AddEntity(way), - iD.actions.AddVertex(way.id, node.id)); - - context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); - } - - behavior = iD.behavior.AddWay(context) + var behavior = iD.behavior.AddWay(context) .on('start', start) .on('startFromWay', startFromWay) .on('startFromNode', startFromNode) - .on('startFromMidpoint', startFromMidpoint); + .on('startFromMidpoint', startFromMidpoint), + defaultTags = {highway: 'residential'}; + function start(loc) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromWay(other, loc, index) { + var graph = context.graph(), + node = iD.Node({loc: loc}), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id), + iD.actions.AddVertex(other.id, node.id, index)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + function startFromNode(node) { + var graph = context.graph(), + parent = graph.parentWays(node)[0], + isLine = parent && parent.geometry(graph) === 'line'; + + if (isLine && parent.first() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'backward', graph)); + + } else if (isLine && parent.last() === node.id) { + context.enter(iD.modes.DrawLine(context, parent.id, 'forward', graph)); + + } else { + var way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + } + + function startFromMidpoint(midpoint) { + var graph = context.graph(), + node = iD.Node(), + way = iD.Way({tags: defaultTags}); + + context.perform( + iD.actions.AddMidpoint(midpoint, node), + iD.actions.AddEntity(way), + iD.actions.AddVertex(way.id, node.id)); + + context.enter(iD.modes.DrawLine(context, way.id, 'forward', graph)); + } + + mode.enter = function() { context.install(behavior); context.tail(t('modes.add_line.tail')); }; diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js index c3dc7f075..5a24dad25 100644 --- a/js/id/modes/add_point.js +++ b/js/id/modes/add_point.js @@ -6,46 +6,44 @@ iD.modes.AddPoint = function(context) { key: t('modes.add_point.key') }; - var behavior; + var behavior = iD.behavior.Draw(context) + .on('click', add) + .on('clickWay', addWay) + .on('clickNode', addNode) + .on('clickMidpoint', addNode) + .on('cancel', cancel) + .on('finish', cancel); + + function add(loc) { + var node = iD.Node({loc: loc}); + + context.perform( + iD.actions.AddEntity(node), + t('operations.add.annotation.point')); + + context.enter(iD.modes.Select(context, [node.id], true)); + } + + function addWay(way, loc, index) { + add(loc); + } + + function addNode(node) { + add(node.loc); + } + + function cancel() { + context.enter(iD.modes.Browse(context)); + } mode.enter = function() { - function add(loc) { - var node = iD.Node({loc: loc}); - - context.perform( - iD.actions.AddEntity(node), - t('operations.add.annotation.point')); - - context.enter(iD.modes.Select(context, [node.id], true)); - } - - function addWay(way, loc, index) { - add(loc); - } - - function addNode(node) { - add(node.loc); - } - - function cancel() { - context.enter(iD.modes.Browse(context)); - } - - behavior = iD.behavior.Draw(context) - .on('click', add) - .on('clickWay', addWay) - .on('clickNode', addNode) - .on('clickMidpoint', addNode) - .on('cancel', cancel) - .on('finish', cancel); - context.install(behavior); context.tail(t('modes.add_point.tail')); }; mode.exit = function() { - context.tail(false); context.uninstall(behavior); + context.tail(false); }; return mode; From 000ceb6467d7c79e22522a2575b413bd8d09b8f3 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:36:53 -0500 Subject: [PATCH 07/50] iD.Context -> iD All the UI setup code moved to iD.ui. --- Makefile | 1 - index.html | 9 +- index_packaged.html | 14 +- js/id/context.js | 44 ----- js/id/id.js | 323 +++++------------------------------ js/id/ui.js | 257 +++++++++++++++++++++++++++- test/index.html | 1 - test/spec/behavior/hash.js | 2 +- test/spec/modes/add_point.js | 2 +- 9 files changed, 319 insertions(+), 334 deletions(-) delete mode 100644 js/id/context.js diff --git a/Makefile b/Makefile index 598bce278..1db4f2efb 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ all: \ js/lib/sha.js \ js/id/start.js \ js/id/id.js \ - js/id/context.js \ js/id/connection.js \ js/id/oauth.js \ js/id/services/*.js \ diff --git a/index.html b/index.html index 306575804..ab6070fbc 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - @@ -135,9 +134,13 @@ locale.current = 'en'; d3.json('keys.json', function(err, keys) { var id = iD(); - id.connection().keys(keys) + + id.connection() + .keys(keys) .url('http://api06.dev.openstreetmap.org'); - d3.select("#iD").call(id); + + d3.select("#iD") + .call(id.ui()) }); diff --git a/index_packaged.html b/index_packaged.html index c48a21873..a961a5202 100644 --- a/index_packaged.html +++ b/index_packaged.html @@ -16,8 +16,16 @@
diff --git a/js/id/context.js b/js/id/context.js deleted file mode 100644 index 60f878b32..000000000 --- a/js/id/context.js +++ /dev/null @@ -1,44 +0,0 @@ -iD.Context = function() { - var history = iD.History(), - connection = iD.Connection(), - controller = iD.Controller(), - container, - map = iD.Map().connection(connection).history(history); - - var context = {}; - - context.container = function (_) { - if (!arguments.length) return container; - container = _; - return context; - }; - - context.connection = function () { return connection; }; - - context.history = function () { return history; }; - context.graph = history.graph; - context.perform = history.perform; - context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; - context.changes = history.changes; - - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - - context.controller = function () { return controller; }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; - - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; - - context.map = function () { return map; }; - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; - context.projection = map.projection; - context.tail = map.tail; - - return context; -}; diff --git a/js/id/id.js b/js/id/id.js index 3c2d0c482..42a8cfb11 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,291 +1,56 @@ -window.iD = function(container) { - // the reported, displayed version of iD. - var version = '0.0.0-alpha1'; +window.iD = function () { + var context = {}, + history = iD.History(), + connection = iD.Connection().version(iD.version), + controller = iD.Controller(), + container, + ui = iD.ui(context), + map = iD.Map().connection(connection).history(history); - var context = iD.Context(); + /* Straight accessors. Avoid using these if you can. */ + context.ui = function () { return ui; }; + context.connection = function () { return connection; }; + context.history = function () { return history; }; + context.controller = function () { return controller; }; + context.map = function () { return map; }; - var connection = context.connection(), - history = context.history(), - map = context.map(), - controller = context.controller(); + /* History delegation. */ + context.graph = history.graph; + context.perform = history.perform; + context.replace = history.replace; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; + context.changes = history.changes; - context.connection() - .version(version); + context.entity = function (id) { return history.graph().entity(id); }; + context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + + context.enter = controller.enter; + context.mode = function () { return controller.mode; }; + + context.install = function (behavior) { context.surface().call(behavior); }; + context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + + context.background = function () { return map.background; }; + context.surface = function () { return map.surface; }; + context.projection = map.projection; + context.tail = map.tail; + + context.container = function (_) { + if (!arguments.length) return container; + container = _; + return context; + }; context.background() .source(iD.BackgroundSource.Bing); - function editor(container) { - context.container(container); - - if (!iD.supported()) { - container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + - 'and Internet Explorer 9 and above. Please upgrade your browser ' + - 'or use Potlatch 2 to edit the map.') - .style('text-align:center;font-style:italic;'); - return; - } - - function hintprefix(x, y) { - return '' + y + '' + '
' + x + '
'; - } - - var m = container.append('div') - .attr('id', 'map') - .call(map); - - var bar = container.append('div') - .attr('id', 'bar') - .attr('class','pad1 fillD'); - - var limiter = bar.append('div') - .attr('class', 'limiter'); - - var buttons_joined = limiter.append('div') - .attr('class', 'button-wrap joined col4'); - - var modes = [ - iD.modes.Browse(context), - iD.modes.AddPoint(context), - iD.modes.AddLine(context), - iD.modes.AddArea(context)]; - - var buttons = buttons_joined.selectAll('button.add-button') - .data(modes) - .enter().append('button') - .attr('tabindex', -1) - .attr('class', function (mode) { return mode.title + ' add-button col3'; }) - .call(bootstrap.tooltip().placement('bottom').html(true)) - .attr('data-original-title', function (mode) { - return hintprefix(mode.key, mode.description); - }) - .on('click.editor', function (mode) { controller.enter(mode); }); - - function disableTooHigh() { - if (map.editable()) { - notice.message(false); - buttons.attr('disabled', null); - } else { - buttons.attr('disabled', 'disabled'); - notice.message(true); - controller.enter(iD.modes.Browse(context)); - } - } - - var notice = iD.ui.notice(limiter) - .message(false) - .on('zoom', function() { map.zoom(16); }); - - map.on('move.editor', _.debounce(function() { - disableTooHigh(); - contributors.call(iD.ui.contributors(map)); - }, 500)); - - buttons.append('span') - .attr('class', function(d) { - return d.id + ' icon icon-pre-text'; - }); - - buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - - controller.on('enter.editor', function (entered) { - buttons.classed('active', function (mode) { return entered.button === mode.button; }); - container.classed("mode-" + entered.id, true); - }); - - controller.on('exit.editor', function (exited) { - container.classed("mode-" + exited.id, false); - }); - - var undo_buttons = limiter.append('div') - .attr('class', 'button-wrap joined col1'), - undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); - - undo_buttons.append('button') - .attr({ id: 'undo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.undo) - .call(undo_tooltip); - - undo_buttons.append('button') - .attr({ id: 'redo', 'class': 'col6' }) - .property('disabled', true) - .html("") - .on('click.editor', history.redo) - .call(undo_tooltip); - - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') - .attr('class', 'save col12') - .call(iD.ui.save(context)); - - var zoom = container.append('div') - .attr('class', 'zoombuttons map-control') - .selectAll('button') - .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) - .enter() - .append('button') - .attr('tabindex', -1) - .attr('class', function(d) { return d[0]; }) - .attr('title', function(d) { return d[3]; }) - .on('click.editor', function(d) { return d[2](); }) - .append('span') - .attr('class', function(d) { - return d[0] + ' icon'; - }); - - if (navigator.geolocation) { - container.append('div') - .call(iD.ui.geolocate(map)); - } - - var gc = container.append('div').attr('class', 'geocode-control map-control') - .call(iD.ui.geocoder().map(map)); - - container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); - - container.append('div') - .style('display', 'none') - .attr('class', 'inspector-wrap fr col5'); - - var about = container.append('div') - .attr('class','col12 about-block fillD pad1'); - - about.append('div') - .attr('class', 'user-container') - .append('div') - .attr('class', 'hello'); - - var aboutList = about.append('ul') - .attr('id','about') - .attr('class','link-list'); - - var linkList = aboutList.append('ul') - .attr('id','about') - .attr('class','pad1 fillD about-block link-list'); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD').text(version); - linkList.append('li').append('a').attr('target', '_blank') - .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); - - 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'); - - linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') - .text('dev') - .on('click.editor', function() { - d3.event.preventDefault(); - if (d3.select(this).classed('live')) { - map.flush().connection() - .url('http://api06.dev.openstreetmap.org'); - d3.select(this).text('dev').classed('live', false); - } else { - map.flush().connection() - .url('http://www.openstreetmap.org'); - d3.select(this).text('live').classed('live', true); - } - }); - - var contributors = linkList.append('li') - .attr('id', 'user-list'); - contributors.append('span') - .attr('class', 'icon nearby icon-pre-text'); - contributors.append('span') - .text('Viewing contributions by '); - contributors.append('span') - .attr('class', 'contributor-list'); - contributors.append('span') - .attr('class', 'contributor-count'); - - history.on('change.editor', function() { - window.onbeforeunload = history.hasChanges() ? function() { - return 'You have unsaved changes.'; - } : null; - - var undo = history.undoAnnotation(), - redo = history.redoAnnotation(); - - function refreshTooltip(selection) { - if (selection.property('disabled')) { - selection.call(undo_tooltip.hide); - } else if (selection.property('tooltipVisible')) { - selection.call(undo_tooltip.show); - } - } - - limiter.select('#undo') - .property('disabled', !undo) - .attr('data-original-title', hintprefix('⌘ + Z', undo)) - .call(refreshTooltip); - - limiter.select('#redo') - .property('disabled', !redo) - .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) - .call(refreshTooltip); - }); - - d3.select(window).on('resize.editor', function() { - map.size(m.size()); - }); - - var keybinding = d3.keybinding('main') - .on('⌘+Z', function() { history.undo(); }) - .on('⌃+Z', function() { history.undo(); }) - .on('⌘+⇧+Z', function() { history.redo(); }) - .on('⌃+⇧+Z', function() { history.redo(); }) - .on('⌫', function() { d3.event.preventDefault(); }); - - modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); - }); - - d3.select(document) - .call(keybinding); - - var hash = iD.behavior.Hash(context); - - hash(); - - if (!hash.hadHash) { - map.centerZoom([-77.02271, 38.90085], 20); - } - - d3.select('.user-container').call(iD.ui.userpanel(connection) - .on('logout.editor', connection.logout) - .on('login.editor', connection.authenticate)); - - controller.enter(iD.modes.Browse(context)); - - if (!localStorage.sawSplash) { - iD.ui.splash(); - localStorage.sawSplash = true; - } - } - - editor.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - return editor; - }; - - editor.map = function() { - return map; - }; - - editor.controller = function() { - return controller; - }; - - if (arguments.length) { - d3.select(container).call(editor); - } - - return editor; + return context; }; +iD.version = '0.0.0-alpha1'; + iD.supported = function() { if (navigator.appName !== 'Microsoft Internet Explorer') { return true; diff --git a/js/id/ui.js b/js/id/ui.js index 8901175f4..978d9175e 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -1 +1,256 @@ -iD.ui = {}; +iD.ui = function (context) { + return function(container) { + context.container(container); + + var connection = context.connection(), + history = context.history(), + map = context.map(), + controller = context.controller(); + + if (!iD.supported()) { + container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + + 'and Internet Explorer 9 and above. Please upgrade your browser ' + + 'or use Potlatch 2 to edit the map.') + .style('text-align:center;font-style:italic;'); + return; + } + + function hintprefix(x, y) { + return '' + y + '' + '
' + x + '
'; + } + + var m = container.append('div') + .attr('id', 'map') + .call(map); + + var bar = container.append('div') + .attr('id', 'bar') + .attr('class','pad1 fillD'); + + var limiter = bar.append('div') + .attr('class', 'limiter'); + + var buttons_joined = limiter.append('div') + .attr('class', 'button-wrap joined col4'); + + var modes = [ + iD.modes.Browse(context), + iD.modes.AddPoint(context), + iD.modes.AddLine(context), + iD.modes.AddArea(context)]; + + var buttons = buttons_joined.selectAll('button.add-button') + .data(modes) + .enter().append('button') + .attr('tabindex', -1) + .attr('class', function (mode) { return mode.title + ' add-button col3'; }) + .call(bootstrap.tooltip().placement('bottom').html(true)) + .attr('data-original-title', function (mode) { + return hintprefix(mode.key, mode.description); + }) + .on('click.editor', function (mode) { controller.enter(mode); }); + + function disableTooHigh() { + if (map.editable()) { + notice.message(false); + buttons.attr('disabled', null); + } else { + buttons.attr('disabled', 'disabled'); + notice.message(true); + controller.enter(iD.modes.Browse(context)); + } + } + + var notice = iD.ui.notice(limiter) + .message(false) + .on('zoom', function() { map.zoom(16); }); + + map.on('move.editor', _.debounce(function() { + disableTooHigh(); + contributors.call(iD.ui.contributors(map)); + }, 500)); + + buttons.append('span') + .attr('class', function(d) { + return d.id + ' icon icon-pre-text'; + }); + + buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); + + controller.on('enter.editor', function (entered) { + buttons.classed('active', function (mode) { return entered.button === mode.button; }); + container.classed("mode-" + entered.id, true); + }); + + controller.on('exit.editor', function (exited) { + container.classed("mode-" + exited.id, false); + }); + + var undo_buttons = limiter.append('div') + .attr('class', 'button-wrap joined col1'), + undo_tooltip = bootstrap.tooltip().placement('bottom').html(true); + + undo_buttons.append('button') + .attr({ id: 'undo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.undo) + .call(undo_tooltip); + + undo_buttons.append('button') + .attr({ id: 'redo', 'class': 'col6' }) + .property('disabled', true) + .html("") + .on('click.editor', history.redo) + .call(undo_tooltip); + + var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + .attr('class', 'save col12') + .call(iD.ui.save(context)); + + var zoom = container.append('div') + .attr('class', 'zoombuttons map-control') + .selectAll('button') + .data([['zoom-in', '+', map.zoomIn, 'Zoom In'], ['zoom-out', '-', map.zoomOut, 'Zoom Out']]) + .enter() + .append('button') + .attr('tabindex', -1) + .attr('class', function(d) { return d[0]; }) + .attr('title', function(d) { return d[3]; }) + .on('click.editor', function(d) { return d[2](); }) + .append('span') + .attr('class', function(d) { + return d[0] + ' icon'; + }); + + if (navigator.geolocation) { + container.append('div') + .call(iD.ui.geolocate(map)); + } + + var gc = container.append('div').attr('class', 'geocode-control map-control') + .call(iD.ui.geocoder().map(map)); + + container.append('div').attr('class', 'map-control layerswitcher-control') + .call(iD.ui.layerswitcher(map)); + + container.append('div') + .style('display', 'none') + .attr('class', 'inspector-wrap fr col5'); + + var about = container.append('div') + .attr('class','col12 about-block fillD pad1'); + + about.append('div') + .attr('class', 'user-container') + .append('div') + .attr('class', 'hello'); + + var aboutList = about.append('ul') + .attr('id','about') + .attr('class','link-list'); + + var linkList = aboutList.append('ul') + .attr('id','about') + .attr('class','pad1 fillD about-block link-list'); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD').text(iD.version); + linkList.append('li').append('a').attr('target', '_blank') + .attr('href', 'http://github.com/systemed/iD/issues').text('report a bug'); + + 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'); + + linkList.append('li').attr('class', 'source-switch').append('a').attr('href', '#') + .text('dev') + .on('click.editor', function() { + d3.event.preventDefault(); + if (d3.select(this).classed('live')) { + map.flush().connection() + .url('http://api06.dev.openstreetmap.org'); + d3.select(this).text('dev').classed('live', false); + } else { + map.flush().connection() + .url('http://www.openstreetmap.org'); + d3.select(this).text('live').classed('live', true); + } + }); + + var contributors = linkList.append('li') + .attr('id', 'user-list'); + contributors.append('span') + .attr('class', 'icon nearby icon-pre-text'); + contributors.append('span') + .text('Viewing contributions by '); + contributors.append('span') + .attr('class', 'contributor-list'); + contributors.append('span') + .attr('class', 'contributor-count'); + + history.on('change.editor', function() { + window.onbeforeunload = history.hasChanges() ? function() { + return 'You have unsaved changes.'; + } : null; + + var undo = history.undoAnnotation(), + redo = history.redoAnnotation(); + + function refreshTooltip(selection) { + if (selection.property('disabled')) { + selection.call(undo_tooltip.hide); + } else if (selection.property('tooltipVisible')) { + selection.call(undo_tooltip.show); + } + } + + limiter.select('#undo') + .property('disabled', !undo) + .attr('data-original-title', hintprefix('⌘ + Z', undo)) + .call(refreshTooltip); + + limiter.select('#redo') + .property('disabled', !redo) + .attr('data-original-title', hintprefix('⌘ + ⇧ + Z', redo)) + .call(refreshTooltip); + }); + + d3.select(window).on('resize.editor', function() { + map.size(m.size()); + }); + + var keybinding = d3.keybinding('main') + .on('⌘+Z', function() { history.undo(); }) + .on('⌃+Z', function() { history.undo(); }) + .on('⌘+⇧+Z', function() { history.redo(); }) + .on('⌃+⇧+Z', function() { history.redo(); }) + .on('⌫', function() { d3.event.preventDefault(); }); + + modes.forEach(function(m) { + keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + }); + + d3.select(document) + .call(keybinding); + + var hash = iD.behavior.Hash(context); + + hash(); + + if (!hash.hadHash) { + map.centerZoom([-77.02271, 38.90085], 20); + } + + d3.select('.user-container').call(iD.ui.userpanel(connection) + .on('logout.editor', connection.logout) + .on('login.editor', connection.authenticate)); + + controller.enter(iD.modes.Browse(context)); + + if (!localStorage.sawSplash) { + iD.ui.splash(); + localStorage.sawSplash = true; + } + }; +}; diff --git a/test/index.html b/test/index.html index b56645ee7..879cc6171 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - diff --git a/test/spec/behavior/hash.js b/test/spec/behavior/hash.js index 5aceb53fc..7f9200ae1 100644 --- a/test/spec/behavior/hash.js +++ b/test/spec/behavior/hash.js @@ -4,7 +4,7 @@ describe("iD.behavior.Hash", function () { var hash, context; beforeEach(function () { - context = iD.Context(); + context = iD(); // Neuter connection context.connection().loadTiles = function () {}; diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js index 5c544c0c5..38a614198 100644 --- a/test/spec/modes/add_point.js +++ b/test/spec/modes/add_point.js @@ -4,7 +4,7 @@ describe("iD.modes.AddPoint", function () { beforeEach(function () { var container = d3.select(document.createElement('div')); - context = iD.Context() + context = iD() .container(container); container.call(context.map()) From a78aeeb62591770ed2856389d07bceeb09b9f8b5 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:46:46 -0500 Subject: [PATCH 08/50] Merge controller into iD --- Makefile | 1 - index.html | 1 - js/id/behavior/hash.js | 2 +- js/id/controller.js | 19 ------------------- js/id/id.js | 22 +++++++++++++++++----- js/id/ui.js | 15 +++++++-------- test/index.html | 1 - 7 files changed, 25 insertions(+), 36 deletions(-) delete mode 100644 js/id/controller.js diff --git a/Makefile b/Makefile index 1db4f2efb..3b0f2d1b7 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ all: \ js/id/modes/*.js \ js/id/operations.js \ js/id/operations/*.js \ - js/id/controller.js \ js/id/graph/*.js \ js/id/renderer/*.js \ js/id/svg.js \ diff --git a/index.html b/index.html index ab6070fbc..52ff209f2 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,6 @@ - diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 871b73a35..9056db393 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -48,7 +48,7 @@ iD.behavior.Hash = function(context) { context.enter(iD.modes.Select([id])); }); - context.controller().on('enter.hash', function() { + context.on('enter.hash', function() { if (context.mode().id !== 'browse') selectoff(); }); } diff --git a/js/id/controller.js b/js/id/controller.js deleted file mode 100644 index af1e7c388..000000000 --- a/js/id/controller.js +++ /dev/null @@ -1,19 +0,0 @@ -// A controller holds a single action at a time and calls `.enter` and `.exit` -// to bind and unbind actions. -iD.Controller = function() { - var event = d3.dispatch('enter', 'exit'); - var controller = { mode: null }; - - controller.enter = function(mode) { - if (controller.mode) { - controller.mode.exit(); - event.exit(controller.mode); - } - - mode.enter(); - controller.mode = mode; - event.enter(mode); - }; - - return d3.rebind(controller, event, 'on'); -}; diff --git a/js/id/id.js b/js/id/id.js index 42a8cfb11..b2d28fb3f 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -2,7 +2,8 @@ window.iD = function () { var context = {}, history = iD.History(), connection = iD.Connection().version(iD.version), - controller = iD.Controller(), + dispatch = d3.dispatch('enter', 'exit'), + mode, container, ui = iD.ui(context), map = iD.Map().connection(connection).history(history); @@ -11,7 +12,6 @@ window.iD = function () { context.ui = function () { return ui; }; context.connection = function () { return connection; }; context.history = function () { return history; }; - context.controller = function () { return controller; }; context.map = function () { return map; }; /* History delegation. */ @@ -26,8 +26,20 @@ window.iD = function () { context.entity = function (id) { return history.graph().entity(id); }; context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; - context.enter = controller.enter; - context.mode = function () { return controller.mode; }; + context.enter = function(newMode) { + if (mode) { + mode.exit(); + dispatch.exit(mode); + } + + mode = newMode; + mode.enter(); + dispatch.enter(mode); + }; + + context.mode = function() { + return mode; + }; context.install = function (behavior) { context.surface().call(behavior); }; context.uninstall = function (behavior) { context.surface().call(behavior.off); }; @@ -46,7 +58,7 @@ window.iD = function () { context.background() .source(iD.BackgroundSource.Bing); - return context; + return d3.rebind(context, dispatch, 'on'); }; iD.version = '0.0.0-alpha1'; diff --git a/js/id/ui.js b/js/id/ui.js index 978d9175e..6b9b3cc08 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -4,8 +4,7 @@ iD.ui = function (context) { var connection = context.connection(), history = context.history(), - map = context.map(), - controller = context.controller(); + map = context.map(); if (!iD.supported()) { container.html('This editor is supported in Firefox, Chrome, Safari, Opera, ' + @@ -48,7 +47,7 @@ iD.ui = function (context) { .attr('data-original-title', function (mode) { return hintprefix(mode.key, mode.description); }) - .on('click.editor', function (mode) { controller.enter(mode); }); + .on('click.editor', function (mode) { context.enter(mode); }); function disableTooHigh() { if (map.editable()) { @@ -57,7 +56,7 @@ iD.ui = function (context) { } else { buttons.attr('disabled', 'disabled'); notice.message(true); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); } } @@ -77,12 +76,12 @@ iD.ui = function (context) { buttons.append('span').attr('class', 'label').text(function (mode) { return mode.title; }); - controller.on('enter.editor', function (entered) { + context.on('enter.editor', function (entered) { buttons.classed('active', function (mode) { return entered.button === mode.button; }); container.classed("mode-" + entered.id, true); }); - controller.on('exit.editor', function (exited) { + context.on('exit.editor', function (exited) { container.classed("mode-" + exited.id, false); }); @@ -228,7 +227,7 @@ iD.ui = function (context) { .on('⌫', function() { d3.event.preventDefault(); }); modes.forEach(function(m) { - keybinding.on(m.key, function() { if (map.editable()) controller.enter(m); }); + keybinding.on(m.key, function() { if (map.editable()) context.enter(m); }); }); d3.select(document) @@ -246,7 +245,7 @@ iD.ui = function (context) { .on('logout.editor', connection.logout) .on('login.editor', connection.authenticate)); - controller.enter(iD.modes.Browse(context)); + context.enter(iD.modes.Browse(context)); if (!localStorage.sawSplash) { iD.ui.splash(); diff --git a/test/index.html b/test/index.html index 879cc6171..d32331ba9 100644 --- a/test/index.html +++ b/test/index.html @@ -119,7 +119,6 @@ - From 6188d128f8d12e73a874b71c46ad8f0c6c078ca6 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 11:50:07 -0500 Subject: [PATCH 09/50] Arrange --- js/id/id.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index b2d28fb3f..ce8131bc9 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -14,7 +14,7 @@ window.iD = function () { context.history = function () { return history; }; context.map = function () { return map; }; - /* History delegation. */ + /* History */ context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; @@ -23,9 +23,16 @@ window.iD = function () { context.redo = history.undo; context.changes = history.changes; - context.entity = function (id) { return history.graph().entity(id); }; - context.geometry = function (id) { return context.entity(id).geometry(history.graph()); }; + /* Graph */ + context.entity = function (id) { + return history.graph().entity(id); + }; + context.geometry = function (id) { + return context.entity(id).geometry(history.graph()); + }; + + /* Modes */ context.enter = function(newMode) { if (mode) { mode.exit(); @@ -41,9 +48,16 @@ window.iD = function () { return mode; }; - context.install = function (behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { context.surface().call(behavior.off); }; + /* Behaviors */ + context.install = function (behavior) { + context.surface().call(behavior); + }; + context.uninstall = function (behavior) { + context.surface().call(behavior.off); + }; + + /* Map */ context.background = function () { return map.background; }; context.surface = function () { return map.surface; }; context.projection = map.projection; From fe49e8fb77637e0598d9c381b7f24385de367c27 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:17:24 -0500 Subject: [PATCH 10/50] Pass around context rather than map --- js/id/behavior/draw.js | 2 +- js/id/behavior/draw_way.js | 2 +- js/id/geo.js | 6 +++--- js/id/id.js | 3 ++- js/id/modes/select.js | 2 +- js/id/renderer/map.js | 35 +++++++++++++---------------------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 4 ++-- js/id/ui/contributors.js | 8 ++++---- js/id/ui/layerswitcher.js | 20 ++++++++++---------- js/id/ui/save.js | 4 ++-- test/spec/renderer/map.js | 20 ++++---------------- 12 files changed, 45 insertions(+), 65 deletions(-) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 8405ac341..625fc04a4 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -29,7 +29,7 @@ iD.behavior.Draw = function(context) { function click() { var d = datum(); if (d.type === 'way') { - var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context.map()); + var choice = iD.geo.chooseIndex(d, d3.mouse(context.surface().node()), context); event.clickWay(d, choice.loc, choice.index); } else if (d.type === 'node') { diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 28a8fbb38..9efcd5bb9 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -19,7 +19,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { if (datum.type === 'node' || datum.type === 'midpoint') { loc = datum.loc; } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context.map()).loc; + loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); diff --git a/js/id/geo.js b/js/id/geo.js index fcce74b9a..dcc11717f 100644 --- a/js/id/geo.js +++ b/js/id/geo.js @@ -14,11 +14,11 @@ iD.geo.dist = function(a, b) { Math.pow(a[1] - b[1], 2)); }; -iD.geo.chooseIndex = function(way, point, map) { +iD.geo.chooseIndex = function(way, point, context) { var dist = iD.geo.dist, - graph = map.history().graph(), + graph = context.graph(), nodes = graph.childNodes(way), - projNodes = nodes.map(function(n) { return map.projection(n.loc); }); + projNodes = nodes.map(function(n) { return context.projection(n.loc); }); for (var i = 0, changes = []; i < projNodes.length - 1; i++) { changes[i] = diff --git a/js/id/id.js b/js/id/id.js index ce8131bc9..713377f15 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -6,7 +6,7 @@ window.iD = function () { mode, container, ui = iD.ui(context), - map = iD.Map().connection(connection).history(history); + map = iD.Map(context); /* Straight accessors. Avoid using these if you can. */ context.ui = function () { return ui; }; @@ -62,6 +62,7 @@ window.iD = function () { context.surface = function () { return map.surface; }; context.projection = map.projection; context.tail = map.tail; + context.redraw = map.redraw; context.container = function (_) { if (!arguments.length) return container; diff --git a/js/id/modes/select.js b/js/id/modes/select.js index df445862d..36066d7d0 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -108,7 +108,7 @@ iD.modes.Select = function(context, selection, initial) { if (datum instanceof iD.Way && !target.classed('fill')) { var choice = iD.geo.chooseIndex(datum, - d3.mouse(context.surface().node()), context.map()), + d3.mouse(context.surface().node()), context), node = iD.Node({ loc: choice.loc }); context.perform( diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index cd99de738..b738feca4 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -1,6 +1,5 @@ -iD.Map = function() { - var connection, history, - dimensions = [], +iD.Map = function(context) { + var dimensions = [], dispatch = d3.dispatch('move', 'drawn'), projection = d3.geo.mercator().scale(1024), roundedProjection = iD.svg.RoundProjection(projection), @@ -27,6 +26,12 @@ iD.Map = function() { surface, tilegroup; function map(selection) { + context.connection() + .on('load.tile', connectionLoad); + + context.history() + .on('change.map', redraw); + selection.call(zoom); tilegroup = selection.append('div') @@ -57,7 +62,7 @@ iD.Map = function() { function drawVector(difference) { var filter, all, extent = map.extent(), - graph = history.graph(); + graph = context.graph(); function addParents(parents) { for (var i = 0; i < parents.length; i++) { @@ -121,7 +126,7 @@ iD.Map = function() { } function connectionLoad(err, result) { - history.merge(result); + context.history().merge(result); redraw(Object.keys(result)); } @@ -182,7 +187,7 @@ iD.Map = function() { tilegroup.call(background); if (map.editable()) { - connection.loadTiles(projection, dimensions); + context.connection().loadTiles(projection, dimensions); drawVector(difference); } else { editOff(); @@ -347,15 +352,8 @@ iD.Map = function() { }; map.flush = function () { - connection.flush(); - history.reset(); - return map; - }; - - map.connection = function(_) { - if (!arguments.length) return connection; - connection = _; - connection.on('load.tile', connectionLoad); + context.connection().flush(); + context.history().reset(); return map; }; @@ -378,13 +376,6 @@ iD.Map = function() { return map; }; - map.history = function (_) { - if (!arguments.length) return history; - history = _; - history.on('change.map', redraw); - return map; - }; - map.background = background; map.projection = projection; map.redraw = redraw; diff --git a/js/id/ui.js b/js/id/ui.js index 6b9b3cc08..5714e05ca 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -66,7 +66,7 @@ iD.ui = function (context) { map.on('move.editor', _.debounce(function() { disableTooHigh(); - contributors.call(iD.ui.contributors(map)); + contributors.call(iD.ui.contributors(context)); }, 500)); buttons.append('span') @@ -131,7 +131,7 @@ iD.ui = function (context) { .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') - .call(iD.ui.layerswitcher(map)); + .call(iD.ui.layerswitcher(context)); container.append('div') .style('display', 'none') diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index d883b1748..6c1133b35 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,4 +1,4 @@ -iD.ui.commit = function(map) { +iD.ui.commit = function(context) { var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { @@ -89,7 +89,7 @@ iD.ui.commit = function(map) { cancelbutton.append('span').attr('class','label').text('Cancel'); var warnings = body.selectAll('div.warning-section') - .data(iD.validate(changes, map.history().graph())) + .data(iD.validate(changes, context.graph())) .enter() .append('div').attr('class', 'modal-section warning-section fillL'); diff --git a/js/id/ui/contributors.js b/js/id/ui/contributors.js index c64d8a2ac..e029bfd04 100644 --- a/js/id/ui/contributors.js +++ b/js/id/ui/contributors.js @@ -1,10 +1,10 @@ -iD.ui.contributors = function(map) { +iD.ui.contributors = function(context) { function contributors(selection) { var users = {}, limit = 3, - entities = map.history().graph().intersects(map.extent()); + entities = context.graph().intersects(context.map().extent()); for (var i in entities) { if (entities[i].user) users[entities[i].user] = true; @@ -21,7 +21,7 @@ iD.ui.contributors = function(map) { l.enter().append('a') .attr('class', 'user-link') - .attr('href', function(d) { return map.connection().userUrl(d); }) + .attr('href', function(d) { return context.connection().userUrl(d); }) .attr('target', '_blank') .text(String); @@ -37,7 +37,7 @@ iD.ui.contributors = function(map) { .append('a') .attr('target', '_blank') .attr('href', function() { - var ext = map.extent(); + var ext = context.map().extent(); return 'http://www.openstreetmap.org/browse/changesets?bbox=' + [ ext[0][0], ext[0][1], ext[1][0], ext[1][1]]; diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index 298c469e0..c64457ab8 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -1,4 +1,4 @@ -iD.ui.layerswitcher = function(map) { +iD.ui.layerswitcher = function(context) { var event = d3.dispatch('cancel', 'save'), sources = [{ name: 'Bing', @@ -94,7 +94,7 @@ iD.ui.layerswitcher = function(map) { function selectLayer(d) { content.selectAll('a.layer') .classed('selected', function(d) { - return d.source === map.background.source(); + return d.source === context.background().source(); }); d3.select('#attribution a') .attr('href', d.link) @@ -126,9 +126,9 @@ iD.ui.layerswitcher = function(map) { d.source = configured; d.name = 'Custom (configured)'; } - map.background.source(d.source); - map.history().imagery_used(d.name); - map.redraw(); + context.background().source(d.source); + context.history().imagery_used(d.name); + context.redraw(); selectLayer(d); }) .insert('span') @@ -145,8 +145,8 @@ iD.ui.layerswitcher = function(map) { ['bottom', [0, 1]]]; function nudge(d) { - map.background.nudge(d[1]); - map.redraw(); + context.background().nudge(d[1]); + context.redraw(); } adjustments.append('a') @@ -181,12 +181,12 @@ iD.ui.layerswitcher = function(map) { .text('reset') .attr('class', 'reset') .on('click', function() { - map.background.offset([0, 0]); - map.redraw(); + context.background().offset([0, 0]); + context.redraw(); }); selection.call(clickoutside); - selectLayer(map.background.source()); + selectLayer(context.background().source()); } return d3.rebind(layerswitcher, event, 'on'); diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 6098519da..ba217adcf 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -50,12 +50,12 @@ iD.ui.save = function(context) { modal.select('.content') .classed('commit-modal', true) .datum(changes) - .call(iD.ui.commit(map) + .call(iD.ui.commit(context) .on('cancel', function() { modal.remove(); }) .on('fix', function(d) { - map.extent(d.entity.extent(map.history().graph())); + map.extent(d.entity.extent(context.graph())); if (map.zoom() > 19) map.zoom(19); context.enter(iD.modes.Select(context, [d.entity.id])); modal.remove(); diff --git a/test/spec/renderer/map.js b/test/spec/renderer/map.js index d7d0e3440..de8abdf61 100644 --- a/test/spec/renderer/map.js +++ b/test/spec/renderer/map.js @@ -1,22 +1,10 @@ describe('iD.Map', function() { - var container, map; + var map; beforeEach(function() { - container = d3.select('body').append('div'); - map = iD.Map(); - container.call(map); - }); - - afterEach(function() { - container.remove(); - }); - - describe('#connection', function() { - it('gets and sets connection', function() { - var connection = iD.Connection(); - expect(map.connection(connection)).to.equal(map); - expect(map.connection()).to.equal(connection); - }); + map = iD().map(); + d3.select(document.createElement('div')) + .call(map); }); describe('#zoom', function() { From 0bcb0520d8eb826298d532695f3fe83b3b165e6a Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 12:46:01 -0500 Subject: [PATCH 11/50] Use iD.version --- js/id/connection.js | 9 +-------- js/id/id.js | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index d2afa6281..399c48d86 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -4,7 +4,6 @@ iD.Connection = function() { url = 'http://www.openstreetmap.org', connection = {}, user = {}, - version, keys, inflight = {}, loadedTiles = {}, @@ -186,7 +185,7 @@ iD.Connection = function() { content: JXON.stringify(connection.changesetJXON({ imagery_used: imagery_used.join(';'), comment: comment, - created_by: 'iD ' + (version || '') + created_by: 'iD ' + iD.version })) }, function (err, changeset_id) { if (err) return callback(err); @@ -322,12 +321,6 @@ iD.Connection = function() { return oauth.authenticate(done); }; - connection.version = function(_) { - if (!arguments.length) return version; - version = _; - return connection; - }; - connection.bboxFromAPI = bboxFromAPI; connection.changesetUrl = changesetUrl; connection.loadFromURL = loadFromURL; diff --git a/js/id/id.js b/js/id/id.js index 713377f15..ef026a097 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,7 +1,7 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection().version(iD.version), + connection = iD.Connection(), dispatch = d3.dispatch('enter', 'exit'), mode, container, From f1b6f5b14a10a958862942eacf452932bbd063f1 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 12:47:57 -0500 Subject: [PATCH 12/50] Fix Google Analytics include --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 52ff209f2..b0e46d55d 100644 --- a/index.html +++ b/index.html @@ -141,8 +141,7 @@ d3.select("#iD") .call(id.ui()) }); - - + + From 031c8d655e02f1686bf654d4a168f0decc16a7cf Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:12:46 -0500 Subject: [PATCH 13/50] Fallback for browsers that do not support localStorage. Fixes #591 --- js/id/connection.js | 4 ++-- js/id/id.js | 44 ++++++++++++++++++++++++----------------- js/id/oauth.js | 15 ++++++-------- js/id/ui.js | 4 ++-- js/id/ui/commit.js | 2 +- test/spec/connection.js | 3 ++- test/spec/oauth.js | 3 ++- 7 files changed, 41 insertions(+), 34 deletions(-) diff --git a/js/id/connection.js b/js/id/connection.js index 399c48d86..794232682 100644 --- a/js/id/connection.js +++ b/js/id/connection.js @@ -1,4 +1,4 @@ -iD.Connection = function() { +iD.Connection = function(context) { var event = d3.dispatch('auth', 'load'), url = 'http://www.openstreetmap.org', @@ -7,7 +7,7 @@ iD.Connection = function() { keys, inflight = {}, loadedTiles = {}, - oauth = iD.OAuth().url(url); + oauth = iD.OAuth(context).url(url); function changesetUrl(changesetId) { return url + '/browse/changeset/' + changesetId; diff --git a/js/id/id.js b/js/id/id.js index ef026a097..0b2bbd617 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -1,34 +1,42 @@ window.iD = function () { var context = {}, history = iD.History(), - connection = iD.Connection(), + storage = localStorage || {}, dispatch = d3.dispatch('enter', 'exit'), mode, container, ui = iD.ui(context), map = iD.Map(context); + context.storage = function(k, v) { + if (arguments.length === 1) return storage[k]; + else storage[k] = v; + }; + + // the connection requires .storage() to be available on calling. + var connection = iD.Connection(context); + /* Straight accessors. Avoid using these if you can. */ - context.ui = function () { return ui; }; - context.connection = function () { return connection; }; - context.history = function () { return history; }; - context.map = function () { return map; }; + context.ui = function() { return ui; }; + context.connection = function() { return connection; }; + context.history = function() { return history; }; + context.map = function() { return map; }; /* History */ - context.graph = history.graph; + context.graph = history.graph; context.perform = history.perform; context.replace = history.replace; - context.pop = history.pop; - context.undo = history.undo; - context.redo = history.undo; + context.pop = history.pop; + context.undo = history.undo; + context.redo = history.undo; context.changes = history.changes; /* Graph */ - context.entity = function (id) { + context.entity = function(id) { return history.graph().entity(id); }; - context.geometry = function (id) { + context.geometry = function(id) { return context.entity(id).geometry(history.graph()); }; @@ -49,22 +57,22 @@ window.iD = function () { }; /* Behaviors */ - context.install = function (behavior) { + context.install = function(behavior) { context.surface().call(behavior); }; - context.uninstall = function (behavior) { + context.uninstall = function(behavior) { context.surface().call(behavior.off); }; /* Map */ - context.background = function () { return map.background; }; - context.surface = function () { return map.surface; }; + context.background = function() { return map.background; }; + context.surface = function() { return map.surface; }; context.projection = map.projection; - context.tail = map.tail; - context.redraw = map.redraw; + context.tail = map.tail; + context.redraw = map.redraw; - context.container = function (_) { + context.container = function(_) { if (!arguments.length) return container; container = _; return context; diff --git a/js/id/oauth.js b/js/id/oauth.js index a09f2affc..4aa1d1876 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -1,4 +1,4 @@ -iD.OAuth = function() { +iD.OAuth = function(context) { var baseurl = 'http://www.openstreetmap.org', o = {}, keys, @@ -6,10 +6,6 @@ iD.OAuth = function() { function keyclean(x) { return x.replace(/\W/g, ''); } - if (token('oauth_token')) { - o.oauth_token = token('oauth_token'); - } - function timenonce(o) { o.oauth_timestamp = ohauth.timestamp(); o.oauth_nonce = ohauth.nonce(); @@ -18,10 +14,11 @@ iD.OAuth = function() { // token getter/setter, namespaced to the current `apibase` value. function token(k, x) { - if (arguments.length == 2) { - localStorage[keyclean(baseurl) + k] = x; - } - return localStorage[keyclean(baseurl) + k]; + return context.storage(keyclean(baseurl) + k, x); + } + + if (token('oauth_token')) { + o.oauth_token = token('oauth_token'); } oauth.authenticated = function() { diff --git a/js/id/ui.js b/js/id/ui.js index 5714e05ca..c28ca1282 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -247,9 +247,9 @@ iD.ui = function (context) { context.enter(iD.modes.Browse(context)); - if (!localStorage.sawSplash) { + if (!context.storage('sawSplash')) { iD.ui.splash(); - localStorage.sawSplash = true; + context.storage('sawSplash', true); } }; }; diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 6c1133b35..09bb99404 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -38,7 +38,7 @@ iD.ui.commit = function(context) { comment_section.append('textarea') .attr('class', 'changeset-comment') .attr('placeholder', 'Brief Description of your contributions') - .property('value', localStorage.comment || '') + .property('value', context.storage('comment') || '') .node().select(); var commit_info = diff --git a/test/spec/connection.js b/test/spec/connection.js index 652c1419b..cdc439a46 100644 --- a/test/spec/connection.js +++ b/test/spec/connection.js @@ -2,7 +2,8 @@ describe('iD.Connection', function () { var c; beforeEach(function () { - c = new iD.Connection(); + context = iD(); + c = new iD.Connection(context); }); it('is instantiated', function () { diff --git a/test/spec/oauth.js b/test/spec/oauth.js index 8fd57c18c..b7ae1074f 100644 --- a/test/spec/oauth.js +++ b/test/spec/oauth.js @@ -2,7 +2,8 @@ describe('iD.OAuth', function() { var o; beforeEach(function() { - o = iD.OAuth(); + context = iD(); + o = iD.OAuth(context); }); describe('#logout', function() { From 619216b33b27f5acae41881c499a4b32b61a1b02 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:13:21 -0500 Subject: [PATCH 14/50] Remove double dispatch --- js/id/renderer/map.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index b738feca4..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -263,7 +263,6 @@ iD.Map = function(context) { t[0] - ll[0] + c[0], t[1] - ll[1] + c[1]]); zoom.translate(projection.translate()); - dispatch.move(map); return true; } From 175cdc553d07477447299810375060ad39b7a073 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 13:17:19 -0500 Subject: [PATCH 15/50] Fix localStorage number of args in oauth --- js/id/oauth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/oauth.js b/js/id/oauth.js index 4aa1d1876..d7cb095a8 100644 --- a/js/id/oauth.js +++ b/js/id/oauth.js @@ -13,8 +13,8 @@ iD.OAuth = function(context) { } // token getter/setter, namespaced to the current `apibase` value. - function token(k, x) { - return context.storage(keyclean(baseurl) + k, x); + function token() { + return context.storage.apply(context, arguments); } if (token('oauth_token')) { From cdda561f6977482316d9cf5f4baf3d5c76bad607 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 13:39:39 -0500 Subject: [PATCH 16/50] Fix dev/live toggle --- js/id/ui.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index c28ca1282..8d764d695 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -167,11 +167,13 @@ iD.ui = function (context) { .on('click.editor', function() { d3.event.preventDefault(); if (d3.select(this).classed('live')) { - map.flush().connection() + map.flush(); + context.connection() .url('http://api06.dev.openstreetmap.org'); d3.select(this).text('dev').classed('live', false); } else { - map.flush().connection() + map.flush(); + context.connection() .url('http://www.openstreetmap.org'); d3.select(this).text('live').classed('live', true); } From 4fbbd1cf822a08310cdbdb819fde6f9cb5fa5162 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 14:11:32 -0500 Subject: [PATCH 17/50] Update to pre-release 3.0.5 d3 --- js/lib/d3.v3.js | 1604 ++++++++++++++++++++++++++--------------------- 1 file changed, 900 insertions(+), 704 deletions(-) diff --git a/js/lib/d3.v3.js b/js/lib/d3.v3.js index 93b676f46..818bab9dc 100644 --- a/js/lib/d3.v3.js +++ b/js/lib/d3.v3.js @@ -12,12 +12,9 @@ }; } d3 = { - version: "3.0.0pre" + version: "3.0.5" }; - var π = Math.PI, ε = 1e-6, εε = .001, d3_radians = π / 180, d3_degrees = 180 / π; - function d3_zero() { - return 0; - } + var π = Math.PI, ε = 1e-6, d3_radians = π / 180, d3_degrees = 180 / π; function d3_target(d) { return d.target; } @@ -234,7 +231,7 @@ return s; }; d3.quantile = function(values, p) { - var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; + var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h; return e ? v + e * (values[h] - v) : v; }; d3.shuffle = function(array) { @@ -454,8 +451,13 @@ d3.rebind(xhr, dispatch, "on"); if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null; - return callback == null ? xhr : xhr.get(callback); + return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); }; + function d3_xhr_fixCallback(callback) { + return callback.length === 1 ? function(error, request) { + callback(error == null ? request : null); + } : callback; + } d3.text = function() { return d3.xhr.apply(d3, arguments).response(d3_text); }; @@ -1687,13 +1689,13 @@ return value; } function bind(group, groupData) { - var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), n1 = Math.max(n, m), updateNodes = [], enterNodes = [], exitNodes = [], node, nodeData; + var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData; if (key) { - var nodeByKeyValue = new d3_Map(), keyValues = [], keyValue, j = groupData.length; + var nodeByKeyValue = new d3_Map(), dataByKeyValue = new d3_Map(), keyValues = [], keyValue; for (i = -1; ++i < n; ) { keyValue = key.call(node = group[i], node.__data__, i); if (nodeByKeyValue.has(keyValue)) { - exitNodes[j++] = node; + exitNodes[i] = node; } else { nodeByKeyValue.set(keyValue, node); } @@ -1701,14 +1703,13 @@ } for (i = -1; ++i < m; ) { keyValue = key.call(groupData, nodeData = groupData[i], i); - if (nodeByKeyValue.has(keyValue)) { - updateNodes[i] = node = nodeByKeyValue.get(keyValue); + if (node = nodeByKeyValue.get(keyValue)) { + updateNodes[i] = node; node.__data__ = nodeData; - enterNodes[i] = exitNodes[i] = null; - } else { + } else if (!dataByKeyValue.has(keyValue)) { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } + dataByKeyValue.set(keyValue, nodeData); nodeByKeyValue.remove(keyValue); } for (i = -1; ++i < n; ) { @@ -1723,19 +1724,15 @@ if (node) { node.__data__ = nodeData; updateNodes[i] = node; - enterNodes[i] = exitNodes[i] = null; } else { enterNodes[i] = d3_selection_dataNode(nodeData); - updateNodes[i] = exitNodes[i] = null; } } for (;i < m; ++i) { enterNodes[i] = d3_selection_dataNode(groupData[i]); - updateNodes[i] = exitNodes[i] = null; } - for (;i < n1; ++i) { + for (;i < n; ++i) { exitNodes[i] = group[i]; - enterNodes[i] = updateNodes[i] = null; } } enterNodes.update = updateNodes; @@ -3769,7 +3766,7 @@ d3.behavior.zoom = function() { var translate = [ 0, 0 ], translate0, scale = 1, scale0, scaleExtent = d3_behavior_zoomInfinity, event = d3_eventDispatch(zoom, "zoom"), x0, x1, y0, y1, touchtime; function zoom() { - this.on("mousedown.zoom", mousedown).on("mousewheel.zoom", mousewheel).on("mousemove.zoom", mousemove).on("DOMMouseScroll.zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); + this.on("mousedown.zoom", mousedown).on("mousemove.zoom", mousemove).on(d3_behavior_zoomWheel + ".zoom", mousewheel).on("dblclick.zoom", dblclick).on("touchstart.zoom", touchstart).on("touchmove.zoom", touchmove).on("touchend.zoom", touchstart); } zoom.translate = function(x) { if (!arguments.length) return translate; @@ -3901,21 +3898,14 @@ } return d3.rebind(zoom, event, "on"); }; - var d3_behavior_zoomDiv, d3_behavior_zoomInfinity = [ 0, Infinity ]; - function d3_behavior_zoomDelta() { - if (!d3_behavior_zoomDiv) { - d3_behavior_zoomDiv = d3.select("body").append("div").style("visibility", "hidden").style("top", 0).style("height", 0).style("width", 0).style("overflow-y", "scroll").append("div").style("height", "2000px").node().parentNode; - } - var e = d3.event, delta; - try { - d3_behavior_zoomDiv.scrollTop = 1e3; - d3_behavior_zoomDiv.dispatchEvent(e); - delta = 1e3 - d3_behavior_zoomDiv.scrollTop; - } catch (error) { - delta = e.wheelDelta || -e.detail * 5; - } - return delta; - } + var d3_behavior_zoomInfinity = [ 0, Infinity ]; + var d3_behavior_zoomDelta, d3_behavior_zoomWheel = "onwheel" in document ? (d3_behavior_zoomDelta = function() { + return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1); + }, "wheel") : "onmousewheel" in document ? (d3_behavior_zoomDelta = function() { + return d3.event.wheelDelta; + }, "mousewheel") : (d3_behavior_zoomDelta = function() { + return -d3.event.detail; + }, "MozMousePixelScroll"); d3.layout = {}; d3.layout.bundle = function() { return function(links) { @@ -4625,10 +4615,8 @@ } d3.layout.hierarchy = function() { var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue; - function recurse(data, depth, nodes) { - var childs = children.call(hierarchy, data, depth), node = d3_layout_hierarchyInline ? data : { - data: data - }; + function recurse(node, depth, nodes) { + var childs = children.call(hierarchy, node, depth); node.depth = depth; nodes.push(node); if (childs && (n = childs.length)) { @@ -4642,7 +4630,7 @@ if (sort) c.sort(sort); if (value) node.value = v; } else if (value) { - node.value = +value.call(hierarchy, data, depth) || 0; + node.value = +value.call(hierarchy, node, depth) || 0; } return node; } @@ -4652,7 +4640,7 @@ var i = -1, n, j = depth + 1; while (++i < n) v += revalue(children[i], j); } else if (value) { - v = +value.call(hierarchy, d3_layout_hierarchyInline ? node : node.data, depth) || 0; + v = +value.call(hierarchy, node, depth) || 0; } if (value) node.value = v; return v; @@ -4685,11 +4673,8 @@ }; function d3_layout_hierarchyRebind(object, hierarchy) { d3.rebind(object, hierarchy, "sort", "children", "value"); + object.nodes = object; object.links = d3_layout_hierarchyLinks; - object.nodes = function(d) { - d3_layout_hierarchyInline = true; - return (object.nodes = object)(d); - }; return object; } function d3_layout_hierarchyChildren(d) { @@ -4711,7 +4696,6 @@ }); })); } - var d3_layout_hierarchyInline = false; d3.layout.pack = function() { var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ]; function pack(d, i) { @@ -5096,7 +5080,7 @@ function squarify(node) { var children = node.children; if (children && children.length) { - var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" || mode === "slice-dice" && node.depth & 1 ? rect.dy : Math.min(rect.dx, rect.dy), n; + var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n; scale(remaining, rect.dx * rect.dy / node.value); row.area = 0; while ((n = remaining.length) > 0) { @@ -5333,78 +5317,168 @@ d3.csv = d3_dsv(",", "text/csv"); d3.tsv = d3_dsv(" ", "text/tab-separated-values"); d3.geo = {}; - function d3_geo_type(types) { - for (var type in d3_geo_typeDefaults) { - if (!(type in types)) { - types[type] = d3_geo_typeDefaults[type]; + d3.geo.stream = function(object, listener) { + if (d3_geo_streamObjectType.hasOwnProperty(object.type)) { + d3_geo_streamObjectType[object.type](object, listener); + } else { + d3_geo_streamGeometry(object, listener); + } + }; + function d3_geo_streamGeometry(geometry, listener) { + if (d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } + } + var d3_geo_streamObjectType = { + Feature: function(feature, listener) { + d3_geo_streamGeometry(feature.geometry, listener); + }, + FeatureCollection: function(object, listener) { + var features = object.features, i = -1, n = features.length; + while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener); + } + }; + var d3_geo_streamGeometryType = { + Sphere: function(object, listener) { + listener.sphere(); + }, + Point: function(object, listener) { + var coordinate = object.coordinates; + listener.point(coordinate[0], coordinate[1]); + }, + MultiPoint: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + }, + LineString: function(object, listener) { + d3_geo_streamLine(object.coordinates, listener, 0); + }, + MultiLineString: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0); + }, + Polygon: function(object, listener) { + d3_geo_streamPolygon(object.coordinates, listener); + }, + MultiPolygon: function(object, listener) { + var coordinates = object.coordinates, i = -1, n = coordinates.length; + while (++i < n) d3_geo_streamPolygon(coordinates[i], listener); + }, + GeometryCollection: function(object, listener) { + var geometries = object.geometries, i = -1, n = geometries.length; + while (++i < n) d3_geo_streamGeometry(geometries[i], listener); + } + }; + function d3_geo_streamLine(coordinates, listener, closed) { + var i = -1, n = coordinates.length - closed, coordinate; + listener.lineStart(); + while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); + listener.lineEnd(); + } + function d3_geo_streamPolygon(coordinates, listener) { + var i = -1, n = coordinates.length; + listener.polygonStart(); + while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1); + listener.polygonEnd(); + } + function d3_geo_spherical(cartesian) { + return [ Math.atan2(cartesian[1], cartesian[0]), Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) ]; + } + function d3_geo_sphericalEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + } + function d3_geo_cartesian(spherical) { + var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ); + return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ]; + } + function d3_geo_cartesianDot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + function d3_geo_cartesianCross(a, b) { + return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; + } + function d3_geo_cartesianAdd(a, b) { + a[0] += b[0]; + a[1] += b[1]; + a[2] += b[2]; + } + function d3_geo_cartesianScale(vector, k) { + return [ vector[0] * k, vector[1] * k, vector[2] * k ]; + } + function d3_geo_cartesianNormalize(d) { + var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + d[0] /= l; + d[1] /= l; + d[2] /= l; + } + function d3_geo_resample(project) { + var δ2 = .5, maxDepth = 16; + function resample(stream) { + var λ0, x0, y0, a0, b0, c0; + var resample = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + stream.polygonStart(); + resample.lineStart = polygonLineStart; + }, + polygonEnd: function() { + stream.polygonEnd(); + resample.lineStart = lineStart; + } + }; + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + function lineStart() { + x0 = NaN; + resample.point = linePoint; + stream.lineStart(); + } + function linePoint(λ, φ) { + var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ); + resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + function lineEnd() { + resample.point = point; + stream.lineEnd(); + } + function polygonLineStart() { + var λ00, φ00, x00, y00, a00, b00, c00; + lineStart(); + resample.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resample.point = linePoint; + }; + resample.lineEnd = function() { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream); + resample.lineEnd = lineEnd; + lineEnd(); + }; + } + return resample; + } + function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy; + if (d2 > 4 * δ2 && depth--) { + var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = Math.abs(Math.abs(c) - 1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > δ2 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { + resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream); + } } } - return types; + resample.precision = function(_) { + if (!arguments.length) return Math.sqrt(δ2); + maxDepth = (δ2 = _ * _) > 0 && 16; + return resample; + }; + return resample; } - var d3_geo_typeDefaults = { - Feature: function(feature) { - this.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - var features = collection.features, i = -1, n = features.length; - while (++i < n) this.Feature(features[i]); - }, - GeometryCollection: function(collection) { - var geometries = collection.geometries, i = -1, n = geometries.length; - while (++i < n) this.geometry(geometries[i]); - }, - LineString: function(lineString) { - this.line(lineString.coordinates); - }, - MultiLineString: function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - }, - MultiPoint: function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - MultiPolygon: function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) this.polygon(coordinates[i]); - }, - Point: function(point) { - this.point(point.coordinates); - }, - Polygon: function(polygon) { - this.polygon(polygon.coordinates); - }, - Sphere: d3_noop, - object: function(object) { - return d3_geo_typeObjects.hasOwnProperty(object.type) ? this[object.type](object) : this.geometry(object); - }, - geometry: function(geometry) { - return d3_geo_typeGeometries.hasOwnProperty(geometry.type) ? this[geometry.type](geometry) : null; - }, - point: d3_noop, - line: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.point(coordinates[i]); - }, - polygon: function(coordinates) { - var i = -1, n = coordinates.length; - while (++i < n) this.line(coordinates[i]); - } - }; - var d3_geo_typeGeometries = { - LineString: 1, - MultiLineString: 1, - MultiPoint: 1, - MultiPolygon: 1, - Point: 1, - Polygon: 1, - Sphere: 1 - }; - var d3_geo_typeObjects = { - Feature: 1, - FeatureCollection: 1, - GeometryCollection: 1 - }; d3.geo.albersUsa = function() { var lower48 = d3.geo.albers(); var alaska = d3.geo.albers().rotate([ 160, 0 ]).center([ 0, 60 ]).parallels([ 55, 65 ]); @@ -5417,15 +5491,6 @@ var lon = point[0], lat = point[1]; return lat > 50 ? alaska : lon < -140 ? hawaii : lat < 21 ? puertoRico : lower48; } - albersUsa.point = function(coordinates, context) { - return projection(coordinates).point(coordinates, context); - }; - albersUsa.line = function(coordinates, context) { - return projection(coordinates[0]).line(coordinates, context); - }; - albersUsa.polygon = function(coordinates, context) { - return projection(coordinates[0][0]).polygon(coordinates, context); - }; albersUsa.scale = function(x) { if (!arguments.length) return lower48.scale(); lower48.scale(x); @@ -5481,38 +5546,123 @@ return d3_geo_projection(d3_geo_azimuthalEquidistant); }).raw = d3_geo_azimuthalEquidistant; d3.geo.bounds = d3_geo_bounds(d3_identity); - function d3_geo_bounds(projection) { - var x0, y0, x1, y1, bounds = d3_geo_type({ - point: function(point) { - point = projection(point); - var x = point[0], y = point[1]; - if (x < x0) x0 = x; - if (x > x1) x1 = x; - if (y < y0) y0 = y; - if (y > y1) y1 = y; + function d3_geo_bounds(projectStream) { + var x0, y0, x1, y1; + var bound = { + point: boundPoint, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + bound.lineEnd = boundPolygonLineEnd; }, - polygon: function(coordinates) { - this.line(coordinates[0]); + polygonEnd: function() { + bound.point = boundPoint; } - }); + }; + function boundPoint(x, y) { + if (x < x0) x0 = x; + if (x > x1) x1 = x; + if (y < y0) y0 = y; + if (y > y1) y1 = y; + } + function boundPolygonLineEnd() { + bound.point = bound.lineEnd = d3_noop; + } return function(feature) { y1 = x1 = -(x0 = y0 = Infinity); - bounds.object(feature); + d3.geo.stream(feature, projectStream(bound)); return [ [ x0, y0 ], [ x1, y1 ] ]; }; } + d3.geo.centroid = function(object) { + d3_geo_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, d3_geo_centroid); + var m; + if (d3_geo_centroidW && Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { + return [ Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees ]; + } + }; + var d3_geo_centroidDimension, d3_geo_centroidW, d3_geo_centroidX, d3_geo_centroidY, d3_geo_centroidZ; + var d3_geo_centroid = { + sphere: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + }, + point: d3_geo_centroidPoint, + lineStart: d3_geo_centroidLineStart, + lineEnd: d3_geo_centroidLineEnd, + polygonStart: function() { + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.lineStart = d3_geo_centroidRingStart; + }, + polygonEnd: function() { + d3_geo_centroid.lineStart = d3_geo_centroidLineStart; + } + }; + function d3_geo_centroidPoint(λ, φ) { + if (d3_geo_centroidDimension) return; + ++d3_geo_centroidW; + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; + d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; + d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + } + function d3_geo_centroidRingStart() { + var λ00, φ00; + d3_geo_centroidDimension = 1; + d3_geo_centroidLineStart(); + d3_geo_centroidDimension = 2; + var linePoint = d3_geo_centroid.point; + d3_geo_centroid.point = function(λ, φ) { + linePoint(λ00 = λ, φ00 = φ); + }; + d3_geo_centroid.lineEnd = function() { + d3_geo_centroid.point(λ00, φ00); + d3_geo_centroidLineEnd(); + d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; + }; + } + function d3_geo_centroidLineStart() { + var x0, y0, z0; + if (d3_geo_centroidDimension > 1) return; + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_centroid.point = function(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians); + x0 = cosφ * Math.cos(λ); + y0 = cosφ * Math.sin(λ); + z0 = Math.sin(φ); + d3_geo_centroid.point = nextPoint; + }; + function nextPoint(λ, φ) { + λ *= d3_radians; + var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z); + d3_geo_centroidW += w; + d3_geo_centroidX += w * (x0 + (x0 = x)); + d3_geo_centroidY += w * (y0 + (y0 = y)); + d3_geo_centroidZ += w * (z0 + (z0 = z)); + } + } + function d3_geo_centroidLineEnd() { + d3_geo_centroid.point = d3_geo_centroidPoint; + } d3.geo.circle = function() { - var origin = [ 0, 0 ], angle, precision = 6, rotate, interpolate; + var origin = [ 0, 0 ], angle, precision = 6, interpolate; function circle() { - var o = typeof origin === "function" ? origin.apply(this, arguments) : origin; - rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0); - var ring = []; + var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = []; interpolate(null, null, 1, { - lineTo: function(λ, φ) { - var point = rotate.invert(λ, φ); - point[0] *= d3_degrees; - point[1] *= d3_degrees; - ring.push(point); + point: function(x, y) { + ring.push(x = rotate(x, y)); + x[0] *= d3_degrees, x[1] *= d3_degrees; } }); return { @@ -5537,84 +5687,9 @@ }; return circle.angle(90); }; - function d3_geo_circleClip(degrees, rotate) { - var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); - return { - point: function(coordinates, context) { - if (visible(coordinates = rotate(coordinates))) { - context.point(coordinates[0], coordinates[1]); - } - }, - line: function(coordinates, context) { - clipLine(coordinates, context); - }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate); - }, - sphere: function(context) { - d3_geo_projectionSphere(context, interpolate); - } - }; - function visible(point) { - return Math.cos(point[1]) * Math.cos(point[0]) > cr; - } - function clipLine(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point0 = rotate(coordinates[0]), point1, point2, v0 = visible(point0), v00 = ring && v0, v, n, clean = ring, area = 0, p, x0, x, y0, y; - if (clean) { - x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0]; - y0 = p[1]; - } - if (v0) context.moveTo(point0[0], point0[1]); - for (var i = 1; i < n; i++) { - point1 = rotate(coordinates[i]); - v = visible(point1); - if (v !== v0) { - point2 = intersect(point0, point1); - if (d3_geo_circlePointsEqual(point0, point2) || d3_geo_circlePointsEqual(point1, point2)) { - point1[0] += ε; - point1[1] += ε; - v = visible(point1); - } - } - if (v !== v0) { - clean = false; - if (v0 = v) { - point2 = intersect(point1, point0); - context.moveTo(point2[0], point2[1]); - } else { - point2 = intersect(point0, point1); - context.lineTo(point2[0], point2[1]); - } - point0 = point2; - } - if (clean) { - p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]); - x = p[0]; - y = p[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - if (v && !d3_geo_circlePointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]); - point0 = point1; - } - return [ clean && area * .5, v00 && v ]; - } - function intersect(a, b) { - var pa = d3_geo_circleCartesian(a, [ 0, 0, 0 ]), pb = d3_geo_circleCartesian(b, [ 0, 0, 0 ]); - var n1 = [ 1, 0, 0 ], n2 = d3_geo_circleCross(pa, pb), n2n2 = d3_geo_circleDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; - if (!determinant) return a; - var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_circleCross(n1, n2), A = d3_geo_circleScale(n1, c1), B = d3_geo_circleScale(n2, c2); - d3_geo_circleAdd(A, B); - var u = n1xn2, w = d3_geo_circleDot(A, u), uu = d3_geo_circleDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_circleDot(A, A) - 1)), q = d3_geo_circleScale(u, (-w - t) / uu); - d3_geo_circleAdd(q, A); - return d3_geo_circleSpherical(q); - } - } function d3_geo_circleInterpolate(radians, precision) { var cr = Math.cos(radians), sr = Math.sin(radians); - return function(from, to, direction, context) { + return function(from, to, direction, listener) { if (from != null) { from = d3_geo_circleAngle(cr, from); to = d3_geo_circleAngle(cr, to); @@ -5623,38 +5698,110 @@ from = radians + direction * 2 * π; to = radians; } + var point; for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { - var c = Math.cos(t), s = Math.sin(t), point = d3_geo_circleSpherical([ cr, -sr * c, -sr * s ]); - context.lineTo(point[0], point[1]); + listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]); } }; } - function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) { - var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), draw = [], visibleArea = 0, invisibleArea = 0, invisible = false; - coordinates.forEach(function(ring) { - var x = buffer(ring, context), ringSegments = x[1], segment, n = ringSegments.length; - if (!n) { - invisible = true; - invisibleArea += x[0][0]; - return; + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_cartesian(point); + a[0] -= cr; + d3_geo_cartesianNormalize(a); + var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); + } + function d3_geo_clip(pointVisible, clipLine, interpolate) { + return function(listener) { + var line = clipLine(listener); + var clip = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { + clip.point = pointRing; + clip.lineStart = ringStart; + clip.lineEnd = ringEnd; + invisible = false; + invisibleArea = visibleArea = 0; + segments = []; + listener.polygonStart(); + }, + polygonEnd: function() { + clip.point = point; + clip.lineStart = lineStart; + clip.lineEnd = lineEnd; + segments = d3.merge(segments); + if (segments.length) { + d3_geo_clipPolygon(segments, interpolate, listener); + } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + listener.polygonEnd(); + segments = null; + }, + sphere: function() { + listener.polygonStart(); + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + listener.polygonEnd(); + } + }; + function point(λ, φ) { + if (pointVisible(λ, φ)) listener.point(λ, φ); } - if (x[0][0] !== false) { - visibleArea += x[0][0]; - draw.push(segment = ringSegments[0]); - var point = segment[0], n = segment.length - 1, i = 0; - context.moveTo(point[0], point[1]); - while (++i < n) context.lineTo((point = segment[i])[0], point[1]); - context.closePath(); - return; + function pointLine(λ, φ) { + line.point(λ, φ); } - if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); - segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1)); - }); - if (!segments.length) { - if (visibleArea < 0 || invisible && invisibleArea < 0) { - d3_geo_projectionSphere(context, interpolate); + function lineStart() { + clip.point = pointLine; + line.lineStart(); } - } + function lineEnd() { + clip.point = point; + line.lineEnd(); + } + var segments, visibleArea, invisibleArea, invisible; + var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), ring; + function pointRing(λ, φ) { + ringListener.point(λ, φ); + ring.push([ λ, φ ]); + } + function ringStart() { + ringListener.lineStart(); + ring = []; + } + function ringEnd() { + pointRing(ring[0][0], ring[0][1]); + ringListener.lineEnd(); + var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += d3_geo_clipAreaRing(ring, -1); + ring = null; + return; + } + ring = null; + if (clean & 1) { + segment = ringSegments[0]; + visibleArea += d3_geo_clipAreaRing(segment, 1); + var n = segment.length - 1, i = -1, point; + listener.lineStart(); + while (++i < n) listener.point((point = segment[i])[0], point[1]); + listener.lineEnd(); + return; + } + if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments.push(ringSegments.filter(d3_geo_clipSegmentLength1)); + } + return clip; + }; + } + function d3_geo_clipPolygon(segments, interpolate, listener) { + var subject = [], clip = []; segments.forEach(function(segment) { var n = segment.length; if (n <= 1) return; @@ -5696,112 +5843,224 @@ subject.push(a); clip.push(b); }); - clip.sort(d3_geo_circleClipSort); - d3_geo_circleLinkCircular(subject); - d3_geo_circleLinkCircular(clip); + clip.sort(d3_geo_clipSort); + d3_geo_clipLinkCircular(subject); + d3_geo_clipLinkCircular(clip); if (!subject.length) return; var start = subject[0], current, points, point; while (1) { current = start; while (current.visited) if ((current = current.next) === start) return; points = current.points; - context.moveTo((point = points.shift())[0], point[1]); + listener.lineStart(); do { current.visited = current.other.visited = true; if (current.entry) { if (current.subject) { - for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]); + for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.next.point, 1, context); + interpolate(current.point, current.next.point, 1, listener); } current = current.next; } else { if (current.subject) { points = current.prev.points; - for (var i = points.length; --i >= 0; ) context.lineTo((point = points[i])[0], point[1]); + for (var i = points.length; --i >= 0; ) listener.point((point = points[i])[0], point[1]); } else { - interpolate(current.point, current.prev.point, -1, context); + interpolate(current.point, current.prev.point, -1, listener); } current = current.prev; } current = current.other; points = current.points; } while (!current.visited); - context.closePath(); + listener.lineEnd(); } } - function d3_geo_circleLinkCircular(array) { - for (var i = 0, a = array[0], b, n = array.length; i < n; ) { - a.next = b = array[++i % n]; + function d3_geo_clipLinkCircular(array) { + if (!(n = array.length)) return; + var n, i = 0, a = array[0], b; + while (++i < n) { + a.next = b = array[i]; b.prev = a; a = b; } + a.next = b = array[0]; + b.prev = a; } - function d3_geo_circleClipSort(a, b) { + function d3_geo_clipSort(a, b) { return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); } - function d3_geo_circleAngle(cr, point) { - var a = d3_geo_circleCartesian(point, [ cr, 0, 0 ]); - d3_geo_circleNormalize(a); - var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); - return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); - } - function d3_geo_circleCartesian(point, origin) { - var p0 = point[0], p1 = point[1], c1 = Math.cos(p1); - return [ c1 * Math.cos(p0) - origin[0], c1 * Math.sin(p0) - origin[1], Math.sin(p1) - origin[2] ]; - } - function d3_geo_circleSpherical(point) { - return [ Math.atan2(point[1], point[0]), Math.asin(Math.max(-1, Math.min(1, point[2]))) ]; - } - function d3_geo_circleDot(a, b) { - return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; - } - function d3_geo_circleCross(a, b) { - return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ]; - } - function d3_geo_circleAdd(a, b) { - a[0] += b[0]; - a[1] += b[1]; - a[2] += b[2]; - } - function d3_geo_circleScale(vector, s) { - return [ vector[0] * s, vector[1] * s, vector[2] * s ]; - } - function d3_geo_circleNormalize(d) { - var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); - d[0] /= l; - d[1] /= l; - d[2] /= l; - } - function d3_geo_circleBufferSegments(f) { - return function(coordinates) { - var segments = [], segment; - return [ f(coordinates, { - moveTo: function(x, y) { - segments.push(segment = [ [ x, y ] ]); - }, - lineTo: function(x, y) { - segment.push([ x, y ]); - } - }, true), segments ]; - }; - } - function d3_geo_circlePointsEqual(a, b) { - return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; - } - function d3_geo_circleSegmentLength1(segment) { + function d3_geo_clipSegmentLength1(segment) { return segment.length > 1; } + function d3_geo_clipBufferListener() { + var lines = [], line; + return { + lineStart: function() { + lines.push(line = []); + }, + point: function(λ, φ) { + line.push([ λ, φ ]); + }, + lineEnd: d3_noop, + buffer: function() { + var buffer = lines; + lines = []; + line = null; + return buffer; + } + }; + } + function d3_geo_clipAreaRing(ring, invisible) { + if (!(n = ring.length)) return 0; + var n, i = 0, area = 0, p = ring[0], λ = p[0], φ = p[1], cosφ = Math.cos(φ), x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), y0 = 1 - invisible * Math.cos(λ) * cosφ, x1 = x0, x, y; + while (++i < n) { + p = ring[i]; + cosφ = Math.cos(φ = p[1]); + x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); + y = 1 - invisible * Math.cos(λ) * cosφ; + if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; + if (Math.abs(y) < ε || Math.abs(y0) < ε) {} else if (Math.abs(Math.abs(x - x0) - π) < ε) { + if (y + y0 > 2) area += 4 * (x - x0); + } else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); + x1 = x0, x0 = x, y0 = y; + } + return area; + } + var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); + function d3_geo_clipAntimeridianLine(listener) { + var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean; + return { + lineStart: function() { + listener.lineStart(); + clean = 1; + }, + point: function(λ1, φ1) { + var sλ1 = λ1 > 0 ? π : -π, dλ = Math.abs(λ1 - λ0); + if (Math.abs(dλ - π) < ε) { + listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + listener.point(λ1, φ0); + clean = 0; + } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1); + listener.point(sλ0, φ0); + listener.lineEnd(); + listener.lineStart(); + listener.point(sλ1, φ0); + clean = 0; + } + listener.point(λ0 = λ1, φ0 = φ1); + sλ0 = sλ1; + }, + lineEnd: function() { + listener.lineEnd(); + λ0 = φ0 = NaN; + }, + clean: function() { + return 2 - clean; + } + }; + } + function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { + var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); + return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; + } + function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { + var φ; + if (from == null) { + φ = direction * π / 2; + listener.point(-π, φ); + listener.point(0, φ); + listener.point(π, φ); + listener.point(π, 0); + listener.point(π, -φ); + listener.point(0, -φ); + listener.point(-π, -φ); + listener.point(-π, 0); + listener.point(-π, φ); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; + listener.point(-s, φ); + listener.point(0, φ); + listener.point(s, φ); + } else { + listener.point(to[0], to[1]); + } + } + function d3_geo_clipCircle(degrees) { + var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); + return d3_geo_clip(visible, clipLine, interpolate); + function visible(λ, φ) { + return Math.cos(λ) * Math.cos(φ) > cr; + } + function clipLine(listener) { + var point0, v0, v00, clean; + return { + lineStart: function() { + v00 = v0 = false; + clean = 1; + }, + point: function(λ, φ) { + var point1 = [ λ, φ ], point2, v = visible(λ, φ); + if (!point0 && (v00 = v0 = v)) listener.lineStart(); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1[0], point1[1]); + } + } + if (v !== v0) { + clean = 0; + if (v0 = v) { + listener.lineStart(); + point2 = intersect(point1, point0); + listener.point(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + listener.point(point2[0], point2[1]); + listener.lineEnd(); + } + point0 = point2; + } + if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) listener.point(point1[0], point1[1]); + point0 = point1; + }, + lineEnd: function() { + if (v0) listener.lineEnd(); + point0 = null; + }, + clean: function() { + return clean | (v00 && v0) << 1; + } + }; + } + function intersect(a, b) { + var pa = d3_geo_cartesian(a, 0), pb = d3_geo_cartesian(b, 0); + var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2; + if (!determinant) return a; + var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2); + d3_geo_cartesianAdd(A, B); + var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t = Math.sqrt(w * w - uu * (d3_geo_cartesianDot(A, A) - 1)), q = d3_geo_cartesianScale(u, (-w - t) / uu); + d3_geo_cartesianAdd(q, A); + return d3_geo_spherical(q); + } + } function d3_geo_compose(a, b) { - if (a === d3_geo_equirectangular) return b; - if (b === d3_geo_equirectangular) return a; - function compose(λ, φ) { - var coordinates = a(λ, φ); - return b(coordinates[0], coordinates[1]); + function compose(x, y) { + return x = a(x, y), b(x[0], x[1]); } if (a.invert && b.invert) compose.invert = function(x, y) { - var coordinates = b.invert(x, y); - return a.invert(coordinates[0], coordinates[1]); + return x = b.invert(x, y), x && a.invert(x[0], x[1]); }; return compose; } @@ -5821,12 +6080,15 @@ var x1, x0, y1, y0, dx = 22.5, dy = dx, x, y, precision = 2.5; function graticule() { return { - type: "GeometryCollection", - geometries: graticule.lines() + type: "MultiLineString", + coordinates: lines() }; } + function lines() { + return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)); + } graticule.lines = function() { - return d3.range(Math.ceil(x0 / dx) * dx, x1, dx).map(x).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).map(y)).map(function(coordinates) { + return lines().map(function(coordinates) { return { type: "LineString", coordinates: coordinates @@ -5877,11 +6139,23 @@ }); }; } + d3.geo.interpolate = function(source, target) { + return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians); + }; + function d3_geo_interpolate(x0, y0, x1, y1) { + var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0)))), k = 1 / Math.sin(d); + function interpolate(t) { + var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; + return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; + } + interpolate.distance = d; + return interpolate; + } d3.geo.greatArc = function() { - var source = d3_source, p0, target = d3_target, p1, precision = 6 * d3_radians, interpolate = d3_geo_greatArcInterpolator(); + var source = d3_source, source_, target = d3_target, target_, precision = 6 * d3_radians, interpolate; function greatArc() { - var d = greatArc.distance.apply(this, arguments), t = 0, dt = precision / d, coordinates = [ p0 ]; - while ((t += dt) < 1) coordinates.push(interpolate(t)); + var p0 = source_ || source.apply(this, arguments), p1 = target_ || target.apply(this, arguments), i = interpolate || d3.geo.interpolate(p0, p1), t = 0, dt = precision / i.distance, coordinates = [ p0 ]; + while ((t += dt) < 1) coordinates.push(i(t)); coordinates.push(p1); return { type: "LineString", @@ -5889,20 +6163,18 @@ }; } greatArc.distance = function() { - if (typeof source === "function") interpolate.source(p0 = source.apply(this, arguments)); - if (typeof target === "function") interpolate.target(p1 = target.apply(this, arguments)); - return interpolate.distance(); + return (interpolate || d3.geo.interpolate(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments))).distance; }; greatArc.source = function(_) { if (!arguments.length) return source; - source = _; - if (typeof source !== "function") interpolate.source(p0 = source); + source = _, source_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.target = function(_) { if (!arguments.length) return target; - target = _; - if (typeof target !== "function") interpolate.target(p1 = target); + target = _, target_ = typeof _ === "function" ? null : _; + interpolate = source_ && target_ ? d3.geo.interpolate(source_, target_) : null; return greatArc; }; greatArc.precision = function(_) { @@ -5912,36 +6184,6 @@ }; return greatArc; }; - function d3_geo_greatArcInterpolator() { - var x0, y0, cy0, sy0, kx0, ky0, x1, y1, cy1, sy1, kx1, ky1, d, k; - function interpolate(t) { - var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1; - return [ Math.atan2(y, x) / d3_radians, Math.atan2(z, Math.sqrt(x * x + y * y)) / d3_radians ]; - } - interpolate.distance = function() { - if (d == null) k = 1 / Math.sin(d = Math.acos(Math.max(-1, Math.min(1, sy0 * sy1 + cy0 * cy1 * Math.cos(x1 - x0))))); - return d; - }; - interpolate.source = function(_) { - var cx0 = Math.cos(x0 = _[0] * d3_radians), sx0 = Math.sin(x0); - cy0 = Math.cos(y0 = _[1] * d3_radians); - sy0 = Math.sin(y0); - kx0 = cy0 * cx0; - ky0 = cy0 * sx0; - d = null; - return interpolate; - }; - interpolate.target = function(_) { - var cx1 = Math.cos(x1 = _[0] * d3_radians), sx1 = Math.sin(x1); - cy1 = Math.cos(y1 = _[1] * d3_radians); - sy1 = Math.sin(y1); - kx1 = cy1 * cx1; - ky1 = cy1 * sx1; - d = null; - return interpolate; - }; - return interpolate; - } function d3_geo_mercator(λ, φ) { return [ λ / (2 * π), Math.max(-.5, Math.min(+.5, Math.log(Math.tan(π / 4 + φ / 2)) / (2 * π))) ]; } @@ -5958,203 +6200,290 @@ return d3_geo_projection(d3_geo_orthographic); }).raw = d3_geo_orthographic; d3.geo.path = function() { - var pointRadius = 4.5, pointCircle = d3_geo_pathCircle(pointRadius), projection = d3.geo.albersUsa(), bounds, buffer = []; - var bufferContext = { - point: function(x, y) { - buffer.push("M", x, ",", y, pointCircle); - }, - moveTo: function(x, y) { - buffer.push("M", x, ",", y); - }, - lineTo: function(x, y) { - buffer.push("L", x, ",", y); - }, - closePath: function() { - buffer.push("Z"); - } - }; - var area, centroidWeight, x00, y00, x0, y0, cx, cy; - var areaContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var lineCentroidContext = { - point: function(x, y) { - cx += x; - cy += y; - ++centroidWeight; - }, - moveTo: moveTo, - lineTo: function(x, y) { - var dx = x - x0, dy = y - y0, δ = Math.sqrt(dx * dx + dy * dy); - centroidWeight += δ; - cx += δ * (x0 + x) / 2; - cy += δ * (y0 + y) / 2; - x0 = x; - y0 = y; - }, - closePath: closePath - }; - var polygonCentroidContext = { - point: d3_noop, - moveTo: moveTo, - lineTo: function(x, y) { - var δ = y0 * x - x0 * y; - centroidWeight += δ * 3; - cx += δ * (x0 + x); - cy += δ * (y0 + y); - x0 = x; - y0 = y; - }, - closePath: closePath - }; - function moveTo(x, y) { - x00 = x0 = x; - y00 = y0 = y; - } - function closePath() { - this.lineTo(x00, y00); - } - var context = bufferContext; + var pointRadius = 4.5, projection, context, projectStream, contextStream; function path(object) { - var result = null; - if (object != result) { - if (typeof pointRadius === "function") pointCircle = d3_geo_pathCircle(pointRadius.apply(this, arguments)); - pathType.object(object); - if (buffer.length) result = buffer.join(""), buffer = []; - } - return result; - } - var pathType = d3_geo_type({ - line: function(coordinates) { - projection.line(coordinates, context); - }, - polygon: function(coordinates) { - projection.polygon(coordinates, context); - }, - point: function(coordinates) { - projection.point(coordinates, context); - }, - Sphere: function() { - projection.sphere(context); - } - }); - var areaType = d3_geo_type({ - Feature: function(feature) { - return areaType.geometry(feature.geometry); - }, - FeatureCollection: function(collection) { - return d3.sum(collection.features, areaType.Feature); - }, - GeometryCollection: function(collection) { - return d3.sum(collection.geometries, areaType.geometry); - }, - LineString: d3_zero, - MultiLineString: d3_zero, - MultiPoint: d3_zero, - MultiPolygon: function(multiPolygon) { - return d3.sum(multiPolygon.coordinates, polygonArea); - }, - Point: d3_zero, - Polygon: function(polygon) { - return polygonArea(polygon.coordinates); - }, - Sphere: sphereArea - }); - function polygonArea(coordinates) { - area = 0; - projection.polygon(coordinates, areaContext); - return Math.abs(area) / 2; - } - function sphereArea() { - area = 0; - projection.sphere(areaContext); - return Math.abs(area) / 2; + if (object) d3.geo.stream(object, projectStream(contextStream.pointRadius(typeof pointRadius === "function" ? +pointRadius.apply(this, arguments) : pointRadius))); + return contextStream.result(); } path.area = function(object) { - return areaType.object(object); - }; - var centroidType = d3_geo_type({ - Feature: function(feature) { - return centroidType.geometry(feature.geometry); - }, - LineString: weightedCentroid(function(lineString) { - projection.line(lineString.coordinates, lineCentroidContext); - }), - MultiLineString: weightedCentroid(function(multiLineString) { - var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.line(coordinates[i], lineCentroidContext); - }), - MultiPoint: weightedCentroid(function(multiPoint) { - var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.point(coordinates[i], lineCentroidContext); - }), - MultiPolygon: weightedCentroid(function(multiPolygon) { - var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; - while (++i < n) projection.polygon(coordinates[i], polygonCentroidContext); - }), - Point: weightedCentroid(function(point) { - projection.point(point.coordinates, lineCentroidContext); - }), - Polygon: weightedCentroid(function(polygon) { - projection.polygon(polygon.coordinates, polygonCentroidContext); - }), - Sphere: weightedCentroid(function() { - projection.sphere(polygonCentroidContext); - }) - }); - function weightedCentroid(f) { - return function() { - centroidWeight = cx = cy = 0; - f.apply(this, arguments); - return centroidWeight ? [ cx / centroidWeight, cy / centroidWeight ] : null; - }; - } - path.bounds = function(object) { - return (bounds || (bounds = d3_geo_bounds(projection)))(object); + d3_geo_pathAreaSum = 0; + d3.geo.stream(object, projectStream(d3_geo_pathArea)); + return d3_geo_pathAreaSum; }; path.centroid = function(object) { - return centroidType.object(object); + d3_geo_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + d3.geo.stream(object, projectStream(d3_geo_pathCentroid)); + return d3_geo_centroidZ ? [ d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ ] : undefined; + }; + path.bounds = function(object) { + return d3_geo_bounds(projectStream)(object); }; path.projection = function(_) { if (!arguments.length) return projection; - projection = _; - bounds = null; + projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity; return path; }; path.context = function(_) { - if (!arguments.length) return context === bufferContext ? null : context; - context = _; - if (context == null) context = bufferContext; + if (!arguments.length) return context; + contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_); return path; }; - path.pointRadius = function(x) { + path.pointRadius = function(_) { if (!arguments.length) return pointRadius; - if (typeof x === "function") pointRadius = x; else pointCircle = d3_geo_pathCircle(pointRadius = +x); + pointRadius = typeof _ === "function" ? _ : +_; return path; }; - return path; + return path.projection(d3.geo.albersUsa()).context(null); }; function d3_geo_pathCircle(radius) { return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + +2 * radius + "z"; } - var d3_geo_pathIdentity = d3.geo.path().projection({ - polygon: function(polygon, context) { - polygon.forEach(function(ring) { - var n = ring.length, i = 0, point; - context.moveTo((point = ring[0])[0], point[1]); - while (++i < n) context.lineTo((point = ring[i])[0], point[1]); - context.closePath(); - }); + function d3_geo_pathProjectStream(project) { + var resample = d3_geo_resample(function(λ, φ) { + return project([ λ * d3_degrees, φ * d3_degrees ]); + }); + return function(stream) { + stream = resample(stream); + return { + point: function(λ, φ) { + stream.point(λ * d3_radians, φ * d3_radians); + }, + sphere: function() { + stream.sphere(); + }, + lineStart: function() { + stream.lineStart(); + }, + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); + } + }; + }; + } + function d3_geo_pathBuffer() { + var pointCircle = d3_geo_pathCircle(4.5), buffer = []; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointCircle = d3_geo_pathCircle(_); + return stream; + }, + result: function() { + if (buffer.length) { + var result = buffer.join(""); + buffer = []; + return result; + } + } + }; + function point(x, y) { + buffer.push("M", x, ",", y, pointCircle); } - }); - d3.geo.centroid = d3_geo_pathIdentity.centroid; + function pointLineStart(x, y) { + buffer.push("M", x, ",", y); + stream.point = pointLine; + } + function pointLine(x, y) { + buffer.push("L", x, ",", y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + buffer.push("Z"); + } + return stream; + } + function d3_geo_pathContext(context) { + var pointRadius = 4.5; + var stream = { + point: point, + lineStart: function() { + stream.point = pointLineStart; + }, + lineEnd: lineEnd, + polygonStart: function() { + stream.lineEnd = lineEndPolygon; + }, + polygonEnd: function() { + stream.lineEnd = lineEnd; + stream.point = point; + }, + pointRadius: function(_) { + pointRadius = _; + return stream; + }, + result: d3_noop + }; + function point(x, y) { + context.moveTo(x, y); + context.arc(x, y, pointRadius, 0, 2 * π); + } + function pointLineStart(x, y) { + context.moveTo(x, y); + stream.point = pointLine; + } + function pointLine(x, y) { + context.lineTo(x, y); + } + function lineEnd() { + stream.point = point; + } + function lineEndPolygon() { + context.closePath(); + } + return stream; + } + var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_pathAreaPolygon = 0; + d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart; + }, + polygonEnd: function() { + d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; + d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + } + }; + function d3_geo_pathAreaRingStart() { + var x00, y00, x0, y0; + d3_geo_pathArea.point = function(x, y) { + d3_geo_pathArea.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + d3_geo_pathAreaPolygon += y0 * x - x0 * y; + x0 = x, y0 = y; + } + d3_geo_pathArea.lineEnd = function() { + nextPoint(x00, y00); + }; + } + var d3_geo_pathCentroid = { + point: d3_geo_pathCentroidPoint, + lineStart: d3_geo_pathCentroidLineStart, + lineEnd: d3_geo_pathCentroidLineEnd, + polygonStart: function() { + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart; + }, + polygonEnd: function() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart; + d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd; + } + }; + function d3_geo_pathCentroidPoint(x, y) { + if (d3_geo_centroidDimension) return; + d3_geo_centroidX += x; + d3_geo_centroidY += y; + ++d3_geo_centroidZ; + } + function d3_geo_pathCentroidLineStart() { + var x0, y0; + if (d3_geo_centroidDimension !== 1) { + if (d3_geo_centroidDimension < 1) { + d3_geo_centroidDimension = 1; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } else return; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x0 = x, y0 = y; + }; + function nextPoint(x, y) { + var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy); + d3_geo_centroidX += z * (x0 + x) / 2; + d3_geo_centroidY += z * (y0 + y) / 2; + d3_geo_centroidZ += z; + x0 = x, y0 = y; + } + } + function d3_geo_pathCentroidLineEnd() { + d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint; + } + function d3_geo_pathCentroidRingStart() { + var x00, y00, x0, y0; + if (d3_geo_centroidDimension < 2) { + d3_geo_centroidDimension = 2; + d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + } + d3_geo_pathCentroid.point = function(x, y) { + d3_geo_pathCentroid.point = nextPoint; + x00 = x0 = x, y00 = y0 = y; + }; + function nextPoint(x, y) { + var z = y0 * x - x0 * y; + d3_geo_centroidX += z * (x0 + x); + d3_geo_centroidY += z * (y0 + y); + d3_geo_centroidZ += z * 3; + x0 = x, y0 = y; + } + d3_geo_pathCentroid.lineEnd = function() { + nextPoint(x00, y00); + }; + } + d3.geo.area = function(object) { + d3_geo_areaSum = 0; + d3.geo.stream(object, d3_geo_area); + return d3_geo_areaSum; + }; + var d3_geo_areaSum, d3_geo_areaRing; + var d3_geo_area = { + sphere: function() { + d3_geo_areaSum += 4 * π; + }, + point: d3_noop, + lineStart: d3_noop, + lineEnd: d3_noop, + polygonStart: function() { + d3_geo_areaRing = 0; + d3_geo_area.lineStart = d3_geo_areaRingStart; + }, + polygonEnd: function() { + d3_geo_areaSum += d3_geo_areaRing < 0 ? 4 * π + d3_geo_areaRing : d3_geo_areaRing; + d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; + } + }; + function d3_geo_areaRingStart() { + var λ00, φ00, λ1, λ0, φ0, cosφ0, sinφ0; + d3_geo_area.point = function(λ, φ) { + d3_geo_area.point = nextPoint; + λ1 = λ0 = (λ00 = λ) * d3_radians, φ0 = (φ00 = φ) * d3_radians, cosφ0 = Math.cos(φ0), + sinφ0 = Math.sin(φ0); + }; + function nextPoint(λ, φ) { + λ *= d3_radians, φ *= d3_radians; + if (Math.abs(Math.abs(φ0) - π / 2) < ε && Math.abs(Math.abs(φ) - π / 2) < ε) return; + var cosφ = Math.cos(φ), sinφ = Math.sin(φ); + if (Math.abs(φ0 - π / 2) < ε) d3_geo_areaRing += (λ - λ1) * 2; else { + var dλ = λ - λ0, cosdλ = Math.cos(dλ), d = Math.atan2(Math.sqrt((d = cosφ * Math.sin(dλ)) * d + (d = cosφ0 * sinφ - sinφ0 * cosφ * cosdλ) * d), sinφ0 * sinφ + cosφ0 * cosφ * cosdλ), s = (d + π + φ0 + φ) / 4; + d3_geo_areaRing += (dλ < 0 && dλ > -π || dλ > π ? -4 : 4) * Math.atan(Math.sqrt(Math.abs(Math.tan(s) * Math.tan(s - d / 2) * Math.tan(s - π / 4 - φ0 / 2) * Math.tan(s - π / 4 - φ / 2)))); + } + λ1 = λ0, λ0 = λ, φ0 = φ, cosφ0 = cosφ, sinφ0 = sinφ; + } + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; + } d3.geo.projection = d3_geo_projection; d3.geo.projectionMutator = d3_geo_projectionMutator; function d3_geo_projection(project) { @@ -6163,86 +6492,26 @@ })(); } function d3_geo_projectionMutator(projectAt) { - var project, rotate, projectRotate, k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx = x, δy = y, δ2 = .5, clip = d3_geo_projectionCutAntemeridian(rotatePoint), clipAngle = null, context; - function projection(coordinates) { - coordinates = projectRotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - return [ coordinates[0] * k + δx, δy - coordinates[1] * k ]; + var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) { + x = project(x, y); + return [ x[0] * k + δx, δy - x[1] * k ]; + }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, clip = d3_geo_clipAntimeridian, clipAngle = null; + function projection(point) { + point = projectRotate(point[0] * d3_radians, point[1] * d3_radians); + return [ point[0] * k + δx, δy - point[1] * k ]; } - function invert(coordinates) { - coordinates = projectRotate.invert((coordinates[0] - δx) / k, (δy - coordinates[1]) / k); - return [ coordinates[0] * d3_degrees, coordinates[1] * d3_degrees ]; + function invert(point) { + point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k); + return point && [ point[0] * d3_degrees, point[1] * d3_degrees ]; } - projection.point = function(coordinates, c) { - context = c; - clip.point(coordinates, resample); - context = null; - }; - projection.line = function(coordinates, c) { - context = c; - clip.line(coordinates, resample); - context = null; - }; - projection.polygon = function(coordinates, c) { - context = c; - clip.polygon(coordinates, resample); - context = null; - }; - projection.sphere = function(c) { - context = c; - clip.sphere(resample); - context = null; + projection.stream = function(stream) { + return d3_geo_projectionRadiansRotate(rotate, clip(projectResample(stream))); }; projection.clipAngle = function(_) { if (!arguments.length) return clipAngle; - clip = _ == null ? (clipAngle = _, d3_geo_projectionCutAntemeridian(rotatePoint)) : d3_geo_circleClip(clipAngle = +_, rotatePoint); + clip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle(clipAngle = +_); return projection; }; - var λ00, φ00, λ0, sinφ0, cosφ0, x0, y0, maxDepth = 16; - function point(λ, φ) { - var p = projectPoint(λ, φ); - context.point(p[0], p[1]); - } - function moveTo(λ, φ) { - var p = projectPoint(λ00 = λ0 = λ, φ00 = φ); - sinφ0 = Math.sin(φ); - cosφ0 = Math.cos(φ); - context.moveTo(x0 = p[0], y0 = p[1]); - } - function lineTo(λ, φ) { - var p = projectPoint(λ, φ); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x0 = p[0], y0 = p[1], λ0 = λ, sinφ0 = Math.sin(φ), cosφ0 = Math.cos(φ), maxDepth); - context.lineTo(x0, y0); - } - function resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x1, y1, λ1, sinφ1, cosφ1, depth) { - var dx = x1 - x0, dy = y1 - y0, distance2 = dx * dx + dy * dy; - if (distance2 > 4 * δ2 && depth--) { - var cosΩ = sinφ0 * sinφ1 + cosφ0 * cosφ1 * Math.cos(λ1 - λ0), k = 1 / (Math.SQRT2 * Math.sqrt(1 + cosΩ)), x = k * (cosφ0 * Math.cos(λ0) + cosφ1 * Math.cos(λ1)), y = k * (cosφ0 * Math.sin(λ0) + cosφ1 * Math.sin(λ1)), z = Math.max(-1, Math.min(1, k * (sinφ0 + sinφ1))), φ2 = Math.asin(z), zε = Math.abs(Math.abs(z) - 1), λ2 = zε < ε || zε < εε && (Math.abs(cosφ0) < εε || Math.abs(cosφ1) < εε) ? (λ0 + λ1) / 2 : Math.atan2(y, x), p = projectPoint(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x0 - x2, dy2 = y0 - y2, dz = dx * dy2 - dy * dx2; - if (dz * dz / distance2 > δ2) { - var cosφ2 = Math.cos(φ2); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, x2, y2, λ2, z, cosφ2, depth); - context.lineTo(x2, y2); - resampleLineTo(x2, y2, λ2, z, cosφ2, x1, y1, λ1, sinφ1, cosφ1, depth); - } - } - } - function closePath() { - var p = projectPoint(λ00, φ00); - resampleLineTo(x0, y0, λ0, sinφ0, cosφ0, p[0], p[1], λ00, Math.sin(φ00), Math.cos(φ00), maxDepth); - context.closePath(); - } - var resample = { - point: point, - moveTo: moveTo, - lineTo: lineTo, - closePath: closePath - }; - function rotatePoint(coordinates) { - return rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians); - } - function projectPoint(λ, φ) { - var point = project(λ, φ); - return [ point[0] * k + δx, δy - point[1] * k ]; - } projection.scale = function(_) { if (!arguments.length) return k; k = +_; @@ -6267,11 +6536,7 @@ δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0; return reset(); }; - projection.precision = function(_) { - if (!arguments.length) return Math.sqrt(δ2); - maxDepth = (δ2 = _ * _) > 0 && 16; - return projection; - }; + d3.rebind(projection, projectResample, "precision"); function reset() { projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project); var center = project(λ, φ); @@ -6285,104 +6550,35 @@ return reset(); }; } - function d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1) { - var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); - return Math.abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2; - } - function d3_geo_projectionCutAntemeridian(rotatePoint) { - var clip = { - point: function(coordinates, context) { - var point = rotatePoint(coordinates); - context.point(point[0], point[1]); + function d3_geo_projectionRadiansRotate(rotate, stream) { + return { + point: function(x, y) { + y = rotate(x * d3_radians, y * d3_radians), x = y[0]; + stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); }, - line: function(coordinates, context, ring) { - if (!(n = coordinates.length)) return [ ring && 0, false ]; - var point = rotatePoint(coordinates[0]), λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n, clean = ring, area = 0, x0 = (point = d3_geo_stereographic(λ0, φ0))[0], x, y0 = point[1], y; - context.moveTo(λ0, φ0); - while (++i < n) { - point = rotatePoint(coordinates[i]); - λ1 = point[0]; - φ1 = point[1]; - sλ1 = λ1 > 0 ? π : -π; - dλ = Math.abs(λ1 - λ0); - if (Math.abs(dλ - π) < ε) { - context.lineTo(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - context.lineTo(λ1, φ0); - clean = false; - } else if (sλ0 !== sλ1 && dλ >= π) { - if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; - if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; - φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1); - context.lineTo(sλ0, φ0); - context.moveTo(sλ1, φ0); - clean = false; - } - if (clean) { - x = (point = d3_geo_stereographic(λ1, φ1))[0]; - y = point[1]; - area += y0 * x - x0 * y; - x0 = x; - y0 = y; - } - context.lineTo(λ0 = λ1, φ0 = φ1); - sλ0 = sλ1; - } - return [ clean && area, true ]; + sphere: function() { + stream.sphere(); }, - polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate); + lineStart: function() { + stream.lineStart(); }, - sphere: function(context) { - d3_geo_projectionSphere(context, d3_geo_antemeridianInterpolate); + lineEnd: function() { + stream.lineEnd(); + }, + polygonStart: function() { + stream.polygonStart(); + }, + polygonEnd: function() { + stream.polygonEnd(); } }; - return clip; - } - function d3_geo_antemeridianInterpolate(from, to, direction, context) { - var φ; - if (from == null) { - φ = direction * π / 2; - context.lineTo(-π, φ); - context.lineTo(0, φ); - context.lineTo(π, φ); - context.lineTo(π, 0); - context.lineTo(π, -φ); - context.lineTo(0, -φ); - context.lineTo(-π, -φ); - context.lineTo(-π, 0); - } else if (Math.abs(from[0] - to[0]) > ε) { - var s = (from[0] < to[0] ? 1 : -1) * π; - φ = direction * s / 2; - context.lineTo(-s, φ); - context.lineTo(0, φ); - context.lineTo(s, φ); - } else { - context.lineTo(to[0], to[1]); - } - } - function d3_geo_projectionSphere(context, interpolate) { - var moved = false; - interpolate(null, null, 1, { - lineTo: function(x, y) { - (moved ? context.lineTo : (moved = true, context.moveTo))(x, y); - } - }); - context.closePath(); } function d3_geo_rotation(δλ, δφ, δγ) { - return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; + return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_equirectangular; } - function d3_geo_identityRotation(λ, φ) { - return [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; - } - d3_geo_identityRotation.invert = function(x, y) { - return [ x, y ]; - }; function d3_geo_forwardRotationλ(δλ) { return function(λ, φ) { - return [ (λ += δλ) > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; + return λ += δλ, [ λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ ]; }; } function d3_geo_rotationλ(δλ) { From ae8891c48581e8b19b17b36c5d341d17658d779f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:05:21 -0500 Subject: [PATCH 18/50] Unused vars --- js/id/ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/ui.js b/js/id/ui.js index 8d764d695..87cc3745a 100644 --- a/js/id/ui.js +++ b/js/id/ui.js @@ -103,7 +103,7 @@ iD.ui = function (context) { .on('click.editor', history.redo) .call(undo_tooltip); - var save_button = limiter.append('div').attr('class','button-wrap col1').append('button') + limiter.append('div').attr('class','button-wrap col1').append('button') .attr('class', 'save col12') .call(iD.ui.save(context)); @@ -127,7 +127,7 @@ iD.ui = function (context) { .call(iD.ui.geolocate(map)); } - var gc = container.append('div').attr('class', 'geocode-control map-control') + container.append('div').attr('class', 'geocode-control map-control') .call(iD.ui.geocoder().map(map)); container.append('div').attr('class', 'map-control layerswitcher-control') From 3da0e70c0fdc663a25e8e4cb3043a2b154e3cc0f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:30:46 -0500 Subject: [PATCH 19/50] Shift-selection --- js/id/behavior/select.js | 8 ++++-- js/id/id.js | 8 ++++++ test/index.html | 1 + test/index_packaged.html | 1 + test/spec/behavior/select.js | 54 ++++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/spec/behavior/select.js diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js index e078fdbf1..b5276a4d0 100644 --- a/js/id/behavior/select.js +++ b/js/id/behavior/select.js @@ -2,8 +2,12 @@ iD.behavior.Select = function(context) { function click() { var datum = d3.select(d3.event.target).datum(); if (datum instanceof iD.Entity) { - context.enter(iD.modes.Select(context, [datum.id])); - } else { + if (d3.event.shiftKey) { + context.enter(iD.modes.Select(context, context.selection().concat([datum.id]))); + } else { + context.enter(iD.modes.Select(context, [datum.id])); + } + } else if (!d3.event.shiftKey) { context.enter(iD.modes.Browse(context)); } } diff --git a/js/id/id.js b/js/id/id.js index 0b2bbd617..358610525 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -56,6 +56,14 @@ window.iD = function () { return mode; }; + context.selection = function() { + if (mode.id === 'select') { + return mode.selection(); + } else { + return []; + } + }; + /* Behaviors */ context.install = function(behavior) { context.surface().call(behavior); diff --git a/test/index.html b/test/index.html index d32331ba9..16f86b781 100644 --- a/test/index.html +++ b/test/index.html @@ -185,6 +185,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 11f8368f4..588c8e0a3 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -80,6 +80,7 @@ + diff --git a/test/spec/behavior/select.js b/test/spec/behavior/select.js new file mode 100644 index 000000000..42c568cca --- /dev/null +++ b/test/spec/behavior/select.js @@ -0,0 +1,54 @@ +describe("iD.behavior.Select", function() { + var a, b, context, behavior, container; + + beforeEach(function() { + container = d3.select('body').append('div'); + + context = iD().container(container); + + a = iD.Node({loc: [0, 0]}); + b = iD.Node({loc: [0, 0]}); + + context.perform(iD.actions.AddEntity(a), iD.actions.AddEntity(b)); + + container.call(context.map()) + .append('div') + .attr('class', 'inspector-wrap'); + + context.surface().selectAll('circle') + .data([a, b]) + .enter().append('circle') + .attr('class', function(d) { return d.id; }); + + behavior = iD.behavior.Select(context); + context.install(behavior); + }); + + afterEach(function() { + context.uninstall(behavior); + container.remove(); + }); + + specify("click on entity selects the entity", function() { + happen.click(context.surface().select('.' + a.id).node()); + expect(context.selection()).to.eql([a.id]); + }); + + specify("click on empty space clears the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node()); + expect(context.selection()).to.eql([]); + }); + + specify("shift-click on entity adds the entity to the selection", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().select('.' + b.id).node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id, b.id]); + }); + + specify("shift-click on empty space leaves the selection unchanged", function() { + context.enter(iD.modes.Select(context, [a.id])); + happen.click(context.surface().node(), {shiftKey: true}); + expect(context.selection()).to.eql([a.id]); + }); +}); From 7ba31f05d2db3ec3d92f954cb97160cab5e1cc25 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 14:46:59 -0500 Subject: [PATCH 20/50] Support deleting relations --- index.html | 1 + js/id/actions/delete_relation.js | 13 +++++++++++++ js/id/operations/delete.js | 10 ++++++---- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_relation.js | 17 +++++++++++++++++ 6 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 js/id/actions/delete_relation.js create mode 100644 test/spec/actions/delete_relation.js diff --git a/index.html b/index.html index b0e46d55d..5cd8ff735 100644 --- a/index.html +++ b/index.html @@ -77,6 +77,7 @@ + diff --git a/js/id/actions/delete_relation.js b/js/id/actions/delete_relation.js new file mode 100644 index 000000000..48c62f1e1 --- /dev/null +++ b/js/id/actions/delete_relation.js @@ -0,0 +1,13 @@ +// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/DeleteRelationAction.as +iD.actions.DeleteRelation = function(relationId) { + return function(graph) { + var relation = graph.entity(relationId); + + graph.parentRelations(relation) + .forEach(function(parent) { + graph = graph.replace(parent.removeMember(relationId)); + }); + + return graph.remove(relation); + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index d631a2ed7..33c638ce0 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -3,7 +3,11 @@ iD.operations.Delete = function(selection, context) { var operation = function() { var entity = context.entity(entityId), - action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type], + action = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }[entity.type], annotation = t('operations.delete.annotation.' + context.geometry(entityId)); context.perform( @@ -12,9 +16,7 @@ iD.operations.Delete = function(selection, context) { }; operation.available = function() { - var entity = context.entity(entityId); - return selection.length === 1 && - (entity.type === 'way' || entity.type === 'node'); + return selection.length === 1; }; operation.enabled = function() { diff --git a/test/index.html b/test/index.html index 16f86b781..120938b5f 100644 --- a/test/index.html +++ b/test/index.html @@ -74,6 +74,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 588c8e0a3..1764e5a9e 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -36,6 +36,7 @@ + diff --git a/test/spec/actions/delete_relation.js b/test/spec/actions/delete_relation.js new file mode 100644 index 000000000..c96909f3f --- /dev/null +++ b/test/spec/actions/delete_relation.js @@ -0,0 +1,17 @@ +describe("iD.actions.DeleteRelation", function () { + it("removes the relation from the graph", function () { + var relation = iD.Relation(), + action = iD.actions.DeleteRelation(relation.id), + graph = action(iD.Graph([relation])); + expect(graph.entity(relation.id)).to.be.undefined; + }); + + it("removes the relation from parent relations", function () { + var a = iD.Relation(), + b = iD.Relation(), + parent = iD.Relation({members: [{ id: a.id }, { id: b.id }]}), + action = iD.actions.DeleteRelation(a.id), + graph = action(iD.Graph([a, b, parent])); + expect(graph.entity(parent.id).members).to.eql([{ id: b.id }]); + }); +}); From 58fcf746a2f030210a79947f168434f4dbcb1627 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:08:41 -0500 Subject: [PATCH 21/50] Delete multiple --- index.html | 1 + js/id/actions/delete_multiple.js | 15 +++++++++++++++ js/id/operations/delete.js | 20 +++++++++----------- locale/en.js | 3 ++- test/index.html | 2 ++ test/index_packaged.html | 1 + test/spec/actions/delete_multiple.js | 12 ++++++++++++ 7 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 js/id/actions/delete_multiple.js create mode 100644 test/spec/actions/delete_multiple.js diff --git a/index.html b/index.html index 5cd8ff735..f94a11019 100644 --- a/index.html +++ b/index.html @@ -76,6 +76,7 @@ + diff --git a/js/id/actions/delete_multiple.js b/js/id/actions/delete_multiple.js new file mode 100644 index 000000000..839c1d780 --- /dev/null +++ b/js/id/actions/delete_multiple.js @@ -0,0 +1,15 @@ +iD.actions.DeleteMultiple = function(ids) { + return function(graph) { + var actions = { + way: iD.actions.DeleteWay, + node: iD.actions.DeleteNode, + relation: iD.actions.DeleteRelation + }; + + ids.forEach(function (id) { + graph = actions[graph.entity(id).type](id)(graph); + }); + + return graph; + }; +}; diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 33c638ce0..2c9765b62 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -1,22 +1,20 @@ iD.operations.Delete = function(selection, context) { - var entityId = selection[0]; - var operation = function() { - var entity = context.entity(entityId), - action = { - way: iD.actions.DeleteWay, - node: iD.actions.DeleteNode, - relation: iD.actions.DeleteRelation - }[entity.type], - annotation = t('operations.delete.annotation.' + context.geometry(entityId)); + var annotation; + + if (selection.length === 1) { + annotation = t('operations.delete.annotation.' + context.geometry(selection[0])); + } else { + annotation = t('operations.delete.annotation.multiple', {n: selection.length}); + } context.perform( - action(entityId), + iD.actions.DeleteMultiple(selection), annotation); }; operation.available = function() { - return selection.length === 1; + return true; }; operation.enabled = function() { diff --git a/locale/en.js b/locale/en.js index d7fc3628f..b32149ee3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -73,7 +73,8 @@ locale.en = { point: "Deleted a point.", vertex: "Deleted a node from a way.", line: "Deleted a line.", - area: "Deleted an area." + area: "Deleted an area.", + multiple: "Deleted {n} objects." } }, move: { diff --git a/test/index.html b/test/index.html index 120938b5f..1ed505174 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ + @@ -141,6 +142,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 1764e5a9e..4b44086c5 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -35,6 +35,7 @@ + diff --git a/test/spec/actions/delete_multiple.js b/test/spec/actions/delete_multiple.js new file mode 100644 index 000000000..3a70adc38 --- /dev/null +++ b/test/spec/actions/delete_multiple.js @@ -0,0 +1,12 @@ +describe("iD.actions.DeleteMultiple", function () { + it("deletes multiple entities of heterogeneous types", function () { + var n = iD.Node(), + w = iD.Way(), + r = iD.Relation(), + action = iD.actions.DeleteMultiple([n.id, w.id, r.id]), + graph = action(iD.Graph([n, w, r])); + expect(graph.entity(n.id)).to.be.undefined; + expect(graph.entity(w.id)).to.be.undefined; + expect(graph.entity(r.id)).to.be.undefined; + }); +}); From 7235632a6304ce1916c4b60e56a61518e4842a7c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:13:44 -0500 Subject: [PATCH 22/50] Update logic for multi-select --- js/id/modes/select.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/js/id/modes/select.js b/js/id/modes/select.js index 36066d7d0..02db6a57f 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -82,21 +82,22 @@ iD.modes.Select = function(context, selection, initial) { inspector .on('changeTags', changeTags) .on('close', function() { context.enter(iD.modes.Browse(context)); }); + } - context.history().on('change.select', function() { + context.history().on('change.select', function() { + context.surface().call(radialMenu.close); + + if (_.any(selection, function (id) { return !context.entity(id); })) { // Exit mode if selected entity gets undone - var oldEntity = entity, - newEntity = context.entity(selection[0]); + context.enter(iD.modes.Browse(context)); - if (!newEntity) { - context.enter(iD.modes.Browse(context)); - } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) { + } else if (entity) { + var newEntity = context.entity(selection[0]); + if (!_.isEqual(entity.tags, newEntity.tags)) { inspector.tags(newEntity.tags); } - - context.surface().call(radialMenu.close); - }); - } + } + }); context.map().on('move.select', function() { context.surface().call(radialMenu.close); From 7bfdf4de1a232df610f1f543b3c30fcc2b7126c3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:23:30 +0100 Subject: [PATCH 23/50] Change search to use OSM Nominatim API --- js/id/renderer/map.js | 4 ++++ js/id/ui/geocoder.js | 13 +++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..e076e33a5 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,6 +346,10 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); + if(newZoom > 19){ + newZoom = 19; + } + map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index ed37d2cb1..c54b8d6ad 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,18 +6,19 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - d3.json('http://a.tiles.mapbox.com/v3/openstreetmap.map-hn253zqn/geocode/' + - encodeURIComponent(this.value) + '.json', function(err, resp) { + var searchVal = this.value; + d3.json('http://nominatim.openstreetmap.org/search/' + + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); hide(); - if (!resp.results.length) { + if (!resp.length) { return iD.ui.flash() .select('.content') .append('h3') - .text('No location found for "' + resp.query[0] + '"'); + .text('No location found for "' + searchVal + '"'); } - var bounds = resp.results[0][0].bounds; - map.extent(iD.geo.Extent([bounds[0], bounds[1]], [bounds[2], bounds[3]])); + var bounds = resp[0].boundingbox; + map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); }); } From 839844de274d3d3f6bcce3f39460fe598330629e Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 22:28:43 +0100 Subject: [PATCH 24/50] Convert tabs to spaces --- js/id/renderer/map.js | 6 +++--- js/id/ui/geocoder.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index e076e33a5..5258f8701 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,9 +346,9 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } + if(newZoom > 19){ + newZoom = 19; + } map.centerZoom(extent.center(), newZoom); } diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index c54b8d6ad..e6695792f 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -6,7 +6,7 @@ iD.ui.geocoder = function() { function keydown() { if (d3.event.keyCode !== 13) return; d3.event.preventDefault(); - var searchVal = this.value; + var searchVal = this.value; d3.json('http://nominatim.openstreetmap.org/search/' + encodeURIComponent(searchVal) + '?limit=10&format=json', function(err, resp) { if (err) return hide(); From f4975447f89cf9c2eba368ac03658d5e8951e53a Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:05:47 +0100 Subject: [PATCH 25/50] Limit zoom to 19 if geocode bounding box too small --- js/id/renderer/map.js | 4 ---- js/id/ui/geocoder.js | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 5258f8701..1f22a7c81 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -346,10 +346,6 @@ iD.Map = function(context) { vZoomDiff = Math.log(Math.abs(vFactor)) / Math.LN2, newZoom = map.zoom() - Math.max(hZoomDiff, vZoomDiff); - if(newZoom > 19){ - newZoom = 19; - } - map.centerZoom(extent.center(), newZoom); } }; diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index e6695792f..6d4d39d03 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,6 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); + if (map.zoom() > 19) map.zoom(19); }); } From 01e8f5f606ad5ce5affa54c8aac5fb7b91171af3 Mon Sep 17 00:00:00 2001 From: Dr Ian Date: Fri, 1 Feb 2013 23:08:11 +0100 Subject: [PATCH 26/50] Convert tabs to spaces --- js/id/ui/geocoder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index 6d4d39d03..d7de24b37 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -19,7 +19,7 @@ iD.ui.geocoder = function() { } var bounds = resp[0].boundingbox; map.extent(iD.geo.Extent([parseFloat(bounds[3]), parseFloat(bounds[0])], [parseFloat(bounds[2]), parseFloat(bounds[1])])); - if (map.zoom() > 19) map.zoom(19); + if (map.zoom() > 19) map.zoom(19); }); } From e1d5a0cb0324ae5334e6973bb959c56ad8137a79 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:45:45 -0500 Subject: [PATCH 27/50] Unjoin -> Disconnect --- combobox.html | 2 +- css/app.css | 2 +- index.html | 4 ++-- .../actions/{unjoin_node.js => disconnect.js} | 4 ++-- js/id/operations/disconnect.js | 24 +++++++++++++++++++ js/id/operations/unjoin.js | 24 ------------------- locale/en.js | 12 +++++----- test/index.html | 6 ++--- test/index_packaged.html | 2 +- .../actions/{unjoin_node.js => disconnect.js} | 10 ++++---- 10 files changed, 45 insertions(+), 45 deletions(-) rename js/id/actions/{unjoin_node.js => disconnect.js} (92%) create mode 100644 js/id/operations/disconnect.js delete mode 100644 js/id/operations/unjoin.js rename test/spec/actions/{unjoin_node.js => disconnect.js} (89%) diff --git a/combobox.html b/combobox.html index 0d97698b0..469e73823 100644 --- a/combobox.html +++ b/combobox.html @@ -79,13 +79,13 @@ + - diff --git a/css/app.css b/css/app.css index 1a880ad02..7a0751d02 100644 --- a/css/app.css +++ b/css/app.css @@ -476,7 +476,7 @@ button[disabled] .icon.nearby { background-position: -340px -40px;} .icon-operation-circularize { background-position: -20px -140px;} .icon-operation-straighten { background-position: -40px -140px;} .icon-operation-split { background-position: -60px -140px;} -.icon-operation-unjoin { background-position: -80px -140px;} +.icon-operation-disconnect { background-position: -80px -140px;} .icon-operation-reverse { background-position: -100px -140px;} .icon-operation-move { background-position: -120px -140px;} .icon-operation-merge { background-position: -140px -140px;} diff --git a/index.html b/index.html index f94a11019..3ef8abad5 100644 --- a/index.html +++ b/index.html @@ -80,13 +80,13 @@ + - @@ -112,10 +112,10 @@ + - diff --git a/js/id/actions/unjoin_node.js b/js/id/actions/disconnect.js similarity index 92% rename from js/id/actions/unjoin_node.js rename to js/id/actions/disconnect.js index 39e05a9cf..8645c436b 100644 --- a/js/id/actions/unjoin_node.js +++ b/js/id/actions/disconnect.js @@ -1,4 +1,4 @@ -// Unjoin the ways at the given node. +// Disconect the ways at the given node. // // For testing convenience, accepts an ID to assign to the (first) new node. // Normally, this will be undefined and the way will automatically @@ -8,7 +8,7 @@ // https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as // https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java // -iD.actions.UnjoinNode = function(nodeId, newNodeId) { +iD.actions.Disconnect = function(nodeId, newNodeId) { var action = function(graph) { if (!action.enabled(graph)) return graph; diff --git a/js/id/operations/disconnect.js b/js/id/operations/disconnect.js new file mode 100644 index 000000000..c8cc0d014 --- /dev/null +++ b/js/id/operations/disconnect.js @@ -0,0 +1,24 @@ +iD.operations.Disconnect = function(selection, context) { + var entityId = selection[0], + action = iD.actions.Disconnect(entityId); + + var operation = function() { + context.perform(action, t('operations.disconnect.annotation')); + }; + + operation.available = function() { + return selection.length === 1 && + context.geometry(entityId) === 'vertex'; + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "disconnect"; + operation.key = t('operations.disconnect.key'); + operation.title = t('operations.disconnect.title'); + operation.description = t('operations.disconnect.description'); + + return operation; +}; diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js deleted file mode 100644 index d4fcc1426..000000000 --- a/js/id/operations/unjoin.js +++ /dev/null @@ -1,24 +0,0 @@ -iD.operations.Unjoin = function(selection, context) { - var entityId = selection[0], - action = iD.actions.UnjoinNode(entityId); - - var operation = function() { - context.perform(action, 'Unjoined lines.'); - }; - - operation.available = function() { - return selection.length === 1 && - context.geometry(entityId) === 'vertex'; - }; - - operation.enabled = function() { - return action.enabled(context.graph()); - }; - - operation.id = "unjoin"; - operation.key = t('operations.unjoin.key'); - operation.title = t('operations.unjoin.title'); - operation.description = t('operations.unjoin.description'); - - return operation; -}; diff --git a/locale/en.js b/locale/en.js index b32149ee3..7d1bed947 100644 --- a/locale/en.js +++ b/locale/en.js @@ -77,6 +77,12 @@ locale.en = { multiple: "Deleted {n} objects." } }, + disconnect: { + title: "Disconnect", + description: "Disconnect these ways from each other.", + key: "D", + annotation: "Disconnected ways." + }, move: { title: "Move", description: "Move this to a different location.", @@ -99,12 +105,6 @@ locale.en = { description: "Split this into two ways at this point.", key: "X", annotation: "Split a way." - }, - unjoin: { - title: "Unjoin", - description: "Disconnect these ways from each other.", - key: "⇧-J", - annotation: "Unjoined ways." } }, diff --git a/test/index.html b/test/index.html index 1ed505174..e06e18a4b 100644 --- a/test/index.html +++ b/test/index.html @@ -77,12 +77,12 @@ + - @@ -108,10 +108,10 @@ + - @@ -146,12 +146,12 @@ + - diff --git a/test/index_packaged.html b/test/index_packaged.html index 4b44086c5..673ea46e8 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -39,12 +39,12 @@ + - diff --git a/test/spec/actions/unjoin_node.js b/test/spec/actions/disconnect.js similarity index 89% rename from test/spec/actions/unjoin_node.js rename to test/spec/actions/disconnect.js index 5901e97da..4ce298feb 100644 --- a/test/spec/actions/unjoin_node.js +++ b/test/spec/actions/disconnect.js @@ -1,9 +1,9 @@ -describe("iD.actions.UnjoinNode", function () { +describe("iD.actions.Disconnect", function () { describe("#enabled", function () { it("returns false for a node shared by less than two ways", function () { var graph = iD.Graph({'a': iD.Node()}); - expect(iD.actions.UnjoinNode('a').enabled(graph)).to.equal(false); + expect(iD.actions.Disconnect('a').enabled(graph)).to.equal(false); }); it("returns true for a node shared by two or more ways", function () { @@ -19,7 +19,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - expect(iD.actions.UnjoinNode('b').enabled(graph)).to.equal(true); + expect(iD.actions.Disconnect('b').enabled(graph)).to.equal(true); }); }); @@ -46,7 +46,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); expect(graph.entity('|').nodes).to.eql(['d', 'e']); @@ -64,7 +64,7 @@ describe("iD.actions.UnjoinNode", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.UnjoinNode('b', 'e')(graph); + graph = iD.actions.Disconnect('b', 'e')(graph); // Immutable loc => should be shared by identity. expect(graph.entity('b').loc).to.equal(loc); From c86792a77b9e7e1c02cb0de6ec1cac6107faf9eb Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:56:48 -0500 Subject: [PATCH 28/50] SplitWay -> Split --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{split_way.js => split.js} | 2 +- js/id/operations/split.js | 2 +- test/index.html | 4 +-- test/index_packaged.html | 2 +- test/spec/actions/{split_way.js => split.js} | 28 ++++++++++---------- 7 files changed, 21 insertions(+), 21 deletions(-) rename js/id/actions/{split_way.js => split.js} (97%) rename test/spec/actions/{split_way.js => split.js} (92%) diff --git a/combobox.html b/combobox.html index 469e73823..914d834e7 100644 --- a/combobox.html +++ b/combobox.html @@ -85,7 +85,7 @@ - + diff --git a/index.html b/index.html index 3ef8abad5..defb5403b 100644 --- a/index.html +++ b/index.html @@ -86,7 +86,7 @@ - + diff --git a/js/id/actions/split_way.js b/js/id/actions/split.js similarity index 97% rename from js/id/actions/split_way.js rename to js/id/actions/split.js index 6fbd08595..0c648bd26 100644 --- a/js/id/actions/split_way.js +++ b/js/id/actions/split.js @@ -7,7 +7,7 @@ // Reference: // https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/SplitWayAction.as // -iD.actions.SplitWay = function(nodeId, newWayId) { +iD.actions.Split = function(nodeId, newWayId) { function candidateWays(graph) { var node = graph.entity(nodeId), parents = graph.parentWays(node); diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 0d7ff6ae4..07d7d56ff 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -1,6 +1,6 @@ iD.operations.Split = function(selection, context) { var entityId = selection[0], - action = iD.actions.SplitWay(entityId); + action = iD.actions.Split(entityId); var operation = function() { context.perform(action, t('operations.split.annotation')); diff --git a/test/index.html b/test/index.html index e06e18a4b..ceb1e7fbc 100644 --- a/test/index.html +++ b/test/index.html @@ -82,7 +82,7 @@ - + @@ -151,7 +151,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 673ea46e8..050badd7d 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -44,7 +44,7 @@ - + diff --git a/test/spec/actions/split_way.js b/test/spec/actions/split.js similarity index 92% rename from test/spec/actions/split_way.js rename to test/spec/actions/split.js index 2958521a9..5ee68e67e 100644 --- a/test/spec/actions/split_way.js +++ b/test/spec/actions/split.js @@ -1,4 +1,4 @@ -describe("iD.actions.SplitWay", function () { +describe("iD.actions.Split", function () { describe("#enabled", function () { it("returns true for a non-end node of a single way", function () { var graph = iD.Graph({ @@ -8,7 +8,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.true; + expect(iD.actions.Split('b').enabled(graph)).to.be.true; }); it("returns false for the first node of a single way", function () { @@ -18,7 +18,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('a').enabled(graph)).to.be.false; + expect(iD.actions.Split('a').enabled(graph)).to.be.false; }); it("returns false for the last node of a single way", function () { @@ -28,7 +28,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b']}) }); - expect(iD.actions.SplitWay('b').enabled(graph)).to.be.false; + expect(iD.actions.Split('b').enabled(graph)).to.be.false; }); }); @@ -48,7 +48,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -63,7 +63,7 @@ describe("iD.actions.SplitWay", function () { '-': iD.Way({id: '-', nodes: ['a', 'b', 'c'], tags: tags}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); // Immutable tags => should be shared by identity. expect(graph.entity('-').tags).to.equal(tags); @@ -92,7 +92,7 @@ describe("iD.actions.SplitWay", function () { '|': iD.Way({id: '|', nodes: ['d', 'b']}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('-').nodes).to.eql(['a', 'b']); expect(graph.entity('=').nodes).to.eql(['b', 'c']); @@ -118,7 +118,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=']); }); @@ -144,7 +144,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '-', type: 'way'}, {id: '~', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['-', '=', '~']); }); @@ -170,7 +170,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '=', '-']); }); @@ -184,7 +184,7 @@ describe("iD.actions.SplitWay", function () { 'r': iD.Relation({id: 'r', members: [{id: '~', type: 'way'}, {id: '-', type: 'way'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(_.pluck(graph.entity('r').members, 'id')).to.eql(['~', '-', '=']); }); @@ -214,7 +214,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '=', role: 'from'}, @@ -246,7 +246,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '~', role: 'from'}, @@ -278,7 +278,7 @@ describe("iD.actions.SplitWay", function () { {id: 'c', role: 'via'}]}) }); - graph = iD.actions.SplitWay('b', '=')(graph); + graph = iD.actions.Split('b', '=')(graph); expect(graph.entity('r').members).to.eql([ {id: '-', role: 'from'}, From 9120f33aa7c5ea68a09a7084b51abdaabe1dd471 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 15:58:34 -0500 Subject: [PATCH 29/50] ReverseWay -> Reverse --- combobox.html | 2 +- index.html | 2 +- js/id/actions/{reverse_way.js => reverse.js} | 2 +- js/id/operations/reverse.js | 2 +- test/index.html | 4 +- test/index_packaged.html | 2 +- .../actions/{reverse_way.js => reverse.js} | 40 +++++++++---------- 7 files changed, 27 insertions(+), 27 deletions(-) rename js/id/actions/{reverse_way.js => reverse.js} (98%) rename test/spec/actions/{reverse_way.js => reverse.js} (76%) diff --git a/combobox.html b/combobox.html index 914d834e7..c739f4898 100644 --- a/combobox.html +++ b/combobox.html @@ -84,7 +84,7 @@ - + diff --git a/index.html b/index.html index defb5403b..87e93c643 100644 --- a/index.html +++ b/index.html @@ -85,7 +85,7 @@ - + diff --git a/js/id/actions/reverse_way.js b/js/id/actions/reverse.js similarity index 98% rename from js/id/actions/reverse_way.js rename to js/id/actions/reverse.js index 160017637..5c8315467 100644 --- a/js/id/actions/reverse_way.js +++ b/js/id/actions/reverse.js @@ -27,7 +27,7 @@ http://wiki.openstreetmap.org/wiki/Route#Members http://josm.openstreetmap.de/browser/josm/trunk/src/org/openstreetmap/josm/corrector/ReverseWayTagCorrector.java */ -iD.actions.ReverseWay = function(wayId) { +iD.actions.Reverse = function(wayId) { var replacements = [ [/:right$/, ':left'], [/:left$/, ':right'], [/:forward$/, ':backward'], [/:backward$/, ':forward'] diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js index 77bacc64e..f750c134d 100644 --- a/js/id/operations/reverse.js +++ b/js/id/operations/reverse.js @@ -3,7 +3,7 @@ iD.operations.Reverse = function(selection, context) { var operation = function() { context.perform( - iD.actions.ReverseWay(entityId), + iD.actions.Reverse(entityId), t('operations.reverse.annotation')); }; diff --git a/test/index.html b/test/index.html index ceb1e7fbc..512a1ee1e 100644 --- a/test/index.html +++ b/test/index.html @@ -81,7 +81,7 @@ - + @@ -150,7 +150,7 @@ - + diff --git a/test/index_packaged.html b/test/index_packaged.html index 050badd7d..5adaf06f6 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -43,7 +43,7 @@ - + diff --git a/test/spec/actions/reverse_way.js b/test/spec/actions/reverse.js similarity index 76% rename from test/spec/actions/reverse_way.js rename to test/spec/actions/reverse.js index ca61cb778..40d0f5500 100644 --- a/test/spec/actions/reverse_way.js +++ b/test/spec/actions/reverse.js @@ -1,9 +1,9 @@ -describe("iD.actions.ReverseWay", function () { +describe("iD.actions.Reverse", function () { it("reverses the order of nodes in the way", function () { var node1 = iD.Node(), node2 = iD.Node(), way = iD.Way({nodes: [node1.id, node2.id]}), - graph = iD.actions.ReverseWay(way.id)(iD.Graph([node1, node2, way])); + graph = iD.actions.Reverse(way.id)(iD.Graph([node1, node2, way])); expect(graph.entity(way.id).nodes).to.eql([node2.id, node1.id]); }); @@ -11,7 +11,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'highway': 'residential'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'highway': 'residential'}); }); @@ -19,7 +19,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'oneway': 'yes'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'oneway': 'yes'}); }); @@ -27,10 +27,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'cycleway:right': 'lane'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:left': 'lane'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'cycleway:right': 'lane'}); }); @@ -38,10 +38,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:forward': '25'}); }); @@ -49,10 +49,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -60,10 +60,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': 'up'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'down'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': 'up'}); }); @@ -71,16 +71,16 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'incline': '5%'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-5%'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '5%'}); way = iD.Way({tags: {'incline': '.8°'}}); graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'incline': '-.8°'}); }); @@ -88,10 +88,10 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'sidewalk': 'right'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'left'}); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'sidewalk': 'right'}); }); @@ -99,7 +99,7 @@ describe("iD.actions.ReverseWay", function () { var way = iD.Way({tags: {'maxspeed:forward': '25', 'maxspeed:backward': '30'}}), graph = iD.Graph([way]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(way.id).tags).to.eql({'maxspeed:backward': '25', 'maxspeed:forward': '30'}); }); @@ -108,10 +108,10 @@ describe("iD.actions.ReverseWay", function () { relation = iD.Relation({members: [{type: 'way', id: way.id, role: 'forward'}]}), graph = iD.Graph([way, relation]); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('backward'); - graph = iD.actions.ReverseWay(way.id)(graph); + graph = iD.actions.Reverse(way.id)(graph); expect(graph.entity(relation.id).members[0].role).to.eql('forward'); }); }); From 20730e5f1a442e766416951cb88a73d49709e9a7 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:16 -0500 Subject: [PATCH 30/50] Entity#mergeTags --- js/id/graph/entity.js | 14 ++++++++++++++ test/spec/graph/entity.js | 27 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index f91bb89d9..ae612c268 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -66,6 +66,20 @@ iD.Entity.prototype = { return iD.Entity(this, attrs, {_updated: true}); }, + mergeTags: function(tags) { + var merged = _.clone(this.tags); + for (var k in tags) { + var t1 = merged[k], + t2 = tags[k]; + if (t1 && t1 !== t2) { + merged[k] = t1 + "; " + t2; + } else { + merged[k] = t2; + } + } + return this.update({tags: merged}); + }, + created: function() { return this._updated && this.osmId().charAt(0) === '-'; }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index a573f4c8c..088ab5516 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -69,6 +69,33 @@ describe('iD.Entity', function () { }); }); + describe("#mergeTags", function () { + it("returns a new Entity", function () { + var a = iD.Entity(), + b = a.mergeTags({}); + expect(b instanceof iD.Entity).to.be.true; + expect(a).not.to.equal(b); + }); + + it("merges tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({b: 'b'}); + expect(b.tags).to.eql({a: 'a', b: 'b'}); + }); + + it("combines non-conflicting tags", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'a'}); + expect(b.tags).to.eql({a: 'a'}); + }); + + it("combines conflicting tags with semicolons", function () { + var a = iD.Entity({tags: {a: 'a'}}), + b = a.mergeTags({a: 'b'}); + expect(b.tags).to.eql({a: 'a; b'}); + }); + }); + describe("#osmId", function () { it("returns an OSM ID as a string", function () { expect(iD.Entity({id: 'w1234'}).osmId()).to.eql('1234'); From a8410be6eb9042935fb25533888fcada2f6e06f0 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:24:50 -0500 Subject: [PATCH 31/50] iD.actions.Join --- index.html | 1 + js/id/actions/join.js | 65 ++++++++++++++ js/id/actions/split.js | 2 + test/index.html | 2 + test/index_packaged.html | 1 + test/spec/actions/join.js | 172 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 243 insertions(+) create mode 100644 js/id/actions/join.js create mode 100644 test/spec/actions/join.js diff --git a/index.html b/index.html index 87e93c643..07063cf81 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/js/id/actions/join.js b/js/id/actions/join.js new file mode 100644 index 000000000..fd6862a9b --- /dev/null +++ b/js/id/actions/join.js @@ -0,0 +1,65 @@ +// Join ways at the end node they share. +// +// This is the inverse of `iD.actions.Split`. +// +// Reference: +// https://github.com/systemed/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/MergeWaysAction.as +// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/CombineWayAction.java +// +iD.actions.Join = function(idA, idB) { + var action = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB), + nodes, tags; + + if (a.first() === b.first()) { + // a <-- b ==> c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.slice().reverse().concat(a.nodes.slice(1)); + + } else if (a.first() === b.last()) { + // a <-- b <== c + // Expected result: + // a <-- b <-- c + nodes = b.nodes.concat(a.nodes.slice(1)); + + } else if (a.last() === b.first()) { + // a --> b ==> c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice(1)); + + } else if (a.last() === b.last()) { + // a --> b <== c + // Expected result: + // a --> b --> c + nodes = a.nodes.concat(b.nodes.slice().reverse().slice(1)); + } + + graph.parentRelations(b) + .forEach(function (parent) { + var memberA = parent.memberById(idA), + memberB = parent.memberById(idB); + if (!memberA) { + graph = graph.replace(parent.addMember({id: idA, role: memberB.role})); + } + }); + + graph = graph.replace(a.mergeTags(b.tags).update({nodes: nodes})); + graph = iD.actions.DeleteWay(idB)(graph); + + return graph; + }; + + action.enabled = function(graph) { + var a = graph.entity(idA), + b = graph.entity(idB); + return a.first() === b.first() || + a.first() === b.last() || + a.last() === b.first() || + a.last() === b.last(); + }; + + return action; +}; diff --git a/js/id/actions/split.js b/js/id/actions/split.js index 0c648bd26..19a71c619 100644 --- a/js/id/actions/split.js +++ b/js/id/actions/split.js @@ -1,5 +1,7 @@ // Split a way at the given node. // +// This is the inverse of `iD.actions.Join`. +// // For testing convenience, accepts an ID to assign to the new way. // Normally, this will be undefined and the way will automatically // be assigned a new ID. diff --git a/test/index.html b/test/index.html index 512a1ee1e..524894c36 100644 --- a/test/index.html +++ b/test/index.html @@ -78,6 +78,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 5adaf06f6..948ed0ae1 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -40,6 +40,7 @@ + diff --git a/test/spec/actions/join.js b/test/spec/actions/join.js new file mode 100644 index 000000000..1cddb94bf --- /dev/null +++ b/test/spec/actions/join.js @@ -0,0 +1,172 @@ +describe("iD.actions.Join", function () { + describe("#enabled", function () { + it("returns true for ways that share an end/start node", function () { + // a --> b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/end node", function () { + // a <-- b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share a start/start node", function () { + // a <-- b ==> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns true for ways that share an end/end node", function () { + // a --> b <== c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.true; + }); + + it("returns false for ways that don't share the necessary nodes", function () { + // a -- b -- c + // | + // d + 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']}), + '=': iD.Way({id: '=', nodes: ['b', 'd']}) + }); + + expect(iD.actions.Join('-', '=').enabled(graph)).to.be.false; + }); + }); + + it("joins a --> b ==> c", function () { + // Expected result: + // a --> b --> c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b <== c", function () { + // Expected result: + // a <-- b <-- c + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a <-- b ==> c", function () { + // Expected result: + // a <-- b <-- c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['b', 'a']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['c', 'b', 'a']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("joins a --> b <== c", function () { + // Expected result: + // a --> b --> c + // tags on === reversed + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['c', 'b']}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('=')).to.be.undefined; + }); + + it("merges tags", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b'], tags: {a: 'a', b: '-', c: 'c'}}), + '=': iD.Way({id: '=', nodes: ['b', 'c'], tags: {a: 'a', b: '=', d: 'd'}}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('-').tags).to.eql({a: 'a', b: '-; =', c: 'c', d: 'd'}); + }); + + it("merges relations", function () { + var graph = iD.Graph({ + 'a': iD.Node({id: 'a'}), + 'b': iD.Node({id: 'b'}), + 'c': iD.Node({id: 'c'}), + '-': iD.Way({id: '-', nodes: ['a', 'b']}), + '=': iD.Way({id: '=', nodes: ['b', 'c']}), + 'r1': iD.Relation({id: 'r1', members: [{id: '=', role: 'r1'}]}), + 'r2': iD.Relation({id: 'r2', members: [{id: '=', role: 'r1'}, {id: '-', role: 'r2'}]}) + }); + + graph = iD.actions.Join('-', '=')(graph); + + expect(graph.entity('r1').members).to.eql([{id: '-', role: 'r1'}]); + expect(graph.entity('r2').members).to.eql([{id: '-', role: 'r2'}]); + }); +}); From f5036db97894bdca33f02349e93dd55f8bfb195d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 1 Feb 2013 17:28:50 -0500 Subject: [PATCH 32/50] Start iD.operations.Merge (#435) It's currently limited to merging (joining) exactly two lines. Fixes #370. --- index.html | 1 + js/id/operations/merge.js | 27 +++++++++++++++++++++++++++ locale/en.js | 6 ++++++ test/index.html | 1 + 4 files changed, 35 insertions(+) create mode 100644 js/id/operations/merge.js diff --git a/index.html b/index.html index 07063cf81..d8be52dcd 100644 --- a/index.html +++ b/index.html @@ -114,6 +114,7 @@ + diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js new file mode 100644 index 000000000..08517e9b2 --- /dev/null +++ b/js/id/operations/merge.js @@ -0,0 +1,27 @@ +iD.operations.Merge = function(selection, context) { + var action = iD.actions.Join(selection[0], selection[1]); + + var operation = function() { + context.perform( + action, + t('operations.merge.annotation', {n: selection.length})); + }; + + operation.available = function() { + return selection.length === 2 && + _.all(selection, function (id) { + return context.geometry(id) === 'line'; + }); + }; + + operation.enabled = function() { + return action.enabled(context.graph()); + }; + + operation.id = "merge"; + operation.key = t('operations.merge.key'); + operation.title = t('operations.merge.title'); + operation.description = t('operations.merge.description'); + + return operation; +}; diff --git a/locale/en.js b/locale/en.js index 7d1bed947..9e4eb5b2a 100644 --- a/locale/en.js +++ b/locale/en.js @@ -83,6 +83,12 @@ locale.en = { key: "D", annotation: "Disconnected ways." }, + merge: { + title: "Merge", + description: "Merge these lines.", + key: "C", + annotation: "Merged {n} lines." + }, move: { title: "Move", description: "Move this to a different location.", diff --git a/test/index.html b/test/index.html index 524894c36..da3cd475c 100644 --- a/test/index.html +++ b/test/index.html @@ -110,6 +110,7 @@ + From 3e404df3394152c0234152af8b5061373b30f061 Mon Sep 17 00:00:00 2001 From: Tom MacWright Date: Fri, 1 Feb 2013 19:16:01 -0500 Subject: [PATCH 33/50] Add more translations, opacity to brightness YOU WIN @ansis --- js/id/ui/geocoder.js | 4 ++-- js/id/ui/inspector.js | 8 ++++---- js/id/ui/layerswitcher.js | 4 ++-- locale/en.js | 20 +++++++++++++------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/js/id/ui/geocoder.js b/js/id/ui/geocoder.js index d7de24b37..4aac10bf2 100644 --- a/js/id/ui/geocoder.js +++ b/js/id/ui/geocoder.js @@ -45,7 +45,7 @@ iD.ui.geocoder = function() { var button = selection.append('button') .attr('tabindex', -1) - .attr('title', 'Find A Location') + .attr('title', t('geocoder.find_location')) .html('') .on('click', toggle); @@ -53,7 +53,7 @@ iD.ui.geocoder = function() { gcForm.attr('class','content fillD map-overlay hide') .append('input') - .attr({ type: 'text', placeholder: 'find a place' }) + .attr({ type: 'text', placeholder: t('geocoder.find_a_place') }) .on('keydown', keydown); selection.call(clickoutside); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 9612cadd9..32e3fc857 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -35,7 +35,7 @@ iD.ui.inspector = function() { }); newTag.append('span').attr('class', 'icon icon-pre-text plus'); - newTag.append('span').attr('class','label').text('New tag') + newTag.append('span').attr('class','label').text(t('inspector.new_tag')); drawTags(entity.tags); @@ -63,7 +63,7 @@ iD.ui.inspector = function() { .attr('class', 'apply action') .on('click', apply); - inspectorButton.append('span').attr('class','label').text('Okay'); + inspectorButton.append('span').attr('class','label').text(t('okay')); var minorButtons = selection.append('div').attr('class','minor-buttons fl'); @@ -148,7 +148,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_combination')); + .text(t('inspector.no_documentation_combination')); } }); } else if (d.key) { @@ -166,7 +166,7 @@ iD.ui.inspector = function() { iD.ui.flash() .select('.content') .append('h3') - .text(t('no_documentation_key')); + .text(t('inspector.no_documentation_key')); } }); } diff --git a/js/id/ui/layerswitcher.js b/js/id/ui/layerswitcher.js index c64457ab8..5c4835757 100644 --- a/js/id/ui/layerswitcher.js +++ b/js/id/ui/layerswitcher.js @@ -59,7 +59,7 @@ iD.ui.layerswitcher = function(context) { .append('div') .attr('class', 'opacity-options-wrapper'); - opa.append('h4').text(t('layers')); + opa.append('h4').text(t('layerswitcher.layers')); opa.append('ul') .attr('class', 'opacity-options') @@ -68,7 +68,7 @@ iD.ui.layerswitcher = function(context) { .enter() .append('li') .attr('data-original-title', function(d) { - return t('percent_opacity', { opacity: (d * 100) }); + return t('layerswitcher.percent_brightness', { opacity: (d * 100) }); }) .on('click.set-opacity', function(d) { d3.select('#tile-g') diff --git a/locale/en.js b/locale/en.js index 9e4eb5b2a..866c7eca3 100644 --- a/locale/en.js +++ b/locale/en.js @@ -136,8 +136,11 @@ locale.en = { "layer_settings": "Layer Settings", - "no_documentation_combination": "This is no documentation available for this tag combination", - "no_documentation_key": "This is no documentation available for this key", + inspector: { + no_documentation_combination: "This is no documentation available for this tag combination", + no_documentation_key: "This is no documentation available for this key", + new_tag: "New Tag" + }, "view_on_osm": "View on OSM", @@ -145,13 +148,16 @@ locale.en = { "edit_tags": "Edit tags", - "find_location": "Find A Location", - "find_placeholder": "find a place", + geocoder: { + "find_location": "Find A Location", + "find_a_place": "find a place" + }, "description": "Description", "logout": "logout", - - "layers": "Layers", - "percent_opacity": "{opacity}% opacity" + layerswitcher: { + layers: "Layers", + percent_brightness: "{opacity}% brightness" + } }; From 09dac581bebf0db7698da32eec8930256dbbb882 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 12:55:06 -0500 Subject: [PATCH 34/50] Fix MoveWay mode (fixes #602) --- js/id/modes/move_way.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js index 30c473406..986eef5bd 100644 --- a/js/id/modes/move_way.js +++ b/js/id/modes/move_way.js @@ -47,7 +47,7 @@ iD.modes.MoveWay = function(context, wayId) { context.enter(iD.modes.Browse(context)); } - context.selection() + context.surface() .on('mousemove.move-way', move) .on('click.move-way', finish); @@ -63,7 +63,7 @@ iD.modes.MoveWay = function(context, wayId) { }; mode.exit = function() { - context.selection() + context.surface() .on('mousemove.move-way', null) .on('click.move-way', null); From ddc5e324f6cfa7fdcc25bfc679b0f25c70ad97b2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 18:28:44 -0500 Subject: [PATCH 35/50] Extract iD.Difference iD.Difference represents the difference between two graphs. It knows how to calculate the set of entities that were created, modified, or deleted, and also contains the logic for recursively extending a difference to the complete set of entities that will require a redraw, taking into account child and parent relationships. Additionally, all history mutators now return a difference. --- index.html | 1 + js/id/graph/difference.js | 113 +++++++++++++++++++ js/id/graph/graph.js | 60 ---------- js/id/graph/history.js | 44 ++++---- js/id/renderer/map.js | 33 +----- test/index.html | 2 + test/index_packaged.html | 1 + test/spec/graph/difference.js | 202 ++++++++++++++++++++++++++++++++++ test/spec/graph/graph.js | 82 -------------- test/spec/graph/history.js | 41 +++++-- 10 files changed, 375 insertions(+), 204 deletions(-) create mode 100644 js/id/graph/difference.js create mode 100644 test/spec/graph/difference.js diff --git a/index.html b/index.html index d8be52dcd..f9b627c16 100644 --- a/index.html +++ b/index.html @@ -119,6 +119,7 @@ + diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js new file mode 100644 index 000000000..8bea5664c --- /dev/null +++ b/js/id/graph/difference.js @@ -0,0 +1,113 @@ +/* + iD.Difference represents the difference between two graphs. + It knows how to calculate the set of entities that were + created, modified, or deleted, and also contains the logic + for recursively extending a difference to the complete set + of entities that will require a redraw, taking into account + child and parent relationships. + */ +iD.Difference = function (base, head) { + var changes = {}, length = 0; + + _.each(head.entities, function(h, id) { + var b = base.entities[id]; + if (h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + _.each(base.entities, function(b, id) { + var h = head.entities[id]; + if (!changes[id] && h !== b) { + changes[id] = {base: b, head: h}; + length++; + } + }); + + var difference = {}; + + difference.length = function () { + return length; + }; + + difference.changes = function() { + return changes; + }; + + difference.modified = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.created = function() { + var result = []; + _.each(changes, function(change) { + if (!change.base && change.head) result.push(change.head); + }); + return result; + }; + + difference.deleted = function() { + var result = []; + _.each(changes, function(change) { + if (change.base && !change.head) result.push(change.base); + }); + return result; + }; + + difference.complete = function(extent) { + var result = {}, id, change; + + function addParents(parents) { + for (var i = 0; i < parents.length; i++) { + var parent = parents[i]; + + if (parent.id in result) + continue; + + result[parent.id] = parent; + addParents(head.parentRelations(parent)); + } + } + + for (id in changes) { + change = changes[id]; + + var h = change.head, + b = change.base, + entity = h || b; + + if (extent && !entity.intersects(extent, h ? head : base)) + continue; + + result[id] = h; + + if (entity.type === 'way') { + var nh = h ? h.nodes : [], + nb = b ? b.nodes : [], + diff; + + diff = _.difference(nh, nb); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + + diff = _.difference(nb, nh); + for (var i = 0; i < diff.length; i++) { + result[diff[i]] = head.entity(diff[i]); + } + } + + addParents(head.parentWays(entity)); + addParents(head.parentRelations(entity)); + } + + return result; + }; + + return difference; +}; diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js index a19bfe094..2692aeba6 100644 --- a/js/id/graph/graph.js +++ b/js/id/graph/graph.js @@ -232,65 +232,5 @@ iD.Graph.prototype = { } } return items; - }, - - difference: function (graph) { - - function diff(a, b) { - var result = [], - keys = Object.keys(a.entities), - entity, oldentity, id, i; - - for (i = 0; i < keys.length; i++) { - id = keys[i]; - entity = a.entities[id]; - oldentity = b.entities[id]; - if (entity !== oldentity) { - - // maybe adding affected children better belongs in renderer/map.js? - if (entity && entity.type === 'way' && - oldentity && oldentity.type === 'way') { - result = result - .concat(_.difference(entity.nodes, oldentity.nodes)) - .concat(_.difference(oldentity.nodes, entity.nodes)); - - } else if (entity && entity.type === 'way') { - result = result.concat(entity.nodes); - - } else if (oldentity && oldentity.type === 'way') { - result = result.concat(oldentity.nodes); - } - - result.push(id); - } - } - return result; - } - - return _.unique(diff(this, graph).concat(diff(graph, this)).sort()); - }, - - modified: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && base[id]) result.push(id); - }); - return result; - }, - - created: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (entity && !base[id]) result.push(id); - }); - return result; - }, - - deleted: function() { - var result = [], base = this.base().entities; - _.each(this.entities, function(entity, id) { - if (!entity && base[id]) result.push(id); - }); - return result; } }; diff --git a/js/id/graph/history.js b/js/id/graph/history.js index 42e1b1604..cbadbf87f 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -21,7 +21,9 @@ iD.History = function() { } function change(previous) { - dispatch.change(history.graph().difference(previous)); + var difference = iD.Difference(previous, history.graph()); + dispatch.change(difference); + return difference; } var history = { @@ -42,7 +44,7 @@ iD.History = function() { stack.push(perform(arguments)); index++; - change(previous); + return change(previous); }, replace: function () { @@ -51,7 +53,7 @@ iD.History = function() { // assert(index == stack.length - 1) stack[index] = perform(arguments); - change(previous); + return change(previous); }, pop: function () { @@ -60,7 +62,7 @@ iD.History = function() { if (index > 0) { index--; stack.pop(); - change(previous); + return change(previous); } }, @@ -80,7 +82,7 @@ iD.History = function() { } dispatch.undone(); - change(previous); + return change(previous); }, redo: function () { @@ -92,7 +94,7 @@ iD.History = function() { } dispatch.redone(); - change(previous); + return change(previous); }, undoAnnotation: function () { @@ -111,31 +113,27 @@ iD.History = function() { } }, - changes: function () { - var initial = stack[0].graph, - current = stack[index].graph; + difference: function () { + var base = stack[0].graph, + head = stack[index].graph; + return iD.Difference(base, head); + }, + changes: function () { + var difference = history.difference(); return { - modified: current.modified().map(function (id) { - return current.entity(id); - }), - created: current.created().map(function (id) { - return current.entity(id); - }), - deleted: current.deleted().map(function (id) { - return initial.entity(id); - }) - }; + modified: difference.modified(), + created: difference.created(), + deleted: difference.deleted() + } }, hasChanges: function() { - return !!this.numChanges(); + return this.difference().length() > 0; }, numChanges: function() { - return d3.sum(d3.values(this.changes()).map(function(c) { - return c.length; - })); + return this.difference().length(); }, imagery_used: function(source) { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 1f22a7c81..839aa18a3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -64,44 +64,19 @@ iD.Map = function(context) { extent = map.extent(), graph = context.graph(); - function addParents(parents) { - for (var i = 0; i < parents.length; i++) { - var parent = parents[i]; - if (only[parent.id] === undefined) { - only[parent.id] = parent; - addParents(graph.parentRelations(parent)); - } - } - } - if (!difference) { all = graph.intersects(extent); filter = d3.functor(true); } else { - var only = {}; - - for (var j = 0; j < difference.length; j++) { - var id = difference[j], - entity = graph.entity(id); - - // Even if the entity is false (deleted), it needs to be - // removed from the surface - only[id] = entity; - - if (entity && entity.intersects(extent, graph)) { - addParents(graph.parentWays(only[id])); - addParents(graph.parentRelations(only[id])); - } - } - - all = _.compact(_.values(only)); + var complete = difference.complete(extent); + all = _.compact(_.values(complete)); filter = function(d) { if (d.type === 'midpoint') { for (var i = 0; i < d.ways.length; i++) { - if (d.ways[i].id in only) return true; + if (d.ways[i].id in complete) return true; } } else { - return d.id in only; + return d.id in complete; } }; } diff --git a/test/index.html b/test/index.html index da3cd475c..b6d925923 100644 --- a/test/index.html +++ b/test/index.html @@ -115,6 +115,7 @@ + @@ -164,6 +165,7 @@ + diff --git a/test/index_packaged.html b/test/index_packaged.html index 948ed0ae1..ef91e2823 100644 --- a/test/index_packaged.html +++ b/test/index_packaged.html @@ -55,6 +55,7 @@ + diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js new file mode 100644 index 000000000..f12a77254 --- /dev/null +++ b/test/spec/graph/difference.js @@ -0,0 +1,202 @@ +describe("iD.Difference", function () { + describe("#changes", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("includes undone created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: n1, head: n2}}); + }); + + it("includes undone modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.update(), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: n2, head: n1}}); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({n: {base: node, head: undefined}}); + }); + + it("includes undone deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(head, base); + expect(diff.changes()).to.eql({n: {base: undefined, head: node}}); + }); + + it("doesn't include created entities that were subsequently deleted", function () { + var node = iD.Node(), + base = iD.Graph(), + head = base.replace(node).remove(node), + diff = iD.Difference(base, head); + expect(diff.changes()).to.eql({}); + }); + }); + + describe("#created", function () { + it("returns an array of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.created()).to.eql([node]); + }); + }); + + describe("#modified", function () { + it("returns an array of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.modified()).to.eql([n2]); + }); + }); + + describe("#deleted", function () { + it("returns an array of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.deleted()).to.eql([node]); + }); + }); + + describe("#complete", function () { + it("includes created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(node); + }); + + it("includes modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.complete()['n']).to.equal(n2); + }); + + it("includes deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.complete()).to.eql({n: undefined}); + }); + + it("includes nodes added to a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1']}), + w2 = w1.addNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes nodes removed from a way", function () { + var n1 = iD.Node({id: 'n1'}), + n2 = iD.Node({id: 'n2'}), + w1 = iD.Way({id: 'w', nodes: ['n1', 'n2']}), + w2 = w1.removeNode('n2'), + base = iD.Graph([n1, n2, w1]), + head = base.replace(w2), + diff = iD.Difference(base, head); + + expect(diff.complete()['n2']).to.equal(n2); + }); + + it("includes parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + base = iD.Graph([n1, way]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['w']).to.equal(way); + }); + + it("includes parent relations of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel = iD.Relation({id: 'r', members: [{id: 'n'}]}), + base = iD.Graph([n1, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("includes parent relations of modified entities, recursively", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([n1, rel1, rel2]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r2']).to.equal(rel2); + }); + + it("includes parent relations of parent ways of modified nodes", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + way = iD.Way({id: 'w', nodes: ['n']}), + rel = iD.Relation({id: 'r', members: [{id: 'w'}]}), + base = iD.Graph([n1, way, rel]), + head = base.replace(n2), + diff = iD.Difference(base, head); + + expect(diff.complete()['r']).to.equal(rel); + }); + + it("copes with recursive relations", function () { + var node = iD.Node({id: 'n'}), + rel1 = iD.Relation({id: 'r1', members: [{id: 'n'}, {id: 'r2'}]}), + rel2 = iD.Relation({id: 'r2', members: [{id: 'r1'}]}), + base = iD.Graph([node, rel1, rel2]), + head = base.replace(node.move([1, 2])), + diff = iD.Difference(base, head); + + expect(diff.complete()).to.be.ok; + }); + + it("limits changes to those within a given extent"); + }); +}); diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js index 05bcbbe89..a357ff360 100644 --- a/test/spec/graph/graph.js +++ b/test/spec/graph/graph.js @@ -333,86 +333,4 @@ describe('iD.Graph', function() { expect(graph.childNodes(way)).to.eql([node]); }); }); - - describe("#difference", function () { - it("returns an Array of ids of changed entities", function () { - var initial = iD.Node({id: "n1"}), - updated = initial.update({}), - created = iD.Node(), - deleted = iD.Node({id: 'n2'}), - graph1 = iD.Graph([initial, deleted]), - graph2 = graph1.replace(updated).replace(created).remove(deleted); - expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]); - }); - - - it("includes created entities, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(), - graph2 = graph1.replace(node); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes entities changed from base, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes already changed entities that were updated, and reverse", function () { - var node = iD.Node(), - graph1 = iD.Graph().replace(node), - graph2 = graph1.replace(node.update()); - expect(graph2.difference(graph1)).to.eql([node.id]); - expect(graph1.difference(graph2)).to.eql([node.id]); - }); - - it("includes affected child nodes", function () { - var n = iD.Node({id: 'n'}), - n2 = iD.Node({id: 'n2'}), - w1 = iD.Way({id: 'w1', nodes: ['n']}), - w1_ = iD.Way({id: 'w1', nodes: ['n', 'n2']}), - graph1 = iD.Graph([n, n2, w1]), - graph2 = graph1.replace(w1_); - expect(graph2.difference(graph1)).to.eql(['n2', 'w1']); - expect(graph1.difference(graph2)).to.eql(['n2', 'w1']); - }); - - }); - - describe("#modified", function () { - it("returns an Array of ids of modified entities", function () { - var node = iD.Node({id: 'n1'}), - node_ = iD.Node({id: 'n1'}), - graph = iD.Graph([node]).replace(node_); - expect(graph.modified()).to.eql([node.id]); - }); - }); - - describe("#created", function () { - it("returns an Array of ids of created entities", function () { - var node1 = iD.Node({id: 'n-1'}), - node2 = iD.Node({id: 'n2'}), - graph = iD.Graph([node2]).replace(node1); - expect(graph.created()).to.eql([node1.id]); - }); - }); - - describe("#deleted", function () { - it("returns an Array of ids of deleted entities", function () { - var node1 = iD.Node({id: "n1"}), - node2 = iD.Node(), - graph = iD.Graph([node1, node2]).remove(node1); - expect(graph.deleted()).to.eql([node1.id]); - }); - - it("doesn't include created entities that were subsequently deleted", function () { - var node = iD.Node(), - graph = iD.Graph().replace(node).remove(node); - expect(graph.deleted()).to.eql([]); - }); - }); }); diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index dc685b446..3f28f437e 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -14,6 +14,10 @@ describe("iD.History", function () { }); describe("#perform", function () { + it("returns a difference", function () { + expect(history.perform(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.perform(function (graph) { return graph.replace(node); }); @@ -27,8 +31,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.perform(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.perform(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -42,6 +46,10 @@ describe("iD.History", function () { }); describe("#replace", function () { + it("returns a difference", function () { + expect(history.replace(action).changes()).to.eql({}); + }); + it("updates the graph", function () { var node = iD.Node(); history.replace(function (graph) { return graph.replace(node); }); @@ -56,8 +64,8 @@ describe("iD.History", function () { it("emits a change event", function () { history.on('change', spy); - history.replace(action); - expect(spy).to.have.been.calledWith([]); + var difference = history.replace(action); + expect(spy).to.have.been.calledWith(difference); }); it("performs multiple actions", function () { @@ -71,6 +79,11 @@ describe("iD.History", function () { }); describe("#pop", function () { + it("returns a difference", function () { + history.perform(action, "annotation"); + expect(history.pop().changes()).to.eql({}); + }); + it("updates the graph", function () { history.perform(action, "annotation"); history.pop(); @@ -86,12 +99,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.pop(); - expect(spy).to.have.been.calledWith([]); + var difference = history.pop(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#undo", function () { + it("returns a difference", function () { + expect(history.undo().changes()).to.eql({}); + }); + it("pops the undo stack", function () { history.perform(action, "annotation"); history.undo(); @@ -121,12 +138,16 @@ describe("iD.History", function () { it("emits a change event", function () { history.perform(action); history.on('change', spy); - history.undo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.undo(); + expect(spy).to.have.been.calledWith(difference); }); }); describe("#redo", function () { + it("returns a difference", function () { + expect(history.redo().changes()).to.eql({}); + }); + it("emits an redone event", function () { history.perform(action); history.undo(); @@ -139,8 +160,8 @@ describe("iD.History", function () { history.perform(action); history.undo(); history.on('change', spy); - history.redo(); - expect(spy).to.have.been.calledWith([]); + var difference = history.redo(); + expect(spy).to.have.been.calledWith(difference); }); }); From 11d723819dc222e946074b70a5980c59c9890c1d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:13:53 -0500 Subject: [PATCH 36/50] Difference#extantIDs --- js/id/graph/difference.js | 8 ++++++++ test/spec/graph/difference.js | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/js/id/graph/difference.js b/js/id/graph/difference.js index 8bea5664c..e2159271a 100644 --- a/js/id/graph/difference.js +++ b/js/id/graph/difference.js @@ -35,6 +35,14 @@ iD.Difference = function (base, head) { return changes; }; + difference.extantIDs = function() { + var result = []; + _.each(changes, function(change, id) { + if (change.head) result.push(id); + }); + return result; + }; + difference.modified = function() { var result = []; _.each(changes, function(change) { diff --git a/test/spec/graph/difference.js b/test/spec/graph/difference.js index f12a77254..c9647bbea 100644 --- a/test/spec/graph/difference.js +++ b/test/spec/graph/difference.js @@ -59,6 +59,33 @@ describe("iD.Difference", function () { }); }); + describe("#extantIDs", function () { + it("includes the ids of created entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph(), + head = base.replace(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("includes the ids of modified entities", function () { + var n1 = iD.Node({id: 'n'}), + n2 = n1.move([1, 2]), + base = iD.Graph([n1]), + head = base.replace(n2), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql(['n']); + }); + + it("omits the ids of deleted entities", function () { + var node = iD.Node({id: 'n'}), + base = iD.Graph([node]), + head = base.remove(node), + diff = iD.Difference(base, head); + expect(diff.extantIDs()).to.eql([]); + }); + }); + describe("#created", function () { it("returns an array of created entities", function () { var node = iD.Node({id: 'n'}), From 1de05b518dcec864ca39429a3e0a394b31958763 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:15:28 -0500 Subject: [PATCH 37/50] Select result when splitting or joining ways Fixes #601. Fixes #603. --- js/id/operations/merge.js | 6 +++--- js/id/operations/split.js | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/js/id/operations/merge.js b/js/id/operations/merge.js index 08517e9b2..d02222368 100644 --- a/js/id/operations/merge.js +++ b/js/id/operations/merge.js @@ -2,9 +2,9 @@ iD.operations.Merge = function(selection, context) { var action = iD.actions.Join(selection[0], selection[1]); var operation = function() { - context.perform( - action, - t('operations.merge.annotation', {n: selection.length})); + var annotation = t('operations.merge.annotation', {n: selection.length}), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 07d7d56ff..f49f4b9ed 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -3,7 +3,9 @@ iD.operations.Split = function(selection, context) { action = iD.actions.Split(entityId); var operation = function() { - context.perform(action, t('operations.split.annotation')); + var annotation = t('operations.split.annotation'), + difference = context.perform(action, annotation); + context.enter(iD.modes.Select(context, difference.extantIDs())); }; operation.available = function() { From fc00f154a913788b5d16fed1a917a4c3fce80491 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:44:47 -0500 Subject: [PATCH 38/50] Dispatch a change event on merge --- js/id/graph/history.js | 2 ++ js/id/renderer/map.js | 1 - test/spec/graph/history.js | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/js/id/graph/history.js b/js/id/graph/history.js index cbadbf87f..3cc1ace01 100644 --- a/js/id/graph/history.js +++ b/js/id/graph/history.js @@ -35,6 +35,8 @@ iD.History = function() { for (var i = 0; i < stack.length; i++) { stack[i].graph.rebase(entities); } + + dispatch.change(); }, perform: function () { diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index 839aa18a3..c3b528da3 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -102,7 +102,6 @@ iD.Map = function(context) { function connectionLoad(err, result) { context.history().merge(result); - redraw(Object.keys(result)); } function zoomPan() { diff --git a/test/spec/graph/history.js b/test/spec/graph/history.js index 3f28f437e..0ecbed5d3 100644 --- a/test/spec/graph/history.js +++ b/test/spec/graph/history.js @@ -13,6 +13,20 @@ describe("iD.History", function () { }); }); + describe("#merge", function () { + it("merges the entities into all graph versions", function () { + var n = iD.Node({id: 'n'}); + history.merge({n: n}); + expect(history.graph().entity('n')).to.equal(n); + }); + + it("emits a change event", function () { + history.on('change', spy); + history.merge({}); + expect(spy).to.have.been.called; + }); + }); + describe("#perform", function () { it("returns a difference", function () { expect(history.perform(action).changes()).to.eql({}); From ec602a7db7ef139baa1614bcb5c9c8cb0ec43f54 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:46:59 -0500 Subject: [PATCH 39/50] Hook up connection and history in context --- js/id/id.js | 4 ++++ js/id/renderer/map.js | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/js/id/id.js b/js/id/id.js index 358610525..56d3e481a 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -16,6 +16,10 @@ window.iD = function () { // the connection requires .storage() to be available on calling. var connection = iD.Connection(context); + connection.on('load.context', function (err, result) { + history.merge(result); + }); + /* Straight accessors. Avoid using these if you can. */ context.ui = function() { return ui; }; context.connection = function() { return connection; }; diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js index c3b528da3..b2561f21f 100644 --- a/js/id/renderer/map.js +++ b/js/id/renderer/map.js @@ -26,9 +26,6 @@ iD.Map = function(context) { surface, tilegroup; function map(selection) { - context.connection() - .on('load.tile', connectionLoad); - context.history() .on('change.map', redraw); @@ -100,10 +97,6 @@ iD.Map = function(context) { surface.selectAll('.layer *').remove(); } - function connectionLoad(err, result) { - context.history().merge(result); - } - function zoomPan() { if (d3.event && d3.event.sourceEvent.type === 'dblclick') { if (!dblclickEnabled) { From 80a5a083b0affb576065facd7260073835ff4d0d Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:55:52 -0500 Subject: [PATCH 40/50] Remove unused --- js/id/graph/entity.js | 11 +--------- test/spec/graph/entity.js | 42 ------------------------------------- test/spec/graph/node.js | 9 -------- test/spec/graph/relation.js | 9 -------- test/spec/graph/way.js | 9 -------- 5 files changed, 1 insertion(+), 79 deletions(-) diff --git a/js/id/graph/entity.js b/js/id/graph/entity.js index ae612c268..bce9675ee 100644 --- a/js/id/graph/entity.js +++ b/js/id/graph/entity.js @@ -43,7 +43,6 @@ iD.Entity.prototype = { if (!this.id && this.type) { this.id = iD.Entity.id(this.type); - this._updated = true; } if (iD.debug) { @@ -63,7 +62,7 @@ iD.Entity.prototype = { }, update: function(attrs) { - return iD.Entity(this, attrs, {_updated: true}); + return iD.Entity(this, attrs); }, mergeTags: function(tags) { @@ -80,14 +79,6 @@ iD.Entity.prototype = { return this.update({tags: merged}); }, - created: function() { - return this._updated && this.osmId().charAt(0) === '-'; - }, - - modified: function() { - return this._updated && this.osmId().charAt(0) !== '-'; - }, - intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, diff --git a/test/spec/graph/entity.js b/test/spec/graph/entity.js index 088ab5516..f2ac85f17 100644 --- a/test/spec/graph/entity.js +++ b/test/spec/graph/entity.js @@ -52,12 +52,6 @@ describe('iD.Entity', function () { expect(e.id).to.equal('w1'); }); - it("tags the entity as updated", function () { - var tags = {foo: 'bar'}, - e = iD.Entity().update({tags: tags}); - expect(e._updated).to.to.be.true; - }); - it("doesn't modify the input", function () { var attrs = {tags: {foo: 'bar'}}, e = iD.Entity().update(attrs); @@ -104,42 +98,6 @@ describe('iD.Entity', function () { }); }); - describe("#created", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).created()).not.to.be.ok; - }); - - it("returns falsy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).created()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).created()).to.be.ok; - }); - }); - - describe("#modified", function () { - it("returns falsy by default", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns falsy for an unmodified Entity", function () { - expect(iD.Entity({id: 'w1234'}).modified()).not.to.be.ok; - }); - - it("returns truthy for a modified Entity with positive ID", function () { - expect(iD.Entity({id: 'w1234'}).update({}).modified()).to.be.ok; - }); - - it("returns falsy for a modified Entity with negative ID", function () { - expect(iD.Entity({id: 'w-1234'}).update({}).modified()).not.to.be.ok; - }); - }); - describe("#intersects", function () { it("returns true for a way with a node within the given extent", function () { var node = iD.Node({loc: [0, 0]}), diff --git a/test/spec/graph/node.js b/test/spec/graph/node.js index ad149261e..71a2c5e95 100644 --- a/test/spec/graph/node.js +++ b/test/spec/graph/node.js @@ -4,15 +4,6 @@ describe('iD.Node', function () { expect(iD.Node().type).to.equal("node"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Node().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok; - expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok; - }); - it("defaults tags to an empty object", function () { expect(iD.Node().tags).to.eql({}); }); diff --git a/test/spec/graph/relation.js b/test/spec/graph/relation.js index 8edf9c1e7..6e00dcacb 100644 --- a/test/spec/graph/relation.js +++ b/test/spec/graph/relation.js @@ -10,15 +10,6 @@ describe('iD.Relation', function () { expect(iD.Relation().type).to.equal("relation"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Relation().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok; - expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok; - }); - it("defaults members to an empty array", function () { expect(iD.Relation().members).to.eql([]); }); diff --git a/test/spec/graph/way.js b/test/spec/graph/way.js index ec41edec6..6b6b1543f 100644 --- a/test/spec/graph/way.js +++ b/test/spec/graph/way.js @@ -10,15 +10,6 @@ describe('iD.Way', function() { expect(iD.Way().type).to.equal("way"); }); - it("returns a created Entity if no ID is specified", function () { - expect(iD.Way().created()).to.be.ok; - }); - - it("returns an unmodified Entity if ID is specified", function () { - expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok; - expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok; - }); - it("defaults nodes to an empty array", function () { expect(iD.Way().nodes).to.eql([]); }); From b08722fd3fb3cbfdf19af48926d4b3185cbbcdf2 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 19:59:05 -0500 Subject: [PATCH 41/50] Pass just the projection to Circularize --- js/id/actions/circularize.js | 6 +++--- js/id/operations/circularize.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index edbd0d8b3..d844134eb 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -1,4 +1,4 @@ -iD.actions.Circularize = function(wayId, map) { +iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), @@ -6,7 +6,7 @@ iD.actions.Circularize = function(wayId, map) { tags = {}, key, role; var points = nodes.map(function(n) { - return map.projection(n.loc); + return projection(n.loc); }), centroid = d3.geom.polygon(points).centroid(), radius = d3.median(points, function(p) { @@ -15,7 +15,7 @@ iD.actions.Circularize = function(wayId, map) { circular_nodes = []; for (var i = 0; i < 12; i++) { - circular_nodes.push(iD.Node({ loc: map.projection.invert([ + circular_nodes.push(iD.Node({ loc: projection.invert([ centroid[0] + Math.cos((i / 12) * Math.PI * 2) * radius, centroid[1] + Math.sin((i / 12) * Math.PI * 2) * radius]) })); diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index 419a6e9eb..2680da4ec 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -1,6 +1,6 @@ iD.operations.Circularize = function(selection, context) { var entityId = selection[0], - action = iD.actions.Circularize(entityId, context.map()); + action = iD.actions.Circularize(entityId, context.projection); var operation = function() { var annotation = t('operations.circularize.annotation.' + context.geometry(entityId)); From 9e878b1cf927817c4cf7e619bcf5722278bd72e8 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:00:05 -0500 Subject: [PATCH 42/50] Remove redundant conditional --- js/id/validate.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/js/id/validate.js b/js/id/validate.js index aa7d8771b..6329b626f 100644 --- a/js/id/validate.js +++ b/js/id/validate.js @@ -15,31 +15,29 @@ iD.validate = function(changes, graph) { if (tags.building && tags.building === 'yes') return 'building=yes'; } - if (changes.created.length) { - for (var i = 0; i < changes.created.length; i++) { - change = changes.created[i]; + for (var i = 0; i < changes.created.length; i++) { + change = changes.created[i]; - if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { - warnings.push({ - message: t('validations.untagged_point'), - entity: change - }); - } + if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) { + warnings.push({ + message: t('validations.untagged_point'), + entity: change + }); + } - if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_line'), entity: change }); - } + if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_line'), entity: change }); + } - if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { - warnings.push({ message: t('validations.untagged_area'), entity: change }); - } + if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) { + warnings.push({ message: t('validations.untagged_area'), entity: change }); + } - if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { - warnings.push({ - message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), - entity: change - }); - } + if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) { + warnings.push({ + message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}), + entity: change + }); } } From 0b3e0fb3db7556438da8d560d9fab7dffd829e7f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 20:29:01 -0500 Subject: [PATCH 43/50] Use iD.actions.DeleteNode when removing nodes They need to be removed from any parent relations. Also, make sure to uniq child nodes, otherwise the start/end node over-contributes to the centroid calculation. This action needs tests. --- js/id/actions/circularize.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index d844134eb..2c72047d9 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,7 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = graph.childNodes(way), + nodes = _.uniq(graph.childNodes(way)), tags = {}, key, role; var points = nodes.map(function(n) { @@ -36,8 +36,6 @@ iD.actions.Circularize = function(wayId, projection) { circular_nodes.splice(closest, 1, nodes[i]); if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); - } else { - graph = graph.remove(nodes[i]); } } @@ -45,9 +43,16 @@ iD.actions.Circularize = function(wayId, projection) { graph = graph.replace(circular_nodes[i]); } - return graph.replace(way.update({ - nodes: _.pluck(circular_nodes, 'id') - })); + var ids = _.pluck(circular_nodes, 'id'), + difference = _.difference(_.uniq(way.nodes), ids); + + graph = graph.replace(way.update({nodes: ids})); + + for (i = 0; i < difference.length; i++) { + graph = iD.actions.DeleteNode(difference[i])(graph); + } + + return graph; }; action.enabled = function(graph) { From 33f5db4d96b317be8c24d1b2db52405d2063d584 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:17:43 -0500 Subject: [PATCH 44/50] Fix hash with selected id --- js/id/behavior/hash.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/hash.js b/js/id/behavior/hash.js index 9056db393..c8762fb13 100644 --- a/js/id/behavior/hash.js +++ b/js/id/behavior/hash.js @@ -45,7 +45,7 @@ iD.behavior.Hash = function(context) { context.map().on('drawn.hash', function() { if (!context.entity(id)) return; selectoff(); - context.enter(iD.modes.Select([id])); + context.enter(iD.modes.Select(context, [id])); }); context.on('enter.hash', function() { From 441be74539c1b78d95b143109add18ab48504fba Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sat, 2 Feb 2013 21:26:29 -0500 Subject: [PATCH 45/50] Simplify --- js/id/actions/circularize.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/id/actions/circularize.js b/js/id/actions/circularize.js index 2c72047d9..532dcdb45 100644 --- a/js/id/actions/circularize.js +++ b/js/id/actions/circularize.js @@ -2,8 +2,7 @@ iD.actions.Circularize = function(wayId, projection) { var action = function(graph) { var way = graph.entity(wayId), - nodes = _.uniq(graph.childNodes(way)), - tags = {}, key, role; + nodes = _.uniq(graph.childNodes(way)); var points = nodes.map(function(n) { return projection(n.loc); @@ -21,8 +20,6 @@ iD.actions.Circularize = function(wayId, projection) { })); } - circular_nodes.push(circular_nodes[0]); - for (i = 0; i < nodes.length; i++) { if (graph.parentWays(nodes[i]).length > 1) { var closest, closest_dist = Infinity, dist; @@ -34,8 +31,6 @@ iD.actions.Circularize = function(wayId, projection) { } } circular_nodes.splice(closest, 1, nodes[i]); - if (closest === 0) circular_nodes.splice(circular_nodes.length - 1, 1, nodes[i]); - else if (closest === circular_nodes.length - 1) circular_nodes.splice(0, 1, nodes[i]); } } @@ -46,6 +41,8 @@ iD.actions.Circularize = function(wayId, projection) { var ids = _.pluck(circular_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++) { From 13b0b540a7cbc15d0c67f067f72a3f3f16092e62 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Sat, 2 Feb 2013 23:47:29 -0500 Subject: [PATCH 46/50] Don't snap to midpoints, snap to their parent way --- js/id/behavior/draw_way.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 9efcd5bb9..f724613da 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -16,10 +16,13 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { function move(datum) { var loc = context.map().mouseCoordinates(); - if (datum.type === 'node' || datum.type === 'midpoint') { + if (datum.type === 'node') { loc = datum.loc; - } else if (datum.type === 'way') { - loc = iD.geo.chooseIndex(datum, d3.mouse(context.surface().node()), context).loc; + } else if (datum.type === 'midpoint' || datum.type === 'way') { + var way = datum.type === 'way' ? + datum : + baseGraph.entity(datum.ways[0].id); + loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; } context.replace(iD.actions.MoveNode(nodeId, loc)); From 3000bc89ceeb9693a8ab258a103e2edea46a1475 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 08:49:43 -0800 Subject: [PATCH 47/50] shadow should be above fill Makes it much easier to select lines that are within areas. --- js/id/svg/surface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js index 7561802e5..a11f8fbae 100644 --- a/js/id/svg/surface.js +++ b/js/id/svg/surface.js @@ -3,7 +3,7 @@ iD.svg.Surface = function() { selection.append('defs'); var layers = selection.selectAll('.layer') - .data(['shadow', 'fill', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); + .data(['fill', 'shadow', 'casing', 'stroke', 'text', 'hit', 'halo', 'label']); layers.enter().append('g') .attr('class', function(d) { return 'layer layer-' + d; }); From 03800ec8419b01ede93d1f2d509572ebc730a8fd Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Sun, 3 Feb 2013 13:21:27 -0800 Subject: [PATCH 48/50] Don't force point to area boundary (fixes #614) --- css/map.css | 1 + 1 file changed, 1 insertion(+) diff --git a/css/map.css b/css/map.css index 8ad55c790..34d56f0a9 100644 --- a/css/map.css +++ b/css/map.css @@ -725,6 +725,7 @@ text.point { } /* Ensure drawing doesn't interact with area fills. */ +.mode-add-point .area, .mode-draw-line .area, .mode-draw-area .area, .mode-add-line .area, From e0d4f5e87d19a1e9d19a4bb70f2959688c6d4b5f Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Mon, 4 Feb 2013 07:13:49 -0800 Subject: [PATCH 49/50] Use default shape-rendering On Firefox, optimizeSpeed = aliased. Fixes #592. --- css/map.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/css/map.css b/css/map.css index 34d56f0a9..ab2a352c9 100644 --- a/css/map.css +++ b/css/map.css @@ -150,11 +150,6 @@ path.stroke { stroke-width: 2; } -path.stroke, -path.casing { - shape-rendering: optimizeSpeed; -} - path.shadow { pointer-events: stroke; stroke-width: 10; From 240d83c1fd9b6cca93314ace4a591c81cc9f62b8 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 4 Feb 2013 10:54:39 -0500 Subject: [PATCH 50/50] Fix #623, get enitity from current graph --- js/id/behavior/draw_way.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index f724613da..3089bd6ee 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -21,7 +21,7 @@ iD.behavior.DrawWay = function(context, wayId, index, mode, baseGraph) { } else if (datum.type === 'midpoint' || datum.type === 'way') { var way = datum.type === 'way' ? datum : - baseGraph.entity(datum.ways[0].id); + context.entity(datum.ways[0].id); loc = iD.geo.chooseIndex(way, d3.mouse(context.surface().node()), context).loc; }