diff --git a/Makefile b/Makefile
index ddeef62b9..1db4f2efb 100644
--- a/Makefile
+++ b/Makefile
@@ -23,6 +23,7 @@ all: \
js/lib/jxon.js \
js/lib/lodash.js \
js/lib/ohauth.js \
+ js/lib/rtree.js \
js/lib/sha.js \
js/id/start.js \
js/id/id.js \
@@ -40,13 +41,14 @@ all: \
js/id/modes/*.js \
js/id/operations.js \
js/id/operations/*.js \
- js/id/controller/*.js \
+ js/id/controller.js \
js/id/graph/*.js \
js/id/renderer/*.js \
js/id/svg.js \
js/id/svg/*.js \
js/id/ui.js \
js/id/ui/*.js \
+ js/id/validate.js \
js/id/end.js \
locale/locale.js \
locale/en.js
diff --git a/css/app.css b/css/app.css
index c979ea095..b0507e02f 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1175,7 +1175,8 @@ a.success-action {
text-align:center;
}
-.notice .notice-inner {
+.notice .zoom-to {
+ width:100%;
height: 40px;
border-radius: 5px;
line-height: 40px;
@@ -1184,12 +1185,19 @@ a.success-action {
opacity: 0.9;
}
-.notice .notice-inner .zoom-to {
- width:40px;
- height:40px;
+.notice .zoom-to:hover {
+ background: #bde5aa;
+}
+
+.notice .zoom-to .icon {
+ margin-top:10px;
margin-right:10px;
}
+.icon.zoom-in-invert {
+ background-position: -240px -40px;
+}
+
/* Tooltips
------------------------------------------------------- */
diff --git a/css/map.css b/css/map.css
index 7b3038d6c..8ad55c790 100644
--- a/css/map.css
+++ b/css/map.css
@@ -16,7 +16,7 @@ g.point .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
-.behavior-hover g.point.hover .shadow {
+.behavior-hover g.point.hover:not(.selected) .shadow {
fill: #E96666;
fill-opacity: 0.3;
}
@@ -106,7 +106,7 @@ g.vertex .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
-.behavior-hover g.vertex.hover .shadow {
+.behavior-hover g.vertex.hover:not(.selected) .shadow {
fill: #E96666;
fill-opacity: 0.3;
}
@@ -120,7 +120,7 @@ g.vertex.selected .shadow {
g.midpoint .fill {
fill:#aaa;
}
-.behavior-hover g.midpoint .fill.hover {
+.behavior-hover g.midpoint .fill.hover:not(.selected) {
fill:#fff;
stroke:#000;
}
@@ -133,7 +133,7 @@ g.midpoint .shadow {
transition: transform 100ms linear;
-moz-transition: fill 100ms linear;
}
-.behavior-hover g.midpoint .shadow.hover {
+.behavior-hover g.midpoint .shadow.hover:not(.selected) {
fill:#E96666;
fill-opacity: 0.3;
}
@@ -161,7 +161,7 @@ path.shadow {
-webkit-transition: stroke 100ms linear;
}
-.behavior-hover path.shadow.hover {
+.behavior-hover path.shadow.hover:not(.selected) {
stroke: #E96666;
stroke-opacity: 0.3;
}
@@ -616,6 +616,14 @@ text.pointlabel {
pointer-events: none;
}
+.layer-halo rect,
+.layer-halo path,
+.layer-label text {
+ -webkit-transition: opacity 100ms linear;
+ transition: opacity 100ms linear;
+ -moz-transition: opacity 100ms linear;
+}
+
.pathlabel .textpath {
dominant-baseline: middle;
}
@@ -628,12 +636,7 @@ text.pointlabel {
}
-text.area.tag-leisure-park {
- font-size: 16px;
-}
-
-text.point.tag-shop,
-text.point.tag-amenity {
+text.point {
font-size: 9px;
}
diff --git a/index.html b/index.html
index 3c6767927..c8ddd7562 100644
--- a/index.html
+++ b/index.html
@@ -24,7 +24,6 @@
-
@@ -39,7 +38,6 @@
-
@@ -75,10 +73,9 @@
-
-
-
-
+
+
+
@@ -96,7 +93,9 @@
+
+
@@ -116,16 +115,15 @@
-
-
-
+
+
diff --git a/js/id/actions/add_way.js b/js/id/actions/add_entity.js
similarity index 65%
rename from js/id/actions/add_way.js
rename to js/id/actions/add_entity.js
index 2be062d3a..0011d0fe5 100644
--- a/js/id/actions/add_way.js
+++ b/js/id/actions/add_entity.js
@@ -1,4 +1,4 @@
-iD.actions.AddWay = function(way) {
+iD.actions.AddEntity = function(way) {
return function(graph) {
return graph.replace(way);
};
diff --git a/js/id/actions/add_node.js b/js/id/actions/add_node.js
deleted file mode 100644
index 669a81d49..000000000
--- a/js/id/actions/add_node.js
+++ /dev/null
@@ -1,6 +0,0 @@
-// https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/command/AddCommand.java
-iD.actions.AddNode = function(node) {
- return function(graph) {
- return graph.replace(node);
- };
-};
diff --git a/js/id/actions/add_way_node.js b/js/id/actions/add_vertex.js
similarity index 80%
rename from js/id/actions/add_way_node.js
rename to js/id/actions/add_vertex.js
index 3e4fffcfc..279737c05 100644
--- a/js/id/actions/add_way_node.js
+++ b/js/id/actions/add_vertex.js
@@ -1,5 +1,5 @@
// https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/AddNodeToWayAction.as
-iD.actions.AddWayNode = function(wayId, nodeId, index) {
+iD.actions.AddVertex = function(wayId, nodeId, index) {
return function(graph) {
return graph.replace(graph.entity(wayId).addNode(nodeId, index));
};
diff --git a/js/id/actions/change_entity_tags.js b/js/id/actions/change_tags.js
similarity index 71%
rename from js/id/actions/change_entity_tags.js
rename to js/id/actions/change_tags.js
index 96901c2eb..c5972644e 100644
--- a/js/id/actions/change_entity_tags.js
+++ b/js/id/actions/change_tags.js
@@ -1,4 +1,4 @@
-iD.actions.ChangeEntityTags = function(entityId, tags) {
+iD.actions.ChangeTags = function(entityId, tags) {
return function(graph) {
var entity = graph.entity(entityId);
return graph.replace(entity.update({tags: tags}));
diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js
index fa038d201..89ba7f1cd 100644
--- a/js/id/behavior/add_way.js
+++ b/js/id/behavior/add_way.js
@@ -32,7 +32,7 @@ iD.behavior.AddWay = function(mode) {
};
addWay.cancel = function() {
- controller.exit();
+ controller.enter(iD.modes.Browse());
};
return d3.rebind(addWay, event, 'on');
diff --git a/js/id/behavior/drag_midpoint.js b/js/id/behavior/drag_midpoint.js
index bd1a40bd9..6041946cd 100644
--- a/js/id/behavior/drag_midpoint.js
+++ b/js/id/behavior/drag_midpoint.js
@@ -25,7 +25,7 @@ iD.behavior.DragMidpoint = function(mode) {
.on('end', function() {
history.replace(
iD.actions.Noop(),
- 'Added a node to a way.');
+ t('operations.add.annotation.vertex'));
});
return behavior;
diff --git a/js/id/behavior/drag_node.js b/js/id/behavior/drag_node.js
index 1bfddfed6..b9846617d 100644
--- a/js/id/behavior/drag_node.js
+++ b/js/id/behavior/drag_node.js
@@ -25,6 +25,10 @@ iD.behavior.DragNode = function(mode) {
nudgeInterval = null;
}
+ function annotation(entity) {
+ return t('operations.move.annotation.' + entity.geometry(mode.history.graph()));
+ }
+
return iD.behavior.drag()
.delegate(".node")
.origin(function(entity) {
@@ -43,12 +47,12 @@ iD.behavior.DragNode = function(mode) {
history.replace(
iD.actions.MoveNode(entity.id, projection.invert(d3.event.point)),
- 'moved a node');
+ annotation(entity));
})
- .on('end', function() {
+ .on('end', function(entity) {
stopNudge();
history.replace(
iD.actions.Noop(),
- 'moved a node');
+ annotation(entity));
});
};
diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js
index 21c13d2fa..1cc62b755 100644
--- a/js/id/behavior/draw_way.js
+++ b/js/id/behavior/draw_way.js
@@ -2,17 +2,19 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
- way = mode.history.graph().entity(wayId),
+ way = history.graph().entity(wayId),
finished = false,
- annotation = 'Added to a way.',
+ annotation = t((way.isDegenerate() ?
+ 'operations.start.annotation.' :
+ 'operations.continue.annotation.') + way.geometry(history.graph())),
draw = iD.behavior.Draw(map);
var node = iD.Node({loc: map.mouseCoordinates()}),
nodeId = node.id;
history[way.isDegenerate() ? 'replace' : 'perform'](
- iD.actions.AddNode(node),
- iD.actions.AddWayNode(wayId, node.id, index));
+ iD.actions.AddEntity(node),
+ iD.actions.AddVertex(wayId, node.id, index));
function move(datum) {
var loc = map.mouseCoordinates();
@@ -71,12 +73,6 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
history.on('undone.draw', null);
};
- drawWay.annotation = function(_) {
- if (!arguments.length) return annotation;
- annotation = _;
- return drawWay;
- };
-
function ReplaceTemporaryNode(newNode) {
return function(graph) {
return graph
@@ -90,7 +86,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var newNode = iD.Node({loc: loc});
history.replace(
- iD.actions.AddNode(newNode),
+ iD.actions.AddEntity(newNode),
ReplaceTemporaryNode(newNode),
annotation);
@@ -103,8 +99,8 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var newNode = iD.Node({loc: loc});
history.perform(
- iD.actions.AddNode(newNode),
- iD.actions.AddWayNode(way.id, newNode.id, wayIndex),
+ iD.actions.AddEntity(newNode),
+ iD.actions.AddVertex(way.id, newNode.id, wayIndex),
ReplaceTemporaryNode(newNode),
annotation);
@@ -143,7 +139,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
var way = history.graph().entity(wayId);
if (way) {
- controller.enter(iD.modes.Select(way, true));
+ controller.enter(iD.modes.Select([way.id], true));
} else {
controller.enter(iD.modes.Browse());
}
@@ -153,7 +149,7 @@ iD.behavior.DrawWay = function(wayId, index, mode, baseGraph) {
drawWay.cancel = function() {
history.perform(
d3.functor(baseGraph),
- 'Cancelled drawing.');
+ t('operations.cancel_draw.annotation'));
finished = true;
controller.enter(iD.modes.Browse());
diff --git a/js/id/renderer/hash.js b/js/id/behavior/hash.js
similarity index 66%
rename from js/id/renderer/hash.js
rename to js/id/behavior/hash.js
index 384705543..65e748e28 100644
--- a/js/id/renderer/hash.js
+++ b/js/id/behavior/hash.js
@@ -1,9 +1,6 @@
-iD.Hash = function() {
- var hash = { hadHash: false },
- s0 = null, // cached location.hash
- lat = 90 - 1e-8, // allowable latitude range
- controller,
- map;
+iD.behavior.Hash = function(controller, map) {
+ var s0 = null, // cached location.hash
+ lat = 90 - 1e-8; // allowable latitude range
var parser = function(map, s) {
var q = iD.util.stringQs(s);
@@ -49,7 +46,7 @@ iD.Hash = function() {
var entity = map.history().graph().entity(id);
if (entity === undefined) return;
else selectoff();
- controller.enter(iD.modes.Select(entity));
+ controller.enter(iD.modes.Select([entity.id]));
map.on('drawn.hash', null);
});
controller.on('enter.hash', function() {
@@ -61,32 +58,29 @@ iD.Hash = function() {
map.on('drawn.hash', null);
}
- hash.controller = function(_) {
- if (!arguments.length) return controller;
- controller = _;
- return hash;
- };
+ function hash() {
+ map.on('move.hash', move);
- hash.map = function(x) {
- if (!arguments.length) return map;
- if (map) {
- map.on("move.hash", null);
- window.removeEventListener("hashchange", hashchange, false);
- }
- map = x;
- if (x) {
- map.on("move.hash", move);
- window.addEventListener("hashchange", hashchange, false);
- if (location.hash) {
- var q = iD.util.stringQs(location.hash.substring(1));
- if (q.id) {
- willselect(q.id);
- }
- hashchange();
- hash.hadHash = true;
+ d3.select(window)
+ .on('hashchange.hash', hashchange);
+
+ if (location.hash) {
+ var q = iD.util.stringQs(location.hash.substring(1));
+ if (q.id) {
+ willselect(q.id);
}
+ hashchange();
+ hash.hadHash = true;
}
- return hash;
+ }
+
+ hash.off = function() {
+ map.on('move.hash', null);
+
+ d3.select(window)
+ .on('hashchange.hash', null);
+
+ location.hash = "";
};
return hash;
diff --git a/js/id/behavior/select.js b/js/id/behavior/select.js
new file mode 100644
index 000000000..cee2cc940
--- /dev/null
+++ b/js/id/behavior/select.js
@@ -0,0 +1,22 @@
+iD.behavior.Select = function(mode) {
+ var controller = mode.controller;
+
+ function click() {
+ var datum = d3.select(d3.event.target).datum();
+ if (datum instanceof iD.Entity) {
+ controller.enter(iD.modes.Select([datum.id]));
+ } else {
+ controller.enter(iD.modes.Browse());
+ }
+ }
+
+ var behavior = function(selection) {
+ selection.on('click.select', click);
+ };
+
+ behavior.off = function(selection) {
+ selection.on('click.select', null);
+ };
+
+ return behavior;
+};
diff --git a/js/id/controller/controller.js b/js/id/controller.js
similarity index 87%
rename from js/id/controller/controller.js
rename to js/id/controller.js
index 7e74c56d4..6c4e0fd1e 100644
--- a/js/id/controller/controller.js
+++ b/js/id/controller.js
@@ -19,9 +19,5 @@ iD.Controller = function(map, history) {
event.enter(mode);
};
- controller.exit = function() {
- controller.enter(iD.modes.Browse());
- };
-
return d3.rebind(controller, event, 'on');
};
diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js
index 5e865b9ec..a19bfe094 100644
--- a/js/id/graph/graph.js
+++ b/js/id/graph/graph.js
@@ -235,44 +235,39 @@ iD.Graph.prototype = {
},
difference: function (graph) {
- var result = [],
- keys = Object.keys(this.entities),
- entity, oldentity, id, i;
- for (i = 0; i < keys.length; i++) {
- id = keys[i];
- entity = this.entities[id];
- oldentity = graph.entities[id];
- if (entity !== oldentity) {
+ function diff(a, b) {
+ var result = [],
+ keys = Object.keys(a.entities),
+ entity, oldentity, id, i;
- if (entity && entity.type === 'way' &&
- oldentity && oldentity.type === 'way') {
- result = result
- .concat(_.difference(entity.nodes, oldentity.nodes))
- .concat(_.difference(oldentity.nodes, entity.nodes));
+ for (i = 0; i < keys.length; i++) {
+ id = keys[i];
+ entity = a.entities[id];
+ oldentity = b.entities[id];
+ if (entity !== oldentity) {
- } else if (entity && entity.type === 'way') {
- result = result.concat(entity.nodes);
+ // 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 (oldentity && oldentity.type === 'way') {
- result = result.concat(oldentity.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);
}
-
- result.push(id);
}
+ return result;
}
- keys = Object.keys(graph.entities);
- for (i = 0; i < keys.length; i++) {
- id = keys[i];
- entity = graph.entities[id];
- if (entity && !this.entities.hasOwnProperty(id)) {
- result.push(id);
- if (entity.type === 'way') result = result.concat(entity.nodes);
- }
- }
-
- return result.sort();
+ return _.unique(diff(this, graph).concat(diff(graph, this)).sort());
},
modified: function() {
diff --git a/js/id/id.js b/js/id/id.js
index 2aad9b5bf..3e8aabcc3 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -234,7 +234,9 @@ window.iD = function(container) {
d3.select(document)
.call(keybinding);
- var hash = iD.Hash().controller(controller).map(map);
+ var hash = iD.behavior.Hash(controller, map);
+
+ hash();
if (!hash.hadHash) {
map.centerZoom([-77.02271, 38.90085], 20);
diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js
index 614c66677..bda4e0daa 100644
--- a/js/id/modes/add_area.js
+++ b/js/id/modes/add_area.js
@@ -2,9 +2,9 @@ iD.modes.AddArea = function() {
var mode = {
id: 'add-area',
button: 'area',
- title: 'Area',
- description: 'Add parks, buildings, lakes, or other areas to the map.',
- key: 'a'
+ title: t('modes.add_area.title'),
+ description: t('modes.add_area.description'),
+ key: t('modes.add_area.key')
};
var behavior,
@@ -21,10 +21,10 @@ iD.modes.AddArea = function() {
way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddNode(node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(way.id, node.id));
+ 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));
}
@@ -35,11 +35,11 @@ iD.modes.AddArea = function() {
way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddNode(node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(other.id, node.id, index));
+ 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));
}
@@ -49,9 +49,9 @@ iD.modes.AddArea = function() {
way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(way.id, node.id));
+ 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));
}
@@ -63,9 +63,9 @@ iD.modes.AddArea = function() {
history.perform(
iD.actions.AddMidpoint(midpoint, node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(way.id, node.id));
+ 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));
}
@@ -77,7 +77,7 @@ iD.modes.AddArea = function() {
.on('startFromMidpoint', startFromMidpoint);
mode.map.surface.call(behavior);
- mode.map.tail('Click on the map to start drawing an area, like a park, lake, or building.', true);
+ mode.map.tail(t('modes.add_area.tail'));
};
mode.exit = function() {
diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js
index 90adac738..92e9a64d7 100644
--- a/js/id/modes/add_line.js
+++ b/js/id/modes/add_line.js
@@ -2,9 +2,9 @@ iD.modes.AddLine = function() {
var mode = {
id: 'add-line',
button: 'line',
- title: 'Line',
- description: 'Lines can be highways, streets, pedestrian paths, or even canals.',
- key: 'l'
+ title: t('modes.add_line.title'),
+ description: t('modes.add_line.description'),
+ key: t('modes.add_line.key')
};
var behavior,
@@ -21,9 +21,9 @@ iD.modes.AddLine = function() {
way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddNode(node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id));
+ iD.actions.AddEntity(node),
+ iD.actions.AddEntity(way),
+ iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
@@ -34,10 +34,10 @@ iD.modes.AddLine = function() {
way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddNode(node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(other.id, node.id, index));
+ 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));
}
@@ -57,8 +57,8 @@ iD.modes.AddLine = function() {
var way = iD.Way({tags: defaultTags});
history.perform(
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id));
+ iD.actions.AddEntity(way),
+ iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
@@ -71,8 +71,8 @@ iD.modes.AddLine = function() {
history.perform(
iD.actions.AddMidpoint(midpoint, node),
- iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, node.id));
+ iD.actions.AddEntity(way),
+ iD.actions.AddVertex(way.id, node.id));
controller.enter(iD.modes.DrawLine(way.id, 'forward', graph));
}
@@ -84,7 +84,7 @@ iD.modes.AddLine = function() {
.on('startFromMidpoint', startFromMidpoint);
mode.map.surface.call(behavior);
- mode.map.tail('Click on the map to start drawing an road, path, or route.', true);
+ mode.map.tail(t('modes.add_line.tail'));
};
mode.exit = function() {
diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js
index 1b9781a9d..6dd524bb5 100644
--- a/js/id/modes/add_point.js
+++ b/js/id/modes/add_point.js
@@ -1,29 +1,26 @@
iD.modes.AddPoint = function() {
var mode = {
id: 'add-point',
- title: 'Point',
- description: 'Restaurants, monuments, and postal boxes are points.',
- key: 'p'
+ title: t('modes.add_point.title'),
+ description: t('modes.add_point.description'),
+ key: t('modes.add_point.key')
};
var behavior;
mode.enter = function() {
var map = mode.map,
- surface = map.surface,
history = mode.history,
controller = mode.controller;
- map.tail('Click on the map to add a point.', true);
-
function add(loc) {
var node = iD.Node({loc: loc});
history.perform(
- iD.actions.AddNode(node),
- 'Added a point.');
+ iD.actions.AddEntity(node),
+ t('operations.add.annotation.point'));
- controller.enter(iD.modes.Select(node, true));
+ controller.enter(iD.modes.Select([node.id], true));
}
function addWay(way, loc, index) {
@@ -35,7 +32,7 @@ iD.modes.AddPoint = function() {
}
function cancel() {
- controller.exit();
+ controller.enter(iD.modes.Browse());
}
behavior = iD.behavior.Draw(map)
@@ -44,8 +41,10 @@ iD.modes.AddPoint = function() {
.on('clickNode', addNode)
.on('clickMidpoint', addNode)
.on('cancel', cancel)
- .on('finish', cancel)
- (surface);
+ .on('finish', cancel);
+
+ mode.map.surface.call(behavior);
+ mode.map.tail(t('modes.add_point.tail'));
};
mode.exit = function() {
diff --git a/js/id/modes/browse.js b/js/id/modes/browse.js
index ab6bc80cb..7652576ea 100644
--- a/js/id/modes/browse.js
+++ b/js/id/modes/browse.js
@@ -2,9 +2,9 @@ iD.modes.Browse = function() {
var mode = {
button: 'browse',
id: 'browse',
- title: 'Browse',
- description: 'Pan and zoom the map.',
- key: 'b'
+ title: t('modes.browse.title'),
+ description: t('modes.browse.description'),
+ key: t('modes.browse.key')
};
var behaviors;
@@ -14,19 +14,13 @@ iD.modes.Browse = function() {
behaviors = [
iD.behavior.Hover(),
+ iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
behaviors.forEach(function(behavior) {
behavior(surface);
});
-
- surface.on('click.browse', function () {
- var datum = d3.select(d3.event.target).datum();
- if (datum instanceof iD.Entity) {
- mode.controller.enter(iD.modes.Select(datum));
- }
- });
};
mode.exit = function() {
@@ -35,8 +29,6 @@ iD.modes.Browse = function() {
behaviors.forEach(function(behavior) {
behavior.off(surface);
});
-
- surface.on('click.browse', null);
};
return mode;
diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js
index 8423ff436..9801a092c 100644
--- a/js/id/modes/draw_area.js
+++ b/js/id/modes/draw_area.js
@@ -11,8 +11,7 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
headId = way.nodes[way.nodes.length - 2],
tailId = way.first();
- behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph)
- .annotation(way.isDegenerate() ? 'started an area' : 'continued an area');
+ behavior = iD.behavior.DrawWay(wayId, -1, mode, baseGraph);
var addNode = behavior.addNode;
@@ -25,7 +24,7 @@ iD.modes.DrawArea = function(wayId, baseGraph) {
};
mode.map.surface.call(behavior);
- mode.map.tail('Click to add points to your area. Click the first point to finish the area.', true);
+ mode.map.tail(t('modes.draw_area.tail'));
};
mode.exit = function() {
diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js
index ae54ab2e1..5ae8c2703 100644
--- a/js/id/modes/draw_line.js
+++ b/js/id/modes/draw_line.js
@@ -11,8 +11,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
index = (direction === 'forward') ? undefined : 0,
headId = (direction === 'forward') ? way.last() : way.first();
- behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph)
- .annotation(way.isDegenerate() ? 'Started a line.' : 'Continued a line.');
+ behavior = iD.behavior.DrawWay(wayId, index, mode, baseGraph);
var addNode = behavior.addNode;
@@ -25,9 +24,7 @@ iD.modes.DrawLine = function(wayId, direction, baseGraph) {
};
mode.map.surface.call(behavior);
- mode.map.tail('Click to add more points to the line. ' +
- 'Click on other lines to connect to them, and double-click to ' +
- 'end the line.', true);
+ mode.map.tail(t('modes.draw_line.tail'));
};
mode.exit = function() {
diff --git a/js/id/modes/move_way.js b/js/id/modes/move_way.js
index 18c6ee619..5c262e259 100644
--- a/js/id/modes/move_way.js
+++ b/js/id/modes/move_way.js
@@ -11,35 +11,39 @@ iD.modes.MoveWay = function(wayId) {
graph = history.graph(),
selection = map.surface,
controller = mode.controller,
- projection = map.projection;
+ projection = map.projection,
+ way = graph.entity(wayId),
+ origin = d3.mouse(selection.node()),
+ annotation = t('operations.move.annotation.' + way.geometry(graph));
- var way = graph.entity(wayId),
- origin = d3.mouse(selection.node());
+ // If intiated via keyboard
+ if (!origin[0] && !origin[1]) origin = null;
history.perform(
iD.actions.Noop(),
- 'Moved a way.');
+ annotation);
function move() {
var p = d3.mouse(selection.node()),
- delta = [p[0] - origin[0],
- p[1] - origin[1]];
+ delta = origin ?
+ [p[0] - origin[0], p[1] - origin[1]] :
+ [0, 0];
origin = p;
history.replace(
iD.actions.MoveWay(wayId, delta, projection),
- 'Moved a way.');
+ annotation);
}
function finish() {
d3.event.stopPropagation();
- controller.enter(iD.modes.Select(way, true));
+ controller.enter(iD.modes.Select([way.id], true));
}
function cancel() {
history.pop();
- controller.enter(iD.modes.Select(way, true));
+ controller.enter(iD.modes.Select([way.id], true));
}
function undone() {
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index 1ddd4ffc4..8d29450d6 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -1,8 +1,7 @@
-iD.modes.Select = function(entity, initial) {
+iD.modes.Select = function(selection, initial) {
var mode = {
id: 'select',
- button: 'browse',
- entity: entity
+ button: 'browse'
};
var inspector = iD.ui.inspector().initial(!!initial),
@@ -11,23 +10,33 @@ iD.modes.Select = function(entity, initial) {
radialMenu;
function changeTags(d, tags) {
- if (!_.isEqual(entity.tags, tags)) {
+ if (!_.isEqual(singular().tags, tags)) {
mode.history.perform(
- iD.actions.ChangeEntityTags(d.id, tags),
- 'Changed tags.');
+ iD.actions.ChangeTags(d.id, tags),
+ t('operations.change_tags.annotation'));
}
}
+ function singular() {
+ if (selection.length === 1) {
+ return mode.map.history().graph().entity(selection[0]);
+ }
+ }
+
+ mode.selection = function() {
+ return selection;
+ };
+
mode.enter = function() {
var map = mode.map,
- graph = map.history().graph(),
history = map.history(),
- surface = mode.map.surface;
-
- inspector.graph(graph);
+ graph = history.graph(),
+ surface = map.surface,
+ entity = singular();
behaviors = [
iD.behavior.Hover(),
+ iD.behavior.Select(mode),
iD.behavior.DragNode(mode),
iD.behavior.DragMidpoint(mode)];
@@ -36,7 +45,7 @@ iD.modes.Select = function(entity, initial) {
});
var operations = d3.values(iD.operations)
- .map(function (o) { return o(entity.id, mode); })
+ .map(function (o) { return o(selection, mode); })
.filter(function (o) { return o.available(); });
operations.forEach(function(operation) {
@@ -49,88 +58,80 @@ iD.modes.Select = function(entity, initial) {
var q = iD.util.stringQs(location.hash.substring(1));
location.replace('#' + iD.util.qsString(_.assign(q, {
- id: entity.id
+ id: selection.join(',')
}), true));
- d3.select('.inspector-wrap')
- .style('display', 'block')
- .style('opacity', 1)
- .datum(entity)
- .call(inspector);
+ if (entity) {
+ inspector.graph(graph);
- 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(),
- offset = 50,
- shift_left = d3.event.x - map_size[0] + inspector_size[0] + offset,
- center = (map_size[0] / 2) + shift_left + offset;
+ d3.select('.inspector-wrap')
+ .style('display', 'block')
+ .style('opacity', 1)
+ .datum(entity)
+ .call(inspector);
- if (shift_left > 0 && inspector_size[1] > d3.event.y) {
- mode.map.centerEase(mode.map.projection.invert([center, map_size[1]/2]));
+ 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(),
+ 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]));
+ }
}
+
+ inspector
+ .on('changeTags', changeTags)
+ .on('close', function() { mode.controller.enter(iD.modes.Browse()); });
+
+ history.on('change.select', function() {
+ // Exit mode if selected entity gets undone
+ var oldEntity = entity,
+ newEntity = history.graph().entity(selection[0]);
+
+ if (!newEntity) {
+ mode.controller.enter(iD.modes.Browse());
+ } else if (!_.isEqual(oldEntity.tags, newEntity.tags)) {
+ inspector.tags(newEntity.tags);
+ }
+
+ surface.call(radialMenu.close);
+ });
}
- inspector
- .on('changeTags', changeTags)
- .on('close', function() { mode.controller.exit(); });
-
- history.on('change.select', function() {
- // Exit mode if selected entity gets undone
- var old = entity;
- entity = history.graph().entity(entity.id);
- if (!entity) {
- mode.controller.enter(iD.modes.Browse());
- } else if(!_.isEqual(entity.tags, old.tags)) {
- inspector.tags(entity.tags);
- }
-
- surface.call(radialMenu.close);
- });
-
map.on('move.select', function() {
surface.call(radialMenu.close);
});
- function click() {
- var datum = d3.select(d3.event.target).datum();
- if (datum instanceof iD.Entity) {
- mode.controller.enter(iD.modes.Select(datum));
- } else {
- mode.controller.enter(iD.modes.Browse());
- }
- }
-
function dblclick() {
- var selection = d3.select(d3.event.target),
- datum = selection.datum();
+ var target = d3.select(d3.event.target),
+ datum = target.datum();
- if (datum instanceof iD.Way && !selection.classed('fill')) {
+ if (datum instanceof iD.Way && !target.classed('fill')) {
var choice = iD.geo.chooseIndex(datum,
d3.mouse(mode.map.surface.node()), mode.map),
node = iD.Node({ loc: choice.loc });
history.perform(
- iD.actions.AddNode(node),
- iD.actions.AddWayNode(datum.id, node.id, choice.index),
- 'Added a point to a road.');
+ iD.actions.AddEntity(node),
+ iD.actions.AddVertex(datum.id, node.id, choice.index),
+ t('operations.add.annotation.vertex'));
d3.event.preventDefault();
d3.event.stopPropagation();
}
}
- surface.on('click.select', click)
- .on('dblclick.select', dblclick);
-
d3.select(document)
.call(keybinding);
- surface.selectAll("*")
- .filter(function (d) {
- return d && entity && d.id === entity.id;
- })
+ surface.on('dblclick.select', dblclick)
+ .selectAll("*")
+ .filter(function (d) { return d && selection.indexOf(d.id) >= 0; })
.classed('selected', true);
radialMenu = iD.ui.RadialMenu(operations);
@@ -138,7 +139,7 @@ iD.modes.Select = function(entity, initial) {
if (d3.event && !initial) {
var loc = map.mouseCoordinates();
- if (entity.type === 'node') {
+ if (entity && entity.type === 'node') {
loc = entity.loc;
}
@@ -150,8 +151,8 @@ iD.modes.Select = function(entity, initial) {
var surface = mode.map.surface,
history = mode.history;
- if (entity) {
- changeTags(entity, inspector.tags());
+ if (singular()) {
+ changeTags(singular(), inspector.tags());
}
d3.select('.inspector-wrap')
@@ -171,12 +172,10 @@ iD.modes.Select = function(entity, initial) {
keybinding.off();
- surface.on('click.select', null)
- .on('dblclick.select', null);
-
history.on('change.select', null);
- surface.selectAll(".selected")
+ surface.on('dblclick.select', null)
+ .selectAll(".selected")
.classed('selected', false);
surface.call(radialMenu.close);
diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js
index ce161aef4..4b0f27bf9 100644
--- a/js/id/operations/circularize.js
+++ b/js/id/operations/circularize.js
@@ -1,28 +1,20 @@
-iD.operations.Circularize = function(entityId, mode) {
- var history = mode.map.history(),
+iD.operations.Circularize = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history(),
action = iD.actions.Circularize(entityId, mode.map);
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
- geometry = entity.geometry(graph);
+ annotation = t('operations.circularize.annotation.' + entity.geometry(graph));
- if (geometry === 'line') {
- history.perform(
- action,
- 'Made a line circular.');
-
- } else if (geometry === 'area') {
- history.perform(
- action,
- 'Made an area circular.');
- }
+ history.perform(action, annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
- return entity.geometry(graph) === 'area' || entity.geometry(graph) === 'line';
+ return selection.length === 1 && entity.type === 'way';
};
operation.enabled = function() {
@@ -31,9 +23,9 @@ iD.operations.Circularize = function(entityId, mode) {
};
operation.id = "circularize";
- operation.key = "O";
- operation.title = "Circularize";
- operation.description = "Make this round";
+ operation.key = t('operations.circularize.key');
+ operation.title = t('operations.circularize.title');
+ operation.description = t('operations.circularize.description');
return operation;
};
diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js
index cc3750360..12df22a09 100644
--- a/js/id/operations/delete.js
+++ b/js/id/operations/delete.js
@@ -1,37 +1,23 @@
-iD.operations.Delete = function(entityId, mode) {
- var history = mode.map.history();
+iD.operations.Delete = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history();
var operation = function() {
var graph = history.graph(),
entity = graph.entity(entityId),
- geometry = entity.geometry(graph);
+ action = {way: iD.actions.DeleteWay, node: iD.actions.DeleteNode}[entity.type],
+ annotation = t('operations.delete.annotation.' + entity.geometry(graph));
- if (geometry === 'vertex') {
- history.perform(
- iD.actions.DeleteNode(entityId),
- 'Deleted a vertex.');
-
- } else if (geometry === 'point') {
- history.perform(
- iD.actions.DeleteNode(entityId),
- 'Deleted a point.');
-
- } else if (geometry === 'line') {
- history.perform(
- iD.actions.DeleteWay(entityId),
- 'Deleted a line.');
-
- } else if (geometry === 'area') {
- history.perform(
- iD.actions.DeleteWay(entityId),
- 'Deleted an area.');
- }
+ history.perform(
+ action(entityId),
+ annotation);
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
- return _.contains(['vertex', 'point', 'line', 'area'], entity.geometry(graph));
+ return selection.length === 1 &&
+ (entity.type === 'way' || entity.type === 'node');
};
operation.enabled = function() {
@@ -39,9 +25,9 @@ iD.operations.Delete = function(entityId, mode) {
};
operation.id = "delete";
- operation.key = "⌫";
- operation.title = "Delete";
- operation.description = "Remove this from the map.";
+ operation.key = t('operations.delete.key');
+ operation.title = t('operations.delete.title');
+ operation.description = t('operations.delete.description');
return operation;
};
diff --git a/js/id/operations/move.js b/js/id/operations/move.js
index 383ca4206..10405cc53 100644
--- a/js/id/operations/move.js
+++ b/js/id/operations/move.js
@@ -1,5 +1,6 @@
-iD.operations.Move = function(entityId, mode) {
- var history = mode.map.history();
+iD.operations.Move = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history();
var operation = function() {
mode.controller.enter(iD.modes.MoveWay(entityId));
@@ -7,7 +8,8 @@ iD.operations.Move = function(entityId, mode) {
operation.available = function() {
var graph = history.graph();
- return graph.entity(entityId).type === 'way';
+ return selection.length === 1 &&
+ graph.entity(entityId).type === 'way';
};
operation.enabled = function() {
@@ -15,9 +17,9 @@ iD.operations.Move = function(entityId, mode) {
};
operation.id = "move";
- operation.key = "M";
- operation.title = "Move";
- operation.description = "Move this to a different location";
+ operation.key = t('operations.move.key');
+ operation.title = t('operations.move.title');
+ operation.description = t('operations.move.description');
return operation;
};
diff --git a/js/id/operations/reverse.js b/js/id/operations/reverse.js
index 62fd8810b..78b8789dd 100644
--- a/js/id/operations/reverse.js
+++ b/js/id/operations/reverse.js
@@ -1,16 +1,18 @@
-iD.operations.Reverse = function(entityId, mode) {
- var history = mode.map.history();
+iD.operations.Reverse = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history();
var operation = function() {
history.perform(
iD.actions.ReverseWay(entityId),
- 'Reversed a line.');
+ t('operations.reverse.annotation'));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
- return entity.geometry(graph) === 'line';
+ return selection.length === 1 &&
+ entity.geometry(graph) === 'line';
};
operation.enabled = function() {
@@ -18,9 +20,9 @@ iD.operations.Reverse = function(entityId, mode) {
};
operation.id = "reverse";
- operation.key = "V";
- operation.title = "Reverse";
- operation.description = "Make this way go in the opposite direction.";
+ operation.key = t('operations.reverse.key');
+ operation.title = t('operations.reverse.title');
+ operation.description = t('operations.reverse.description');
return operation;
};
diff --git a/js/id/operations/split.js b/js/id/operations/split.js
index 40aa55fd0..4274fc41b 100644
--- a/js/id/operations/split.js
+++ b/js/id/operations/split.js
@@ -1,15 +1,17 @@
-iD.operations.Split = function(entityId, mode) {
- var history = mode.map.history(),
+iD.operations.Split = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history(),
action = iD.actions.SplitWay(entityId);
var operation = function() {
- history.perform(action, 'Split a way.');
+ history.perform(action, t('operations.split.annotation'));
};
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
- return entity.geometry(graph) === 'vertex';
+ return selection.length === 1 &&
+ entity.geometry(graph) === 'vertex';
};
operation.enabled = function() {
@@ -18,9 +20,9 @@ iD.operations.Split = function(entityId, mode) {
};
operation.id = "split";
- operation.key = "X";
- operation.title = "Split";
- operation.description = "Split this into two ways at this point.";
+ operation.key = t('operations.split.key');
+ operation.title = t('operations.split.title');
+ operation.description = t('operations.split.description');
return operation;
};
diff --git a/js/id/operations/unjoin.js b/js/id/operations/unjoin.js
index f38333bb4..2a40ec067 100644
--- a/js/id/operations/unjoin.js
+++ b/js/id/operations/unjoin.js
@@ -1,5 +1,6 @@
-iD.operations.Unjoin = function(entityId, mode) {
- var history = mode.map.history(),
+iD.operations.Unjoin = function(selection, mode) {
+ var entityId = selection[0],
+ history = mode.map.history(),
action = iD.actions.UnjoinNode(entityId);
var operation = function() {
@@ -9,7 +10,8 @@ iD.operations.Unjoin = function(entityId, mode) {
operation.available = function() {
var graph = history.graph(),
entity = graph.entity(entityId);
- return entity.geometry(graph) === 'vertex';
+ return selection.length === 1 &&
+ entity.geometry(graph) === 'vertex';
};
operation.enabled = function() {
@@ -18,9 +20,9 @@ iD.operations.Unjoin = function(entityId, mode) {
};
operation.id = "unjoin";
- operation.key = "⇧-J";
- operation.title = "Unjoin";
- operation.description = "Disconnect these ways from each other.";
+ operation.key = t('operations.unjoin.key');
+ operation.title = t('operations.unjoin.title');
+ operation.description = t('operations.unjoin.description');
return operation;
};
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index 6476e7a28..54e3835ee 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -1,4 +1,10 @@
iD.Background = function() {
+
+ var deviceRatio = (window.devicePixelRatio &&
+ window.devicePixelRatio === 2) ? 0.5 : 1;
+ // tileSize = (deviceRatio === 0.5) ? [128,128] : [256,256];
+ var tileSize = [256, 256];
+
var tile = d3.geo.tile(),
projection,
cache = {},
@@ -13,7 +19,12 @@ iD.Background = function() {
'-o-transform-origin:0 0;' +
'-webkit-user-select: none;' +
'-webkit-user-drag: none;' +
- '-moz-user-drag: none;';
+ '-moz-user-drag: none;' +
+ 'opacity:0;';
+
+ function tileSizeAtZoom(d, z) {
+ return Math.ceil(tileSize[0] * Math.pow(2, z - d[2])) / tileSize[0];
+ }
function atZoom(t, distance) {
var power = Math.pow(2, distance);
@@ -21,108 +32,101 @@ iD.Background = function() {
Math.floor(t[0] * power),
Math.floor(t[1] * power),
t[2] + distance];
- az.push(source(az));
return az;
}
- function upZoom(t, distance) {
- var az = atZoom(t, distance), tiles = [];
- for (var x = 0; x < 2; x++) {
- for (var y = 0; y < 2; y++) {
- var up = [az[0] + x, az[1] + y, az[2]];
- up.push(source(up));
- tiles.push(up);
- }
- }
- return tiles;
- }
-
- function tileSize(d, z) {
- return Math.ceil(256 * Math.pow(2, z - d[2])) / 256;
- }
-
function lookUp(d) {
for (var up = -1; up > -d[2]; up--) {
if (cache[atZoom(d, up)] !== false) return atZoom(d, up);
}
}
+ function uniqueBy(a, n) {
+ var o = [], seen = {};
+ for (var i = 0; i < a.length; i++) {
+ if (seen[a[i][n]] === undefined) {
+ o.push(a[i]);
+ seen[a[i][n]] = true;
+ }
+ }
+ return o;
+ }
+
+ function addSource(d) {
+ d.push(source(d));
+ return d;
+ }
+
// derive the tiles onscreen, remove those offscreen and position tiles
// correctly for the currentstate of `projection`
function background() {
- var tiles = tile
+ var sel = this,
+ tiles = tile
.scale(projection.scale())
.scaleExtent(source.scaleExtent || [1, 17])
.translate(projection.translate())(),
+ requests = [],
scaleExtent = tile.scaleExtent(),
z = Math.max(Math.log(projection.scale()) / Math.log(2) - 8, 0),
- rz = Math.max(scaleExtent[0], Math.min(scaleExtent[1], Math.floor(z))),
- ts = 256 * Math.pow(2, z - rz),
+ rz = Math.max(scaleExtent[0],
+ Math.min(scaleExtent[1], Math.floor(z))),
+ ts = tileSize[0] * Math.pow(2, z - rz),
tile_origin = [
projection.scale() / 2 - projection.translate()[0],
- projection.scale() / 2 - projection.translate()[1]],
- ups = {};
+ projection.scale() / 2 - projection.translate()[1]];
tiles.forEach(function(d) {
-
- if (cache[d] === true) {
- d.push(source(d));
- } else if (cache[d] === false &&
- cache[atZoom(d, -1)] !== false &&
- !ups[atZoom(d, -1)]) {
-
- ups[atZoom(d, -1)] = true;
- tiles.push(atZoom(d, -1));
-
- } else if (cache[d] === undefined &&
- lookUp(d)) {
-
- var upTile = lookUp(d);
- if (!ups[upTile]) {
- ups[upTile] = true;
- tiles.push(upTile);
- }
-
- } else if (cache[d] === undefined ||
- cache[d] === false) {
- upZoom(d, 1).forEach(function(u) {
- if (cache[u] && !ups[u]) {
- ups[u] = true;
- tiles.push(u);
- }
- });
+ addSource(d);
+ requests.push(d);
+ if (!cache[d[3]] && lookUp(d)) {
+ requests.push(addSource(lookUp(d)));
}
});
- var image = this
- .selectAll('img')
- .data(tiles, function(d) { return d; });
+ requests = uniqueBy(requests, 3);
function load(d) {
- cache[d.slice(0, 3)] = true;
- d3.select(this).on('load', null);
+ cache[d[3]] = true;
+ d3.select(this)
+ .on('load', null)
+ .transition()
+ .style('opacity', 1);
+ background.apply(sel);
}
function error(d) {
- cache[d.slice(0, 3)] = false;
+ cache[d[3]] = false;
+ d3.select(this).on('load', null);
d3.select(this).remove();
+ background.apply(sel);
}
+ function imageTransform(d) {
+ var _ts = tileSize[0] * Math.pow(2, z - d[2]);
+ var scale = tileSizeAtZoom(d, z);
+ return 'translate(' +
+ (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' +
+ (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px)' +
+ 'scale(' + scale + ',' + scale + ')';
+ }
+
+ var image = this
+ .selectAll('img')
+ .data(requests, function(d) { return d[3]; });
+
+ image.exit()
+ .style(transformProp, imageTransform)
+ .transition()
+ .style('opacity', 0)
+ .remove();
+
image.enter().append('img')
.attr('style', imgstyle)
.attr('src', function(d) { return d[3]; })
.on('error', error)
.on('load', load);
-
- image.exit().remove();
-
- image.style(transformProp, function(d) {
- var _ts = 256 * Math.pow(2, z - d[2]);
- var scale = tileSize(d, z);
- return 'translate(' +
- (Math.round((d[0] * _ts) - tile_origin[0]) + offset[0]) + 'px,' +
- (Math.round((d[1] * _ts) - tile_origin[1]) + offset[1]) + 'px) scale(' + scale + ',' + scale + ')';
- });
+
+ image.style(transformProp, imageTransform);
if (Object.keys(cache).length > 100) cache = {};
}
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 0d1532948..a2c4781ef 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -112,7 +112,7 @@ iD.Map = function() {
.call(areas, graph, all, filter)
.call(multipolygons, graph, all, filter)
.call(midpoints, graph, all, filter)
- .call(labels, graph, all, filter, dimensions);
+ .call(labels, graph, all, filter, dimensions, !difference);
}
dispatch.drawn(map);
}
@@ -136,7 +136,7 @@ iD.Map = function() {
}
if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) {
- iD.flash()
+ iD.ui.flash()
.select('.content')
.text('Cannot zoom out further in current mode.');
return map.zoom(16);
@@ -361,7 +361,7 @@ iD.Map = function() {
};
var usedTails = {};
- map.tail = function (_, once) {
+ map.tail = function (_) {
if (!_ || usedTails[_] === undefined) {
tail.text(_);
usedTails[_] = true;
diff --git a/js/id/svg/labels.js b/js/id/svg/labels.js
index 2fcc2fcc1..eb4297bca 100644
--- a/js/id/svg/labels.js
+++ b/js/id/svg/labels.js
@@ -2,18 +2,36 @@ iD.svg.Labels = function(projection) {
// Replace with dict and iterate over entities tags instead?
var label_stack = [
+ ['line', 'aeroway'],
['line', 'highway'],
- ['area', 'building', 'yes'],
- ['area', 'leisure', 'park'],
+ ['line', 'railway'],
+ ['line', 'waterway'],
+ ['area', 'aeroway'],
+ ['area', 'amenity'],
+ ['area', 'building'],
+ ['area', 'historic'],
+ ['area', 'leisure'],
+ ['area', 'man_made'],
['area', 'natural'],
+ ['area', 'shop'],
+ ['area', 'tourism'],
+ ['point', 'aeroway'],
['point', 'amenity'],
- ['point', 'shop']
+ ['point', 'building'],
+ ['point', 'historic'],
+ ['point', 'leisure'],
+ ['point', 'man_made'],
+ ['point', 'natural'],
+ ['point', 'shop'],
+ ['point', 'tourism'],
+ ['line', 'name'],
+ ['area', 'name'],
+ ['point', 'name']
];
var default_size = 12;
var font_sizes = label_stack.map(function(d) {
- var style = iD.util.getStyle(
- 'text.' + d[0] + '.tag-' + d.slice(1).join('-'));
+ var style = iD.util.getStyle('text.' + d[0] + '.tag-' + d[1]);
var m = style && style.cssText.match("font-size: ([0-9]{1,2})px;");
if (!m) return default_size;
return parseInt(m[1], 10);
@@ -156,7 +174,7 @@ iD.svg.Labels = function(projection) {
for (var i = 0; i < nodes.length - 1; i++) {
var current = segmentLength(i);
var portion;
- if (!start && sofar + current > from) {
+ if (!start && sofar + current >= from) {
portion = (from - sofar) / current;
start = [
nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
@@ -164,7 +182,7 @@ iD.svg.Labels = function(projection) {
];
i0 = i + 1;
}
- if (!end && sofar + current > to) {
+ if (!end && sofar + current >= to) {
portion = (to - sofar) / current;
end = [
nodes[i][0] + portion * (nodes[i + 1][0] - nodes[i][0]),
@@ -182,15 +200,46 @@ iD.svg.Labels = function(projection) {
}
+ function hideOnMouseover() {
+ var mouse = d3.mouse(this),
+ pad = 50,
+ rect = new RTree.Rectangle(mouse[0] - pad, mouse[1] - pad, 2*pad, 2*pad),
+ labels = _.pluck(rtree.search(rect, this), 'leaf'),
+ selection = d3.select(this);
- return function drawLabels(surface, graph, entities, filter, dimensions) {
+ selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect')
+ .style('opacity', '');
+
+ if (!labels.length) return;
+ selection.selectAll('.layer-label text, .layer-halo path, .layer-halo rect')
+ .filter(function(d) {
+ return _.contains(labels, d.id);
+ })
+ .style('opacity', 0);
+ }
+
+ var rtree = new RTree(),
+ rectangles = {};
+
+ return function drawLabels(surface, graph, entities, filter, dimensions, fullRedraw) {
+
+ d3.select(surface.node().parentNode)
+ .on('mousemove.hidelabels', hideOnMouseover);
- var rtree = new RTree();
var hidePoints = !d3.select('.node.point').node();
var labelable = [], i, k, entity;
for (i = 0; i < label_stack.length; i++) labelable.push([]);
+ if (fullRedraw) {
+ rtree = new RTree();
+ rectangles = {};
+ } else {
+ for (i = 0; i < entities.length; i++) {
+ rtree.remove(rectangles[entities[i].id], entities[i].id);
+ }
+ }
+
// Split entities into groups specified by label_stack
for (i = 0; i < entities.length; i++) {
entity = entities[i];
@@ -198,7 +247,7 @@ iD.svg.Labels = function(projection) {
if (hidePoints && entity.geometry(graph) === 'point') continue;
for (k = 0; k < label_stack.length; k ++) {
if (entity.geometry(graph) === label_stack[k][0] &&
- entity.tags[label_stack[k][1]] && !entity.tags[label_stack[k][2]]) {
+ entity.tags[label_stack[k][1]]) {
labelable[k].push(entity);
break;
}
@@ -252,7 +301,7 @@ iD.svg.Labels = function(projection) {
textAnchor: offset[2]
};
var rect = new RTree.Rectangle(p.x - m, p.y - m, width + 2*m, height + 2*m);
- if (tryInsert(rect)) return p;
+ if (tryInsert(rect, entity.id)) return p;
}
@@ -275,7 +324,7 @@ iD.svg.Labels = function(projection) {
Math.abs(sub[0][1] - sub[sub.length - 1][1]) + 30
);
if (rev) sub = sub.reverse();
- if (tryInsert(rect)) return {
+ if (tryInsert(rect, entity.id)) return {
'font-size': height + 2,
lineString: lineString(sub),
startOffset: offset + '%'
@@ -298,16 +347,19 @@ iD.svg.Labels = function(projection) {
height: height
};
var rect = new RTree.Rectangle(p.x - width/2, p.y, width, height);
- if (tryInsert(rect)) return p;
+ if (tryInsert(rect, entity.id)) return p;
}
- function tryInsert(rect) {
+ function tryInsert(rect, id) {
// Check that label is visible
if (rect.x1 < 0 || rect.y1 < 0 || rect.x2 > dimensions[0] ||
rect.y2 > dimensions[1]) return false;
var v = rtree.search(rect, true).length === 0;
- if (v) rtree.insert(rect);
+ if (v) {
+ rtree.insert(rect, id);
+ rectangles[id] = rect;
+ }
return v;
}
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index 65cfccd27..83c533255 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -35,7 +35,7 @@ iD.svg.Lines = function(projection) {
return function drawLines(surface, graph, entities, filter) {
function drawPaths(group, lines, filter, classes, lineString) {
- var paths = group.selectAll('path')
+ var paths = group.selectAll('path.line')
.filter(filter)
.data(lines, iD.Entity.key);
diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js
index ee8d2c5e2..935f84719 100644
--- a/js/id/svg/midpoints.js
+++ b/js/id/svg/midpoints.js
@@ -47,6 +47,8 @@ iD.svg.Midpoints = function(projection) {
groups.attr('transform', iD.svg.PointTransform(projection));
+ groups.select('circle');
+
groups.exit()
.remove();
};
diff --git a/js/id/ui/notice.js b/js/id/ui/notice.js
index bcf44b7c9..12b1a2c09 100644
--- a/js/id/ui/notice.js
+++ b/js/id/ui/notice.js
@@ -4,20 +4,17 @@ iD.ui.notice = function(selection) {
notice = {};
var div = selection.append('div')
- .attr('class', 'notice')
- .append('div')
- .attr('class', 'notice-inner');
+ .attr('class', 'notice');
- div.append('button')
- .attr('class', 'zoom-to')
- .on('click', function() {
- event.zoom();
- })
- .append('span')
- .attr('class', 'icon invert zoom-in');
+ var button = div.append('button')
+ .attr('class', 'zoom-to notice')
+ .on('click', event.zoom);
- div.append('span')
- .attr('class', 'notice-text')
+ button.append('span')
+ .attr('class', 'icon zoom-in-invert');
+
+ button.append('span')
+ .attr('class', 'label')
.text(t('zoom_in_edit'));
notice.message = function(_) {
diff --git a/js/id/ui/save.js b/js/id/ui/save.js
index 8784b2ca4..b8085e21a 100644
--- a/js/id/ui/save.js
+++ b/js/id/ui/save.js
@@ -60,7 +60,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));
+ controller.enter(iD.modes.Select([d.entity.id]));
modal.remove();
})
.on('save', commit));
diff --git a/js/id/graph/validate.js b/js/id/validate.js
similarity index 79%
rename from js/id/graph/validate.js
rename to js/id/validate.js
index 805b775c3..aa7d8771b 100644
--- a/js/id/graph/validate.js
+++ b/js/id/validate.js
@@ -21,22 +21,22 @@ iD.validate = function(changes, graph) {
if (change.geometry(graph) === 'point' && _.isEmpty(change.tags)) {
warnings.push({
- message: 'Untagged point which is not part of a line or area',
+ message: t('validations.untagged_point'),
entity: change
});
}
if (change.geometry(graph) === 'line' && _.isEmpty(change.tags)) {
- warnings.push({ message: 'Untagged line', entity: change });
+ warnings.push({ message: t('validations.untagged_line'), entity: change });
}
if (change.geometry(graph) === 'area' && _.isEmpty(change.tags)) {
- warnings.push({ message: 'Untagged area', entity: change });
+ warnings.push({ message: t('validations.untagged_area'), entity: change });
}
if (change.geometry(graph) === 'line' && tagSuggestsArea(change)) {
warnings.push({
- message: 'The tag ' + tagSuggestsArea(change) + ' suggests line should be area, but it is not and area',
+ message: t('validations.tag_suggests_area', {tag: tagSuggestsArea(change)}),
entity: change
});
}
diff --git a/js/lib/queue.js b/js/lib/queue.js
deleted file mode 100644
index 9a3b9da47..000000000
--- a/js/lib/queue.js
+++ /dev/null
@@ -1,84 +0,0 @@
-(function() {
- if (typeof module === "undefined") self.queue = queue;
- else module.exports = queue;
-
- queue.version = "1.0.0";
-
- function queue(parallelism) {
- var queue = {},
- active = 0, // number of in-flight deferrals
- remaining = 0, // number of deferrals remaining
- head, tail, // singly-linked list of deferrals
- error = null,
- results = [],
- await = noop,
- awaitAll;
-
- if (arguments.length < 1) parallelism = Infinity;
-
- queue.defer = function() {
- if (!error) {
- var node = arguments;
- node.index = results.push(undefined) - 1;
- if (tail) tail.next = node, tail = tail.next;
- else head = tail = node;
- ++remaining;
- pop();
- }
- return queue;
- };
-
- queue.await = function(f) {
- await = f;
- awaitAll = false;
- if (!remaining) notify();
- return queue;
- };
-
- queue.awaitAll = function(f) {
- await = f;
- awaitAll = true;
- if (!remaining) notify();
- return queue;
- };
-
- function pop() {
- if (head && active < parallelism) {
- var node = head,
- f = node[0],
- a = Array.prototype.slice.call(node, 1),
- i = node.index;
- if (head === tail) head = tail = null;
- else head = head.next;
- ++active;
- a.push(function(e, r) {
- --active;
- if (error != null) return;
- if (e != null) {
- // clearing remaining cancels subsequent callbacks
- // clearing head stops queued tasks from being executed
- // setting error ignores subsequent calls to defer
- error = e;
- remaining = results = head = tail = null;
- notify();
- } else {
- results[i] = r;
- if (--remaining) pop();
- else notify();
- }
- });
- f.apply(null, a);
- }
- }
-
- function notify() {
- if (error != null) await(error);
- else if (awaitAll) await(null, results);
- else await.apply(null, [null].concat(results));
- }
-
- return queue;
- }
-
- function noop() {}
-})();
diff --git a/locale/en.js b/locale/en.js
index 2a011a734..d7fc3628f 100644
--- a/locale/en.js
+++ b/locale/en.js
@@ -1,8 +1,118 @@
locale.en = {
- "browse": "Browse",
- "point": "Point",
- "line": "Line",
- "area": "Area",
+ modes: {
+ add_area: {
+ title: "Area",
+ description: "Add parks, buildings, lakes, or other areas to the map.",
+ tail: "Click on the map to start drawing an area, like a park, lake, or building.",
+ key: "A"
+ },
+ add_line: {
+ title: "Line",
+ description: "Lines can be highways, streets, pedestrian paths, or even canals.",
+ tail: "Click on the map to start drawing an road, path, or route.",
+ key: "L"
+ },
+ add_point: {
+ title: "Point",
+ description: "Restaurants, monuments, and postal boxes are points.",
+ tail: "Click on the map to add a point.",
+ key: "P"
+ },
+ browse: {
+ title: "Browse",
+ description: "Pan and zoom the map.",
+ key: "B"
+ },
+ draw_area: {
+ tail: "Click to add points to your area. Click the first point to finish the area."
+ },
+ draw_line: {
+ tail: "Click to add more points to the line. Click on other lines to connect to them, and double-click to end the line."
+ }
+ },
+
+ operations: {
+ add: {
+ annotation: {
+ point: "Added a point.",
+ vertex: "Added a node to a way."
+ }
+ },
+ start: {
+ annotation: {
+ line: "Started a line.",
+ area: "Started an area."
+ }
+ },
+ continue: {
+ annotation: {
+ line: "Continued a line.",
+ area: "Continued an area."
+ }
+ },
+ cancel_draw: {
+ annotation: "Cancelled drawing."
+ },
+ change_tags: {
+ annotation: "Changed tags."
+ },
+ circularize: {
+ title: "Circularize",
+ description: "Make this round.",
+ key: "O",
+ annotation: {
+ line: "Made a line circular.",
+ area: "Made an area circular."
+ }
+ },
+ delete: {
+ title: "Delete",
+ description: "Remove this from the map.",
+ key: "⌫",
+ annotation: {
+ point: "Deleted a point.",
+ vertex: "Deleted a node from a way.",
+ line: "Deleted a line.",
+ area: "Deleted an area."
+ }
+ },
+ move: {
+ title: "Move",
+ description: "Move this to a different location.",
+ key: "M",
+ annotation: {
+ point: "Moved a point.",
+ vertex: "Moved a node in a way.",
+ line: "Moved a line.",
+ area: "Moved an area."
+ }
+ },
+ reverse: {
+ title: "Reverse",
+ description: "Make this line go in the opposite direction.",
+ key: "V",
+ annotation: "Reversed a line."
+ },
+ split: {
+ title: "Split",
+ 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."
+ }
+ },
+
+ validations: {
+ untagged_point: "Untagged point which is not part of a line or area",
+ untagged_line: "Untagged line",
+ untagged_area: "Untagged area",
+ tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area"
+ },
"save": "Save",
"save_help": "Save changes to OpenStreetMap, making them visible to other users",
@@ -37,4 +147,4 @@ locale.en = {
"layers": "Layers",
"percent_opacity": "{opacity}% opacity"
-}
+};
diff --git a/locale/locale.js b/locale/locale.js
index 9e4786059..291b255a0 100644
--- a/locale/locale.js
+++ b/locale/locale.js
@@ -1,8 +1,12 @@
var locale = { current: 'en' };
function t(s, o) {
- if (locale[locale.current][s] !== undefined) {
- var rep = locale[locale.current][s];
+ var path = s.split(".").reverse(),
+ rep = locale[locale.current];
+
+ while (rep !== undefined && path.length) rep = rep[path.pop()];
+
+ if (rep !== undefined) {
if (o) for (var k in o) rep = rep.replace('{' + k + '}', o[k]);
return rep;
} else {
diff --git a/test/index.html b/test/index.html
index f8149a368..b85eb1b56 100644
--- a/test/index.html
+++ b/test/index.html
@@ -41,7 +41,6 @@
-
@@ -70,10 +69,9 @@
-
-
-
-
+
+
+
@@ -91,7 +89,9 @@
+
+
@@ -111,8 +111,6 @@
-
-
@@ -121,6 +119,7 @@
+
@@ -140,9 +139,8 @@
-
-
-
+
+
@@ -152,6 +150,7 @@
+
@@ -166,7 +165,6 @@
-
@@ -186,6 +184,7 @@
+
diff --git a/test/index_packaged.html b/test/index_packaged.html
index aba99ba04..60979e236 100644
--- a/test/index_packaged.html
+++ b/test/index_packaged.html
@@ -33,9 +33,8 @@
-
-
-
+
+
@@ -45,6 +44,7 @@
+
@@ -59,7 +59,6 @@
-
@@ -79,6 +78,7 @@
+
diff --git a/test/spec/actions/add_entity.js b/test/spec/actions/add_entity.js
new file mode 100644
index 000000000..1e2e092ca
--- /dev/null
+++ b/test/spec/actions/add_entity.js
@@ -0,0 +1,7 @@
+describe("iD.actions.AddEntity", function () {
+ it("adds an entity to the graph", function () {
+ var entity = iD.Entity(),
+ graph = iD.actions.AddEntity(entity)(iD.Graph());
+ expect(graph.entity(entity.id)).to.equal(entity);
+ });
+});
diff --git a/test/spec/actions/add_node.js b/test/spec/actions/add_node.js
deleted file mode 100644
index ae066edc5..000000000
--- a/test/spec/actions/add_node.js
+++ /dev/null
@@ -1,7 +0,0 @@
-describe("iD.actions.AddNode", function () {
- it("adds a node to the graph", function () {
- var node = iD.Node(),
- graph = iD.actions.AddNode(node)(iD.Graph());
- expect(graph.entity(node.id)).to.equal(node);
- });
-});
diff --git a/test/spec/actions/add_way.js b/test/spec/actions/add_way.js
deleted file mode 100644
index 19948c5b2..000000000
--- a/test/spec/actions/add_way.js
+++ /dev/null
@@ -1,7 +0,0 @@
-describe("iD.actions.AddWay", function () {
- it("adds a way to the graph", function () {
- var way = iD.Way(),
- graph = iD.actions.AddWay(way)(iD.Graph());
- expect(graph.entity(way.id)).to.equal(way);
- });
-});
diff --git a/test/spec/actions/change_entity_tags.js b/test/spec/actions/change_tags.js
similarity index 57%
rename from test/spec/actions/change_entity_tags.js
rename to test/spec/actions/change_tags.js
index bfa9830c6..0cc198230 100644
--- a/test/spec/actions/change_entity_tags.js
+++ b/test/spec/actions/change_tags.js
@@ -1,8 +1,8 @@
-describe("iD.actions.ChangeEntityTags", function () {
+describe("iD.actions.ChangeTags", function () {
it("changes an entity's tags", function () {
var entity = iD.Entity(),
tags = {foo: 'bar'},
- graph = iD.actions.ChangeEntityTags(entity.id, tags)(iD.Graph([entity]));
+ graph = iD.actions.ChangeTags(entity.id, tags)(iD.Graph([entity]));
expect(graph.entity(entity.id).tags).to.eql(tags);
});
});
diff --git a/test/spec/actions/delete_way.js b/test/spec/actions/delete_way.js
index 863b0fa2f..d550803fa 100644
--- a/test/spec/actions/delete_way.js
+++ b/test/spec/actions/delete_way.js
@@ -31,8 +31,7 @@ describe("iD.actions.DeleteWay", function () {
expect(graph.entity(node.id)).not.to.be.undefined;
});
- // See #508
- xit("deletes multiple member nodes", function () {
+ it("deletes multiple member nodes", function () {
var a = iD.Node(),
b = iD.Node(),
way = iD.Way({nodes: [a.id, b.id]}),
@@ -42,7 +41,7 @@ describe("iD.actions.DeleteWay", function () {
expect(graph.entity(b.id)).to.be.undefined;
});
- xit("deletes a circular way's start/end node", function () {
+ it("deletes a circular way's start/end node", function () {
var a = iD.Node(),
b = iD.Node(),
c = iD.Node(),
diff --git a/test/spec/renderer/hash.js b/test/spec/behavior/hash.js
similarity index 51%
rename from test/spec/renderer/hash.js
rename to test/spec/behavior/hash.js
index b0e5b567f..7616bf536 100644
--- a/test/spec/renderer/hash.js
+++ b/test/spec/behavior/hash.js
@@ -1,60 +1,41 @@
-describe("iD.Hash", function () {
+describe("iD.behavior.Hash", function () {
var hash, map, controller;
beforeEach(function () {
- hash = iD.Hash();
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]; }
};
+
controller = {
on: function () { return controller; }
};
+
+ hash = iD.behavior.Hash(controller, map);
});
afterEach(function () {
- hash.map(null);
- location.hash = "";
+ hash.off();
});
- describe("#map()", function () {
- it("gets and sets map", function () {
- expect(hash.controller(controller).map(map)).to.equal(hash);
- expect(hash.map()).to.equal(map);
- });
+ 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("sets hadHash if location.hash is present", function () {
- location.hash = "map=20.00/38.87952/-77.02405";
- hash.map(map);
- 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.map(map);
- expect(map.centerZoom).to.have.been.calledWith([-77.02405,38.87952], 20.0);
- });
-
- it("binds the map's move event", function () {
- sinon.spy(map, 'on');
- hash.map(map);
- expect(map.on).to.have.been.calledWith('move.hash', sinon.match.instanceOf(Function));
- });
-
- it("unbinds the map's move event", function () {
- sinon.spy(map, 'on');
- hash.map(map);
- hash.map(null);
- expect(map.on).to.have.been.calledWith('move.hash', null);
- });
+ 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);
});
describe("on window hashchange events", function () {
beforeEach(function () {
- hash.map(map);
+ hash();
});
function onhashchange(fn) {
@@ -77,7 +58,7 @@ describe("iD.Hash", function () {
sinon.stub(map, 'on')
.withArgs("move.hash", sinon.match.instanceOf(Function))
.yields();
- hash.map(map);
+ hash();
expect(location.hash).to.equal("#map=0.00/0/0");
});
});
diff --git a/test/spec/geo.js b/test/spec/geo.js
new file mode 100644
index 000000000..113d4795d
--- /dev/null
+++ b/test/spec/geo.js
@@ -0,0 +1,96 @@
+describe('iD.geo', function() {
+ describe('.roundCoords', function() {
+ expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
+ expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]);
+ expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
+ });
+
+ describe('.interp', function() {
+ it('interpolates halfway', function() {
+ var a = [0, 0],
+ b = [10, 10];
+ expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]);
+ });
+ it('interpolates to one side', function() {
+ var a = [0, 0],
+ b = [10, 10];
+ expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]);
+ });
+ });
+
+ describe('.dist', function() {
+ it('distance between two same points is zero', function() {
+ var a = [0, 0],
+ b = [0, 0];
+ expect(iD.geo.dist(a, b)).to.eql(0);
+ });
+ it('a straight 10 unit line is 10', function() {
+ var a = [0, 0],
+ b = [10, 0];
+ expect(iD.geo.dist(a, b)).to.eql(10);
+ });
+ it('a pythagorean triangle is right', function() {
+ var a = [0, 0],
+ b = [4, 3];
+ expect(iD.geo.dist(a, b)).to.eql(5);
+ });
+ });
+
+ describe('.pointInPolygon', function() {
+ it('says a point in a polygon is on a polygon', function() {
+ var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
+ var point = [0.5, 0.5];
+ expect(iD.geo.pointInPolygon(point, poly)).to.be.true;
+ });
+ it('says a point outside of a polygon is outside', function() {
+ var poly = [
+ [0, 0],
+ [0, 1],
+ [1, 1],
+ [1, 0],
+ [0, 0]];
+ var point = [0.5, 1.5];
+ expect(iD.geo.pointInPolygon(point, poly)).to.be.false;
+ });
+ });
+
+ describe('.polygonContainsPolygon', function() {
+ it('says a polygon in a polygon is in', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true;
+ });
+ it('says a polygon outside of a polygon is out', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false;
+ });
+ });
+
+ describe('.polygonIntersectsPolygon', function() {
+ it('says a polygon in a polygon intersects it', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
+ });
+
+ it('says a polygon that partially intersects does', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
+ });
+
+ it('says totally disjoint polygons do not intersect', function() {
+ var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
+ var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
+ expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
+ });
+ });
+
+ describe('.pathLength', function() {
+ it('calculates a simple path length', function() {
+ var path = [[0, 0], [0, 1], [3, 5]];
+ expect(iD.geo.pathLength(path)).to.eql(6);
+ });
+ });
+});
diff --git a/test/spec/graph/graph.js b/test/spec/graph/graph.js
index 327d66295..05bcbbe89 100644
--- a/test/spec/graph/graph.js
+++ b/test/spec/graph/graph.js
@@ -345,12 +345,42 @@ describe('iD.Graph', function() {
expect(graph2.difference(graph1)).to.eql([created.id, updated.id, deleted.id]);
});
- it("includes created entities that were subsequently deleted", function () {
+
+ it("includes created entities, and reverse", function () {
var node = iD.Node(),
- graph1 = iD.Graph([node]),
- graph2 = graph1.remove(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 () {
diff --git a/test/spec/modes/add_point.js b/test/spec/modes/add_point.js
index 68fe80403..a284cf682 100644
--- a/test/spec/modes/add_point.js
+++ b/test/spec/modes/add_point.js
@@ -28,7 +28,7 @@ describe("iD.modes.AddPoint", function () {
it("selects the node", function () {
happen.click(map.surface.node(), {});
expect(controller.mode.id).to.equal('select');
- expect(controller.mode.entity).to.equal(history.changes().created[0]);
+ expect(controller.mode.selection()).to.eql([history.changes().created[0].id]);
});
});
diff --git a/test/spec/util.js b/test/spec/util.js
index 9d5cf082b..de3fab735 100644
--- a/test/spec/util.js
+++ b/test/spec/util.js
@@ -1,123 +1,24 @@
describe('iD.Util', function() {
- var util;
-
- it('#trueObj', function() {
+ it('.trueObj', function() {
expect(iD.util.trueObj(['a', 'b', 'c'])).to.eql({ a: true, b: true, c: true });
expect(iD.util.trueObj([])).to.eql({});
});
- it('#tagText', function() {
+ it('.tagText', function() {
expect(iD.util.tagText({})).to.eql('');
expect(iD.util.tagText({tags:{foo:'bar'}})).to.eql('foo: bar');
expect(iD.util.tagText({tags:{foo:'bar',two:'three'}})).to.eql('foo: bar\ntwo: three');
});
- it('#stringQs', function() {
+ it('.stringQs', function() {
expect(iD.util.stringQs('foo=bar')).to.eql({foo: 'bar'});
expect(iD.util.stringQs('foo=bar&one=2')).to.eql({foo: 'bar', one: '2' });
expect(iD.util.stringQs('')).to.eql({});
});
- it('#qsString', function() {
+ it('.qsString', function() {
expect(iD.util.qsString({ foo: 'bar' })).to.eql('foo=bar');
expect(iD.util.qsString({ foo: 'bar', one: 2 })).to.eql('foo=bar&one=2');
expect(iD.util.qsString({})).to.eql('');
});
-
- describe('geo', function() {
- describe('#roundCoords', function() {
- expect(iD.geo.roundCoords([0.1, 1])).to.eql([0, 1]);
- expect(iD.geo.roundCoords([0, 1])).to.eql([0, 1]);
- expect(iD.geo.roundCoords([0, 1.1])).to.eql([0, 1]);
- });
-
- describe('#interp', function() {
- it('interpolates halfway', function() {
- var a = [0, 0],
- b = [10, 10];
- expect(iD.geo.interp(a, b, 0.5)).to.eql([5, 5]);
- });
- it('interpolates to one side', function() {
- var a = [0, 0],
- b = [10, 10];
- expect(iD.geo.interp(a, b, 0)).to.eql([0, 0]);
- });
- });
-
- describe('#dist', function() {
- it('distance between two same points is zero', function() {
- var a = [0, 0],
- b = [0, 0];
- expect(iD.geo.dist(a, b)).to.eql(0);
- });
- it('a straight 10 unit line is 10', function() {
- var a = [0, 0],
- b = [10, 0];
- expect(iD.geo.dist(a, b)).to.eql(10);
- });
- it('a pythagorean triangle is right', function() {
- var a = [0, 0],
- b = [4, 3];
- expect(iD.geo.dist(a, b)).to.eql(5);
- });
- });
-
- describe('#pointInPolygon', function() {
- it('says a point in a polygon is on a polygon', function() {
- var poly = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
- var point = [0.5, 0.5];
- expect(iD.geo.pointInPolygon(point, poly)).to.be.true;
- });
- it('says a point outside of a polygon is outside', function() {
- var poly = [
- [0, 0],
- [0, 1],
- [1, 1],
- [1, 0],
- [0, 0]];
- var point = [0.5, 1.5];
- expect(iD.geo.pointInPolygon(point, poly)).to.be.false;
- });
- });
-
- describe('#polygonContainsPolygon', function() {
- it('says a polygon in a polygon is in', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.true;
- });
- it('says a polygon outside of a polygon is out', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 9], [2, 2], [2, 1], [1, 1]];
- expect(iD.geo.polygonContainsPolygon(outer, inner)).to.be.false;
- });
- });
-
- describe('#polygonIntersectsPolygon', function() {
- it('says a polygon in a polygon intersects it', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[1, 1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
- });
-
- it('says a polygon that partially intersects does', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[-1, -1], [1, 2], [2, 2], [2, 1], [1, 1]];
- expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.true;
- });
-
- it('says totally disjoint polygons do not intersect', function() {
- var outer = [[0, 0], [0, 3], [3, 3], [3, 0], [0, 0]];
- var inner = [[-1, -1], [-1, -2], [-2, -2], [-2, -1], [-1, -1]];
- expect(iD.geo.polygonIntersectsPolygon(outer, inner)).to.be.false;
- });
- });
-
- describe('#pathLength', function() {
- it('calculates a simple path length', function() {
- var path = [[0, 0], [0, 1], [3, 5]];
- expect(iD.geo.pathLength(path)).to.eql(6);
- });
- });
- });
});