diff --git a/css/app.css b/css/app.css
index 468478091..90401612c 100644
--- a/css/app.css
+++ b/css/app.css
@@ -1064,7 +1064,17 @@ div.typeahead a:first-child {
}
.Browse .tooltip .tooltip-arrow {
- left: 30px;
- }
-
+ left: 30px;
+}
+.tail {
+ pointer-events:none;
+ position: absolute;
+ background: rgba(255, 255, 255, 0.7);
+ max-width: 250px;
+ margin-top: -15px;
+ padding: 5px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
diff --git a/css/map.css b/css/map.css
index 590cc114e..b2929f149 100644
--- a/css/map.css
+++ b/css/map.css
@@ -65,9 +65,12 @@ g.vertex circle.stroke {
fill:#333;
}
-g.vertex.shared circle {
+g.vertex.shared circle.fill {
fill:#aff;
}
+g.vertex.shared circle.stroke {
+ fill:#044;
+}
g.vertex.hover circle.fill {
-webkit-transform:scale(1.5, 1.5);
@@ -81,9 +84,12 @@ g.vertex.hover circle.stroke {
transform:scale(1.4, 1.4);
}
-g.vertex circle.selected {
+g.vertex circle.selected.fill {
fill: #ffff00;
}
+g.vertex circle.selected.stroke {
+ fill: #38380A;
+}
circle.midpoint {
fill:#aaa;
@@ -252,9 +258,6 @@ path.casing.tag-highway-secondary_link {
stroke:#444;
}
-path.stroke.tag-bridge-yes {
- stroke:#eee;
-}
path.casing.tag-bridge-yes {
stroke-width: 14;
stroke: #000;
diff --git a/index.html b/index.html
index 8e86da888..9db659489 100644
--- a/index.html
+++ b/index.html
@@ -22,6 +22,7 @@
+
@@ -31,7 +32,6 @@
-
diff --git a/js/id/connection.js b/js/id/connection.js
index 101c7a9c8..003750ff2 100644
--- a/js/id/connection.js
+++ b/js/id/connection.js
@@ -16,6 +16,7 @@ iD.Connection = function() {
function bboxFromAPI(box, tile, callback) {
function done(err, parsed) {
loadedTiles[tile.toString()] = true;
+ delete inflight[tile.toString()];
callback(err, parsed);
}
inflight[tile.toString()] = loadFromURL(bboxUrl(box), done);
@@ -26,8 +27,6 @@ iD.Connection = function() {
return callback(null, parse(dom));
}
return d3.xml(url).get().on('load', done);
- inflight.push(d3.xml(url).get()
- .on('load', done));
}
function getNodes(obj) {
@@ -201,7 +200,7 @@ iD.Connection = function() {
}
function loadTiles(projection) {
- var scaleExtent = [16, 16],
+ var scaleExtent = [15, 15],
s = projection.scale(),
tiles = d3.geo.tile()
.scaleExtent(scaleExtent)
diff --git a/js/id/graph/graph.js b/js/id/graph/graph.js
index d1f5e4cc7..74caf7ceb 100644
--- a/js/id/graph/graph.js
+++ b/js/id/graph/graph.js
@@ -13,6 +13,7 @@ iD.Graph = function(entities) {
this.transients = {};
this._parentWays = {};
this._parentRels = {};
+ this._fetches = {};
if (iD.debug) {
Object.freeze(this);
@@ -119,20 +120,29 @@ iD.Graph.prototype = {
// Resolve the id references in a way, replacing them with actual objects.
fetch: function(id) {
+ if (this._fetches[id]) return this._fetches[id];
var entity = this.entities[id], nodes = [];
if (!entity || !entity.nodes || !entity.nodes.length) return entity;
for (var i = 0, l = entity.nodes.length; i < l; i++) {
nodes[i] = this.fetch(entity.nodes[i]);
}
- return iD.Entity(entity, {nodes: nodes});
+ return (this._fetches[id] = iD.Entity(entity, {nodes: nodes}));
},
difference: function (graph) {
- var result = [], entity, id;
+ var result = [], entity, oldentity, id;
for (id in this.entities) {
entity = this.entities[id];
- if (entity !== graph.entities[id]) {
+ oldentity = graph.entities[id];
+ if (entity !== oldentity) {
+ if (entity && entity.type === 'way') {
+ result = oldentity ?
+ result
+ .concat(_.difference(entity.nodes, oldentity.nodes))
+ .concat(_.difference(oldentity.nodes, entity.nodes))
+ : result.concat(entity.nodes);
+ }
result.push(id);
}
}
@@ -141,6 +151,7 @@ iD.Graph.prototype = {
entity = graph.entities[id];
if (entity && !this.entities.hasOwnProperty(id)) {
result.push(id);
+ if (entity.type === 'way') result = result.concat(entity.nodes);
}
}
diff --git a/js/id/graph/way.js b/js/id/graph/way.js
index 77858d29a..5c3e1210b 100644
--- a/js/id/graph/way.js
+++ b/js/id/graph/way.js
@@ -6,7 +6,8 @@ iD.Way = iD.Entity.extend({
return resolver.transient(this, 'extent', function() {
var extent = [[-Infinity, Infinity], [Infinity, -Infinity]];
for (var i = 0, l = this.nodes.length; i < l; i++) {
- var node = resolver.entity(this.nodes[i]);
+ var node = this.nodes[i];
+ if (node.loc === undefined) node = resolver.entity(node);
if (node.loc[0] > extent[0][0]) extent[0][0] = node.loc[0];
if (node.loc[0] < extent[1][0]) extent[1][0] = node.loc[0];
if (node.loc[1] < extent[0][1]) extent[0][1] = node.loc[1];
diff --git a/js/id/id.js b/js/id/id.js
index a6af4242a..e5e6b0eb0 100644
--- a/js/id/id.js
+++ b/js/id/id.js
@@ -183,9 +183,9 @@ window.iD = function(container) {
.call(redo ? refreshTooltip : undo_tooltip.hide);
});
- window.onresize = function() {
+ d3.select(window).on('resize.map-size', function() {
map.size(m.size());
- };
+ });
map.keybinding()
.on('a', function(evt, mods) {
diff --git a/js/id/modes/add_area.js b/js/id/modes/add_area.js
index 33facb041..a9fcb830e 100644
--- a/js/id/modes/add_area.js
+++ b/js/id/modes/add_area.js
@@ -12,7 +12,7 @@ iD.modes.AddArea = function() {
controller = mode.controller;
map.dblclickEnable(false)
- .hint('Click on the map to start drawing an area, like a park, lake, or building.');
+ .tail('Click on the map to start drawing an area, like a park, lake, or building.');
map.surface.on('click.addarea', function() {
var datum = d3.select(d3.event.target).datum() || {},
@@ -23,8 +23,7 @@ iD.modes.AddArea = function() {
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, datum.id),
- iD.actions.AddWayNode(way.id, datum.id),
- 'started an area');
+ iD.actions.AddWayNode(way.id, datum.id));
} else {
// start from a new node
@@ -33,8 +32,7 @@ iD.modes.AddArea = function() {
iD.actions.AddWay(way),
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id),
- iD.actions.AddWayNode(way.id, node.id),
- 'started an area');
+ iD.actions.AddWayNode(way.id, node.id));
}
controller.enter(iD.modes.DrawArea(way.id));
@@ -49,7 +47,7 @@ iD.modes.AddArea = function() {
window.setTimeout(function() {
mode.map.dblclickEnable(true);
}, 1000);
- mode.map.hint(false);
+ mode.map.tail(false);
mode.map.surface.on('click.addarea', null);
mode.map.keybinding().on('⎋.addarea', null);
};
diff --git a/js/id/modes/add_line.js b/js/id/modes/add_line.js
index bc87238ca..b83202097 100644
--- a/js/id/modes/add_line.js
+++ b/js/id/modes/add_line.js
@@ -13,7 +13,7 @@ iD.modes.AddLine = function() {
controller = mode.controller;
map.dblclickEnable(false)
- .hint('Click on the map to start drawing an road, path, or route.');
+ .tail('Click on the map to start drawing an road, path, or route.');
map.surface.on('click.addline', function() {
var datum = d3.select(d3.event.target).datum() || {},
@@ -33,8 +33,7 @@ iD.modes.AddLine = function() {
} else {
history.perform(
iD.actions.AddWay(way),
- iD.actions.AddWayNode(way.id, datum.id),
- 'started a line');
+ iD.actions.AddWayNode(way.id, datum.id));
}
} else if (datum.type === 'way') {
@@ -46,8 +45,7 @@ iD.modes.AddLine = function() {
iD.actions.AddWay(way),
iD.actions.AddNode(node),
iD.actions.AddWayNode(datum.id, node.id, choice.index),
- iD.actions.AddWayNode(way.id, node.id),
- 'started a line');
+ iD.actions.AddWayNode(way.id, node.id));
} else {
// begin a new way
@@ -56,8 +54,7 @@ iD.modes.AddLine = function() {
history.perform(
iD.actions.AddWay(way),
iD.actions.AddNode(node),
- iD.actions.AddWayNode(way.id, node.id),
- 'started a line');
+ iD.actions.AddWayNode(way.id, node.id));
}
controller.enter(iD.modes.DrawLine(way.id, direction));
@@ -70,7 +67,7 @@ iD.modes.AddLine = function() {
mode.exit = function() {
mode.map.dblclickEnable(true);
- mode.map.hint(false);
+ mode.map.tail(false);
mode.map.surface.on('click.addline', null);
mode.map.keybinding().on('⎋.addline', null);
};
diff --git a/js/id/modes/add_point.js b/js/id/modes/add_point.js
index 37767f3de..21d6343e0 100644
--- a/js/id/modes/add_point.js
+++ b/js/id/modes/add_point.js
@@ -10,7 +10,7 @@ iD.modes.AddPoint = function() {
history = mode.history,
controller = mode.controller;
- map.hint('Click on the map to add a point.');
+ map.tail('Click on the map to add a point.');
map.surface.on('click.addpoint', function() {
var node = iD.Node({loc: map.mouseCoordinates(), _poi: true});
@@ -28,7 +28,7 @@ iD.modes.AddPoint = function() {
};
mode.exit = function() {
- mode.map.hint(false);
+ mode.map.tail(false);
mode.map.surface.on('click.addpoint', null);
mode.map.keybinding().on('⎋.addpoint', null);
};
diff --git a/js/id/modes/draw_area.js b/js/id/modes/draw_area.js
index d868a7620..cdb966351 100644
--- a/js/id/modes/draw_area.js
+++ b/js/id/modes/draw_area.js
@@ -18,8 +18,7 @@ iD.modes.DrawArea = function(wayId) {
map.dblclickEnable(false)
.fastEnable(false);
- map.hint('Click on the map to add points to your area. Finish the ' +
- 'area by clicking on your first point');
+ map.tail('Click to add points to your area. Click the first point to finish the area.');
history.perform(
iD.actions.AddNode(node),
@@ -42,22 +41,28 @@ iD.modes.DrawArea = function(wayId) {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === tailId || datum.id === headId) {
- history.replace(iD.actions.DeleteNode(node.id));
- controller.enter(iD.modes.Select(way));
+ if (way.nodes.length > 3) {
+ history.undo();
+ controller.enter(iD.modes.Select(way));
+ } else {
+ // Areas with less than 3 nodes gets deleted
+ history.replace(iD.actions.DeleteWay(way.id));
+ controller.enter(iD.modes.Browse());
+ }
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.AddWayNode(way.id, datum.id, -1),
- 'added to an area');
+ way.nodes.length > 2 ? 'added to an area' : '');
controller.enter(iD.modes.DrawArea(wayId));
} else {
history.replace(
iD.actions.Noop(),
- 'added to an area');
+ way.nodes.length > 2 ? 'added to an area' : '');
controller.enter(iD.modes.DrawArea(wayId));
}
@@ -110,7 +115,7 @@ iD.modes.DrawArea = function(wayId) {
surface.selectAll('.way, .node')
.classed('active', false);
- mode.map.hint(false);
+ mode.map.tail(false);
mode.map.fastEnable(true);
surface
diff --git a/js/id/modes/draw_line.js b/js/id/modes/draw_line.js
index 9c3d4b38e..68130d7b8 100644
--- a/js/id/modes/draw_line.js
+++ b/js/id/modes/draw_line.js
@@ -19,7 +19,7 @@ iD.modes.DrawLine = function(wayId, direction) {
map.dblclickEnable(false)
.fastEnable(false)
- .hint('Click to add more points to the 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.');
@@ -42,16 +42,22 @@ iD.modes.DrawLine = function(wayId, direction) {
if (datum.id === tailId) {
// connect the way in a loop
- history.replace(
- iD.actions.DeleteNode(node.id),
- iD.actions.AddWayNode(wayId, tailId, index),
- 'added to a line');
+ if (way.nodes.length > 2) {
+ history.replace(
+ iD.actions.DeleteNode(node.id),
+ iD.actions.AddWayNode(wayId, tailId, index),
+ 'added to a line');
- controller.enter(iD.modes.Select(way));
+ controller.enter(iD.modes.Select(way));
+
+ } else {
+ history.replace(iD.actions.DeleteWay(way.id));
+ controller.enter(iD.modes.Browse());
+ }
} else if (datum.id === headId) {
// finish the way
- history.replace(iD.actions.DeleteNode(node.id));
+ history.undo();
controller.enter(iD.modes.Select(way));
@@ -146,7 +152,7 @@ iD.modes.DrawLine = function(wayId, direction) {
surface.selectAll('.way, .node')
.classed('active', false);
- mode.map.hint(false);
+ mode.map.tail(false);
mode.map.fastEnable(true);
mode.map.minzoom(0);
diff --git a/js/id/modes/select.js b/js/id/modes/select.js
index a1fde48ef..d091fdc57 100644
--- a/js/id/modes/select.js
+++ b/js/id/modes/select.js
@@ -87,11 +87,12 @@ iD.modes.Select = function (entity) {
// Exit mode if selected entity gets undone
mode.history.on('change.entity-undone', function() {
+ var old = entity;
entity = mode.history.graph().entity(entity.id);
if (!entity) {
mode.controller.enter(iD.modes.Browse());
- } else {
- d3.select('.inspector-wrap').datum(entity).call(inspector);
+ } else if(!_.isEqual(entity.tags, old.tags)) {
+ inspector.tags(entity.tags);
}
});
@@ -138,7 +139,7 @@ iD.modes.Select = function (entity) {
mode.exit = function () {
var surface = mode.map.surface;
- changeTags(entity, inspector.tags());
+ entity && changeTags(entity, inspector.tags());
d3.select('.inspector-wrap')
.style('display', 'none')
.html('');
diff --git a/js/id/renderer/background.js b/js/id/renderer/background.js
index 76188c79f..f861d2226 100644
--- a/js/id/renderer/background.js
+++ b/js/id/renderer/background.js
@@ -55,14 +55,36 @@ iD.Background = function() {
ups = {};
tiles.forEach(function(d) {
- d.push(source(d));
- // this tile has not been loaded yet
- if (!cache[d] &&
- cache[atZoom(d, -1)] &&
+
+ // if this tile has already failed, do
+ // not request it
+ if (cache[d] !== false) d.push(source(d));
+
+ // if this tile has failed, try to request its tile above
+ 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]) {
+
+ // if this tile has not finished, req the one above
+ } else if (cache[d] === undefined &&
+
+ // but the tile above is in the cache
+ cache[atZoom(d, -1)] &&
+
+ // and another tile has not already requested the
+ // tile above
+ !ups[atZoom(d, -1)]) {
+
+ ups[atZoom(d, -1)] = true;
+ tiles.push(atZoom(d, -1));
+
+ // if this tile has not yet completed, try keeping the
+ // tiles below it
+ } else if (cache[d] === undefined ||
+ cache[d] === false) {
upZoom(d, 1).forEach(function(u) {
if (cache[u] && !ups[u]) {
ups[u] = true;
@@ -79,11 +101,12 @@ iD.Background = function() {
image.exit().remove();
function load(d) {
- cache[d] = true;
+ cache[d.slice(0, 3)] = true;
d3.select(this).on('load', null);
}
- function error() {
+ function error(d) {
+ cache[d.slice(0, 3)] = false;
d3.select(this).remove();
}
diff --git a/js/id/renderer/map.js b/js/id/renderer/map.js
index 3f64886b6..9edb347e4 100644
--- a/js/id/renderer/map.js
+++ b/js/id/renderer/map.js
@@ -21,6 +21,7 @@ iD.Map = function() {
lines = iD.svg.Lines(),
areas = iD.svg.Areas(),
midpoints = iD.svg.Midpoints(),
+ tail = d3.tail(),
surface, tilegroup;
function map(selection) {
@@ -29,14 +30,14 @@ iD.Map = function() {
var supersurface = selection.append('div')
.style('position', 'absolute')
+ .on('mousedown.drag', function() {
+ translateStart = projection.translate();
+ })
.call(zoom);
surface = supersurface.append('svg')
.on('mouseup.reset-transform', resetTransform)
.on('touchend.reset-transform', resetTransform)
- .on('mousedown.drag', function() {
- translateStart = projection.translate();
- })
.on('mousedown.zoom', function() {
if (d3.event.button == 2) {
d3.event.stopPropagation();
@@ -44,9 +45,13 @@ iD.Map = function() {
})
.call(iD.svg.Surface());
+
map.size(selection.size());
map.surface = surface;
+ supersurface
+ .call(tail);
+
d3.select(document).call(keybinding);
}
@@ -62,16 +67,22 @@ iD.Map = function() {
all = graph.intersects(extent);
filter = d3.functor(true);
} else {
- var only = {};
+ var only = {},
+ filterOnly = {};
for (var j = 0; j < difference.length; j++) {
- var id = difference[j];
- only[id] = graph.fetch(id);
- if (only[id] && only[id].type === 'node') {
- var parents = graph.parentWays(only[id]);
- for (var k = 0; k < parents.length; k++) {
- // Don't re-fetch parents
- if (only[parents[k].id] === undefined) {
- only[parents[k].id] = graph.fetch(parents[k].id);
+ var id = difference[j],
+ entity = graph.fetch(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)) {
+ if (only[id].type === 'node') {
+ var parents = graph.parentWays(only[id]);
+ for (var k = 0; k < parents.length; k++) {
+ // Don't re-fetch parents
+ if (only[parents[k].id] === undefined) {
+ only[parents[k].id] = graph.fetch(parents[k].id);
+ }
}
}
}
@@ -80,7 +91,7 @@ iD.Map = function() {
filter = function(d) { return d.midpoint ? d.way in only : d.id in only; };
}
- if (all.length > 10000) {
+ if (all.length > 100000) {
editOff();
return;
}
@@ -113,14 +124,14 @@ iD.Map = function() {
if (Math.log(d3.event.scale / Math.LN2 - 8) < minzoom + 1) {
iD.flash()
.select('.content')
- .text('Cannot zoom out further in current mode.')
+ .text('Cannot zoom out further in current mode.');
return map.zoom(16);
}
var fast = (d3.event.scale === projection.scale() && fastEnabled);
projection
.translate(d3.event.translate)
.scale(d3.event.scale);
- if (fast) {
+ if (fast && translateStart) {
var a = d3.event.translate,
b = translateStart,
translate = 'translate(' + ~~(a[0] - b[0]) + 'px,' +
@@ -278,6 +289,11 @@ iD.Map = function() {
return map;
};
+ map.tail = function (_) {
+ tail.text(_);
+ return map;
+ };
+
map.hint = function (_) {
if (_ === false) {
d3.select('div.inspector-wrap')
diff --git a/js/id/renderer/style.js b/js/id/renderer/style.js
deleted file mode 100644
index 734ce10ca..000000000
--- a/js/id/renderer/style.js
+++ /dev/null
@@ -1,38 +0,0 @@
-iD.Style = {};
-
-// all styling that is done outside of CSS in iD.
-//
-// Since SVG does not support z-index, we sort roads manually with d3's `sort`
-// and the `waystack` fn.
-//
-// This also chooses kosher CSS classes for ways, and images for points
-
-iD.Style.highway_stack = {
- motorway: 0,
- motorway_link: 1,
- trunk: 2,
- trunk_link: 3,
- primary: 4,
- primary_link: 5,
- secondary: 6,
- tertiary: 7,
- unclassified: 8,
- residential: 9,
- service: 10,
- footway: 11
-};
-
-iD.Style.waystack = function(a, b) {
- if (!a || !b) return 0;
- if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
- return a.tags.layer - b.tags.layer;
- }
- if (a.tags.bridge) return 1;
- if (b.tags.bridge) return -1;
- var as = 0, bs = 0;
- if (a.tags.highway && b.tags.highway) {
- as -= iD.Style.highway_stack[a.tags.highway];
- bs -= iD.Style.highway_stack[b.tags.highway];
- }
- return as - bs;
-};
diff --git a/js/id/services/taginfo.js b/js/id/services/taginfo.js
index 20f1cd09a..aa89a37fa 100644
--- a/js/id/services/taginfo.js
+++ b/js/id/services/taginfo.js
@@ -33,6 +33,25 @@ iD.taginfo = function() {
return _.omit(parameters, 'geometry');
}
+ function popularKeys(parameters) {
+ var pop_field = 'count_all_fraction';
+ if (parameters.filter) pop_field = 'count_' + parameters.filter + '_fraction';
+ return function(d) { return parseFloat(d[pop_field]) > 0.01; };
+ }
+
+ function popularValues(parameters) {
+ return function(d) { return parseFloat(d['fraction']) > 0.01; };
+ }
+
+ function valKey(d) { return { value: d.key }; }
+
+ function valKeyDescription(d) {
+ return {
+ value: d.value,
+ title: d.description
+ };
+ }
+
taginfo.keys = function(parameters, callback) {
parameters = clean(setSort(setFilter(parameters)));
d3.json(endpoint + 'keys/all?' +
@@ -41,7 +60,10 @@ iD.taginfo = function() {
sortname: 'count_all',
sortorder: 'desc',
page: 1
- }, parameters)), callback);
+ }, parameters)), function(err, d) {
+ if (err) return callback(err);
+ callback(null, d.data.filter(popularKeys(parameters)).map(valKey));
+ });
};
taginfo.values = function(parameters, callback) {
@@ -52,7 +74,10 @@ iD.taginfo = function() {
sortname: 'count_all',
sortorder: 'desc',
page: 1
- }, parameters)), callback);
+ }, parameters)), function(err, d) {
+ if (err) return callback(err);
+ callback(null, d.data.filter(popularValues()).map(valKeyDescription));
+ });
};
taginfo.docs = function(parameters, callback) {
diff --git a/js/id/svg.js b/js/id/svg.js
index fd648f5db..5860126e9 100644
--- a/js/id/svg.js
+++ b/js/id/svg.js
@@ -2,13 +2,13 @@ iD.svg = {
RoundProjection: function (projection) {
return function (d) {
return iD.util.geo.roundCoords(projection(d));
- }
+ };
},
PointTransform: function (projection) {
projection = iD.svg.RoundProjection(projection);
return function (entity) {
return 'translate(' + projection(entity.loc) + ')';
- }
+ };
}
};
diff --git a/js/id/svg/areas.js b/js/id/svg/areas.js
index 386f5f0c3..ff24afecc 100644
--- a/js/id/svg/areas.js
+++ b/js/id/svg/areas.js
@@ -1,5 +1,32 @@
iD.svg.Areas = function() {
- return function(surface, graph, entities, filter, projection) {
+
+ var area_stack = {
+ building: 0,
+ manmade: 1,
+ natural: 1,
+ boundary: 2
+ };
+
+ function findKey(a) {
+ var vals = Object.keys(a.tags).filter(function(k) {
+ return area_stack[k] !== undefined;
+ });
+ if (vals.length > 0) return area_stack[vals[0]];
+ else return -1;
+ }
+
+ function areastack(a, b) {
+ if (!a || !b || !a.tags || !b.tags) return 0;
+ if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
+ return a.tags.layer - b.tags.layer;
+ }
+ var as = 0, bs = 0;
+ as -= findKey(a);
+ bs -= findKey(b);
+ return as - bs;
+ }
+
+ return function drawAreas(surface, graph, entities, filter, projection) {
var areas = [];
for (var i = 0; i < entities.length; i++) {
@@ -9,6 +36,8 @@ iD.svg.Areas = function() {
}
}
+ areas.sort(areastack);
+
var lineStrings = {};
function lineString(entity) {
diff --git a/js/id/svg/lines.js b/js/id/svg/lines.js
index 3a64c1b28..3f1b634d7 100644
--- a/js/id/svg/lines.js
+++ b/js/id/svg/lines.js
@@ -1,16 +1,68 @@
iD.svg.Lines = function() {
- var arrowtext = '►\u3000\u3000',
- alength;
+ var arrowtext = '►\u3000\u3000',
+ alength;
+
+ var highway_stack = {
+ motorway: 0,
+ motorway_link: 1,
+ trunk: 2,
+ trunk_link: 3,
+ primary: 4,
+ primary_link: 5,
+ secondary: 6,
+ tertiary: 7,
+ unclassified: 8,
+ residential: 9,
+ service: 10,
+ footway: 11
+ };
+
+ function waystack(a, b) {
+ if (!a || !b || !a.tags || !b.tags) return 0;
+ if (a.tags.layer !== undefined && b.tags.layer !== undefined) {
+ return a.tags.layer - b.tags.layer;
+ }
+ if (a.tags.bridge) return 1;
+ if (b.tags.bridge) return -1;
+ var as = 0, bs = 0;
+ if (a.tags.highway && b.tags.highway) {
+ as -= highway_stack[a.tags.highway];
+ bs -= highway_stack[b.tags.highway];
+ }
+ return as - bs;
+ }
+
+ function drawPaths(group, lines, filter, classes, lineString) {
+ var paths = group.selectAll('path')
+ .filter(filter)
+ .data(lines, iD.Entity.key);
+
+ paths.enter()
+ .append('path')
+ .attr('class', classes);
+
+ paths
+ .order()
+ .attr('d', lineString)
+ .call(iD.svg.TagClasses());
+
+ paths.exit()
+ .remove();
+
+ return paths;
+ }
+
+ return function drawLines(surface, graph, entities, filter, projection) {
- return function(surface, graph, entities, filter, projection) {
if (!alength) {
var arrow = surface.append('text').text(arrowtext);
alength = arrow.node().getComputedTextLength();
arrow.remove();
}
- var lines = [];
+ var lines = [],
+ lineStrings = {};
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
@@ -19,7 +71,7 @@ iD.svg.Lines = function() {
}
}
- var lineStrings = {};
+ lines.sort(waystack);
function lineString(entity) {
if (lineStrings[entity.id] !== undefined) {
@@ -31,32 +83,12 @@ iD.svg.Lines = function() {
'M' + nodes.map(iD.svg.RoundProjection(projection)).join('L'));
}
- function drawPaths(group, lines, filter, classes) {
- var paths = group.selectAll('path')
- .filter(filter)
- .data(lines, iD.Entity.key);
-
- paths.enter()
- .append('path')
- .attr('class', classes);
-
- paths
- .order()
- .attr('d', lineString)
- .call(iD.svg.TagClasses());
-
- paths.exit()
- .remove();
-
- return paths;
- }
-
var casing = surface.select('.layer-casing'),
stroke = surface.select('.layer-stroke'),
defs = surface.select('defs'),
text = surface.select('.layer-text'),
- casings = drawPaths(casing, lines, filter, 'way line casing'),
- strokes = drawPaths(stroke, lines, filter, 'way line stroke');
+ casings = drawPaths(casing, lines, filter, 'way line casing', lineString),
+ strokes = drawPaths(stroke, lines, filter, 'way line stroke', lineString);
// Determine the lengths of oneway paths
var lengths = {},
@@ -97,5 +129,5 @@ iD.svg.Lines = function() {
// adding longer text than necessary, since overflow is hidden
return (new Array(Math.floor(lengths[d.id] * 1.1))).join(arrowtext);
});
- }
+ };
};
diff --git a/js/id/svg/midpoints.js b/js/id/svg/midpoints.js
index 185afc696..9c060ea3b 100644
--- a/js/id/svg/midpoints.js
+++ b/js/id/svg/midpoints.js
@@ -1,5 +1,5 @@
iD.svg.Midpoints = function() {
- return function(surface, graph, entities, filter, projection) {
+ return function drawMidpoints(surface, graph, entities, filter, projection) {
var midpoints = [];
for (var i = 0; i < entities.length; i++) {
diff --git a/js/id/svg/points.js b/js/id/svg/points.js
index 9015f1d5d..7a2600814 100644
--- a/js/id/svg/points.js
+++ b/js/id/svg/points.js
@@ -10,7 +10,7 @@ iD.svg.Points = function() {
return 'icons/unknown.png';
}
- return function(surface, graph, entities, filter, projection) {
+ return function drawPoints(surface, graph, entities, filter, projection) {
var points = [];
for (var i = 0; i < entities.length; i++) {
@@ -20,6 +20,10 @@ iD.svg.Points = function() {
}
}
+ if (points.length > 100) {
+ return surface.select('.layer-hit').selectAll('g.point').remove();
+ }
+
var groups = surface.select('.layer-hit').selectAll('g.point')
.filter(filter)
.data(points, iD.Entity.key);
diff --git a/js/id/svg/surface.js b/js/id/svg/surface.js
index 5c19a0c65..9fb64ca0e 100644
--- a/js/id/svg/surface.js
+++ b/js/id/svg/surface.js
@@ -1,5 +1,5 @@
iD.svg.Surface = function() {
- return function(selection) {
+ return function drawSurface(selection) {
selection.append('defs')
.append('clipPath')
.attr('id', 'clip')
diff --git a/js/id/svg/vertices.js b/js/id/svg/vertices.js
index 36363fbff..700ef5538 100644
--- a/js/id/svg/vertices.js
+++ b/js/id/svg/vertices.js
@@ -1,5 +1,5 @@
iD.svg.Vertices = function() {
- return function(surface, graph, entities, filter, projection) {
+ return function drawVertices(surface, graph, entities, filter, projection) {
var vertices = [];
for (var i = 0; i < entities.length; i++) {
@@ -9,6 +9,10 @@ iD.svg.Vertices = function() {
}
}
+ if (vertices.length > 2000) {
+ return surface.select('.layer-hit').selectAll('g.vertex').remove();
+ }
+
var groups = surface.select('.layer-hit').selectAll('g.vertex')
.filter(filter)
.data(vertices, iD.Entity.key);
diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js
index 5edc48e9f..9078dea7b 100644
--- a/js/id/ui/inspector.js
+++ b/js/id/ui/inspector.js
@@ -83,6 +83,7 @@ iD.ui.inspector = function() {
}
function drawButtons(selection) {
+<<<<<<< HEAD
var inspectorButton1 = selection.append('div')
.attr('class', 'button-wrap')
.append('button')
@@ -111,7 +112,8 @@ iD.ui.inspector = function() {
tags = [{key: '', value: ''}];
}
- var li = tagList.selectAll('li')
+ var li = tagList.html('')
+ .selectAll('li')
.data(tags, function(d) { return d.key; });
li.exit().remove();
@@ -166,7 +168,6 @@ iD.ui.inspector = function() {
if (en.on_node) types.push('point');
if (en.on_way) types.push('line');
en.types = types;
- console.log(en);
iD.ui.modal()
.select('.content')
.datum(en)
@@ -225,15 +226,26 @@ iD.ui.inspector = function() {
key = row.selectAll('.key'),
value = row.selectAll('.value');
+ function sort(value, data) {
+ var sameletter = [],
+ other = [];
+ for (var i = 0; i < data.length; i++) {
+ if (data[i].value.substring(0, value.length) === value) {
+ sameletter.push(data[i]);
+ } else {
+ other.push(data[i]);
+ }
+ }
+ return sameletter.concat(other);
+ }
+
key.call(d3.typeahead()
.data(_.debounce(function(_, callback) {
taginfo.keys({
geometry: geometry,
query: key.property('value')
}, function(err, data) {
- callback(data.data.map(function (d) {
- return {value: d.key};
- }));
+ if (!err) callback(sort(key.property('value'), data));
});
}, 500)));
@@ -244,9 +256,7 @@ iD.ui.inspector = function() {
geometry: geometry,
query: value.property('value')
}, function(err, data) {
- callback(data.data.map(function (d) {
- return {value: d.value, title: d.description};
- }));
+ if (!err) callback(sort(value.property('value'), data));
});
}, 500)));
}
@@ -272,15 +282,19 @@ iD.ui.inspector = function() {
event.close(entity);
}
- inspector.tags = function () {
- var tags = {};
- tagList.selectAll('li').each(function() {
- var row = d3.select(this),
- key = row.selectAll('.key').property('value'),
- value = row.selectAll('.value').property('value');
- if (key !== '') tags[key] = value;
- });
- return tags;
+ inspector.tags = function (tags) {
+ if (!arguments.length) {
+ var tags = {};
+ tagList.selectAll('li').each(function() {
+ var row = d3.select(this),
+ key = row.selectAll('.key').property('value'),
+ value = row.selectAll('.value').property('value');
+ if (key !== '') tags[key] = value;
+ });
+ return tags;
+ } else {
+ drawTags(tags);
+ }
};
return d3.rebind(inspector, event, 'on');
diff --git a/js/lib/d3.tail.js b/js/lib/d3.tail.js
new file mode 100644
index 000000000..8046e4a96
--- /dev/null
+++ b/js/lib/d3.tail.js
@@ -0,0 +1,70 @@
+d3.tail = function() {
+ var text = false,
+ container,
+ xmargin = 20,
+ tooltip_size = [0, 0],
+ selection_size = [0, 0],
+ transformProp = iD.util.prefixCSSProperty('Transform');
+
+ var tail = function(selection) {
+
+ d3.select(window).on('resize.tail-size', function() {
+ selection_size = selection.size();
+ });
+
+ function setup() {
+
+ container = d3.select(document.body)
+ .append('div').attr('class', 'tail');
+
+ selection
+ .on('mousemove.tail', mousemove)
+ .on('mouseover.tail', mouseover)
+ .on('mouseout.tail', mouseout);
+
+ container
+ .on('mousemove.tail', mousemove);
+
+ selection_size = selection.size();
+
+ }
+
+ function mousemove() {
+ if (text === false) return;
+ var xoffset = ((d3.event.x + tooltip_size[0] + xmargin) > selection_size[0]) ?
+ -tooltip_size[0] - xmargin : xoffset = xmargin;
+ container.style(transformProp, 'translate(' +
+ (~~d3.event.x + xoffset) + 'px,' +
+ ~~d3.event.y + 'px)');
+ }
+
+ function mouseout() {
+ if (d3.event.relatedTarget !== container.node() &&
+ text !== false) container.style('display', 'none');
+ }
+
+ function mouseover() {
+ if (d3.event.relatedTarget !== container.node() &&
+ text !== false) container.style('display', 'block');
+ }
+
+ if (!container) setup();
+
+ };
+
+ tail.text = function(_) {
+ if (_ === false) {
+ text = _;
+ container.style('display', 'none');
+ return tail;
+ } else if (container.style('display') == 'none') {
+ container.style('display', 'block');
+ }
+ text = _;
+ container.text(text);
+ tooltip_size = container.size();
+ return tail;
+ };
+
+ return tail;
+};
diff --git a/js/lib/d3.typeahead.js b/js/lib/d3.typeahead.js
index 554b89a60..08a1a36ec 100644
--- a/js/lib/d3.typeahead.js
+++ b/js/lib/d3.typeahead.js
@@ -2,7 +2,7 @@ d3.typeahead = function() {
var data;
var typeahead = function(selection) {
- var container, hidden, idx = 0;
+ var container, hidden, idx = -1;
function setup() {
var rect = selection.node().getBoundingClientRect();
@@ -20,7 +20,7 @@ d3.typeahead = function() {
function hide() {
container.remove();
- idx = 0;
+ idx = -1;
hidden = true;
}
@@ -33,14 +33,17 @@ d3.typeahead = function() {
.on('blur.typeahead', slowHide);
function key() {
+ var len = container.selectAll('a').data().length;
if (d3.event.keyCode === 40) {
- idx++;
+ idx = Math.min(idx + 1, len - 1);
return highlight();
} else if (d3.event.keyCode === 38) {
- idx--;
+ idx = Math.max(idx - 1, 0);
return highlight();
} else if (d3.event.keyCode === 13) {
- select(container.select('a.selected').datum());
+ if (container.select('a.selected').node()) {
+ select(container.select('a.selected').datum());
+ }
hide();
} else {
update();
diff --git a/test/index.html b/test/index.html
index 072439887..f8d570f36 100644
--- a/test/index.html
+++ b/test/index.html
@@ -37,7 +37,6 @@
-
@@ -55,6 +54,9 @@
+
+
+
@@ -152,6 +154,10 @@
+
+
+
+
diff --git a/test/spec/taginfo.js b/test/spec/taginfo.js
index b41c760e0..61f7bcea0 100644
--- a/test/spec/taginfo.js
+++ b/test/spec/taginfo.js
@@ -1,8 +1,9 @@
describe("iD.taginfo", function() {
- var server;
+ var server, taginfo;
beforeEach(function() {
server = sinon.fakeServer.create();
+ taginfo = iD.taginfo();
});
afterEach(function() {
@@ -15,46 +16,81 @@ describe("iD.taginfo", function() {
describe("#keys", function() {
it("calls the given callback with the results of the keys query", function() {
- var taginfo = iD.taginfo(),
- callback = sinon.spy();
-
+ var callback = sinon.spy();
taginfo.keys({query: "amen"}, callback);
server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/keys/all"),
[200, { "Content-Type": "application/json" },
- '{"data":[{"count_all":5190337,"key":"amenity"}]}']);
+ '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0}]}']);
server.respond();
expect(query(server.requests[0].url)).to.eql(
{query: "amen", page: "1", rp: "6", sortname: "count_all", sortorder: "desc"});
- expect(callback).to.have.been.calledWith(null,
- {"data":[{"count_all":5190337,"key":"amenity"}]});
+ expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]);
+ });
+
+ it("filters only popular nodes", function() {
+ var callback = sinon.spy();
+ taginfo.keys({query: "amen"}, callback);
+
+ server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/keys/all"),
+ [200, { "Content-Type": "application/json" },
+ '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\
+ {"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":0.0}]}']);
+ server.respond();
+
+ expect(callback).to.have.been.calledWith(null, [{"value":"amenity"}]);
+ });
+
+ it("filters only popular nodes with an entity type filter", function() {
+ var callback = sinon.spy();
+
+ taginfo.keys({query: "amen", filter: "nodes"}, callback);
+
+ server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/keys/all"),
+ [200, { "Content-Type": "application/json" },
+ '{"data":[{"count_all":5190337,"key":"amenity","count_all_fraction":1.0, "count_nodes_fraction":1.0},\
+ {"count_all":1,"key":"amenityother","count_all_fraction":0.0, "count_nodes_fraction":1.0}]}']);
+ server.respond();
+
+ expect(callback).to.have.been.calledWith(null, [{"value":"amenity"},{"value":"amenityother"}]);
});
});
describe("#values", function() {
it("calls the given callback with the results of the values query", function() {
- var taginfo = iD.taginfo(),
- callback = sinon.spy();
+ var callback = sinon.spy();
taginfo.values({key: "amenity", query: "par"}, callback);
server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/key/values"),
[200, { "Content-Type": "application/json" },
- '{"data":[{"value":"parking","description":"A place for parking cars"}]}']);
+ '{"data":[{"value":"parking","description":"A place for parking cars", "fraction":0.1}]}']);
server.respond();
expect(query(server.requests[0].url)).to.eql(
{key: "amenity", query: "par", page: "1", rp: "20", sortname: 'count_all', sortorder: 'desc'});
- expect(callback).to.have.been.calledWith(null,
- {"data":[{"value":"parking","description":"A place for parking cars"}]});
+ expect(callback).to.have.been.calledWith(null, [{"value":"parking","title":"A place for parking cars"}]);
+ });
+
+ it("filters popular values", function() {
+ var callback = sinon.spy();
+
+ taginfo.values({key: "amenity", query: "par"}, callback);
+
+ server.respondWith("GET", new RegExp("http://taginfo.openstreetmap.org/api/4/key/values"),
+ [200, { "Content-Type": "application/json" },
+ '{"data":[{"value":"parking","description":"A place for parking cars", "fraction":1.0},\
+ {"value":"party","description":"A place for partying", "fraction":0.0}]}']);
+ server.respond();
+
+ expect(callback).to.have.been.calledWith(null, [{"value":"parking","title":"A place for parking cars"}]);
});
});
describe("#docs", function() {
it("calls the given callback with the results of the docs query", function() {
- var taginfo = iD.taginfo(),
- callback = sinon.spy();
+ var callback = sinon.spy();
taginfo.docs({key: "amenity", value: "parking"}, callback);
diff --git a/test/spec/ui/confirm.js b/test/spec/ui/confirm.js
new file mode 100644
index 000000000..a9380e1cc
--- /dev/null
+++ b/test/spec/ui/confirm.js
@@ -0,0 +1,11 @@
+describe("iD.ui.confirm", function () {
+ it('can be instantiated', function () {
+ var confirm = iD.ui.confirm();
+ expect(confirm).to.be.ok;
+ });
+ it('can be dismissed', function () {
+ var confirm = iD.ui.confirm();
+ happen.click(confirm.select('button').node());
+ expect(confirm.node().parentNode).to.be.null;
+ });
+});
diff --git a/test/spec/ui/flash.js b/test/spec/ui/flash.js
new file mode 100644
index 000000000..0426d1734
--- /dev/null
+++ b/test/spec/ui/flash.js
@@ -0,0 +1,13 @@
+describe("iD.ui.flash", function () {
+ it('can be instantiated', function () {
+ var flash = iD.ui.flash();
+ expect(flash).to.be.ok;
+ });
+ it('leaves after 1000 ms', function (done) {
+ var flash = iD.ui.flash();
+ window.setTimeout(function() {
+ expect(flash.node().parentNode).to.be.null;
+ done();
+ }, 1200);
+ });
+});
diff --git a/test/spec/ui/modal.js b/test/spec/ui/modal.js
new file mode 100644
index 000000000..32a42878a
--- /dev/null
+++ b/test/spec/ui/modal.js
@@ -0,0 +1,8 @@
+describe("iD.ui.modal", function () {
+ it('can be instantiated', function () {
+ var modal = iD.ui.modal()
+ .select('.content')
+ .text('foo');
+ expect(modal).to.be.ok;
+ });
+});
diff --git a/test/spec/util.js b/test/spec/util.js
index e6663ade2..3ac1cdda2 100644
--- a/test/spec/util.js
+++ b/test/spec/util.js
@@ -61,5 +61,36 @@ describe('Util', function() {
expect(iD.util.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.util.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.util.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.util.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.util.geo.polygonContainsPolygon(outer, inner)).to.be.false;
+ });
+ });
});
});