diff --git a/index.html b/index.html index 9db659489..2d42c9f1c 100644 --- a/index.html +++ b/index.html @@ -108,6 +108,7 @@ + diff --git a/js/id/graph/validate.js b/js/id/graph/validate.js new file mode 100644 index 000000000..addb774ca --- /dev/null +++ b/js/id/graph/validate.js @@ -0,0 +1,47 @@ +iD.validate = function(changes) { + var warnings = [], change; + + // https://github.com/openstreetmap/josm/blob/mirror/src/org/ + // openstreetmap/josm/data/validation/tests/UnclosedWays.java#L80 + function tagSuggestsArea(change) { + if (_.isEmpty(change.tags)) return false; + var tags = change.tags; + var presence = ['landuse', 'amenities', 'tourism', 'shop']; + for (var i = 0; i < presence.length; i++) { + if (tags[presence[i]] !== undefined) { + return presence[i] + '=' + tags[presence[i]]; + } + } + if (tags.building && tags.building === 'yes') return 'building=yes'; + } + + if (changes.created.length) { + for (var i = 0; i < changes.created.length; i++) { + change = changes.created[i]; + + if (change.geometry() === 'point' && _.isEmpty(change.tags)) { + warnings.push({ + message: 'Untagged point which is not part of a line or area', + entity: change + }); + } + + if (change.geometry() === 'line' && _.isEmpty(change.tags)) { + warnings.push({ message: 'Untagged line', entity: change }); + } + + if (change.geometry() === 'area' && _.isEmpty(change.tags)) { + warnings.push({ message: 'Untagged area', entity: change }); + } + + if (change.geometry() === 'line' && tagSuggestsArea(change)) { + warnings.push({ + message: 'The tag ' + tagSuggestsArea(change) + ' suggests line should be area, but it is not and area', + entity: change + }); + } + } + } + + return warnings.length ? [warnings] : []; +}; diff --git a/js/id/id.js b/js/id/id.js index 96f4d09d5..e22a77ca6 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -96,7 +96,7 @@ window.iD = function(container) { var save_button = bar.append('button') .attr('class', 'save action wide') - .call(iD.ui.save().map(map)); + .call(iD.ui.save().map(map).controller(controller)); history.on('change.warn-unload', function() { var changes = history.changes(), diff --git a/js/id/ui/commit.js b/js/id/ui/commit.js index 547f4505f..06d072275 100644 --- a/js/id/ui/commit.js +++ b/js/id/ui/commit.js @@ -1,5 +1,5 @@ iD.ui.commit = function() { - var event = d3.dispatch('cancel', 'save'); + var event = d3.dispatch('cancel', 'save', 'fix'); function zipSame(d) { var c = [], n = -1; @@ -58,12 +58,12 @@ iD.ui.commit = function() { header.append('p').text('The changes you upload will be visible on all maps that use OpenStreetMap data.'); - var commit = body.append('div').attr('class','modal-section'); - commit.append('textarea') - .attr('class', 'changeset-comment') - .attr('placeholder', 'Brief Description of your contributions'); + var comment_section = body.append('div').attr('class','modal-section'); + comment_section.append('textarea') + .attr('class', 'changeset-comment') + .attr('placeholder', 'Brief Description of your contributions'); - var buttonwrap = commit.append('div') + var buttonwrap = comment_section.append('div') .attr('class', 'buttons'); var savebutton = buttonwrap.append('button') @@ -84,12 +84,39 @@ iD.ui.commit = function() { cancelbutton.append('span').attr('class','icon close icon-pre-text'); cancelbutton.append('span').attr('class','label').text('Cancel'); + var warnings = body.selectAll('div.warning-section') + .data(iD.validate(changes)) + .enter() + .append('div').attr('class', 'modal-section warning-section'); + + warnings.append('h3') + .text('Warnings'); + + var warning_li = warnings.append('ul') + .attr('class', 'changeset-list') + .selectAll('li') + .data(function(d) { return d; }) + .enter() + .append('li'); + + warning_li.append('button') + .attr('class', 'minor') + .on('click', event.fix) + .append('span') + .attr('class', 'icon inspect'); + + warning_li.append('strong').text(function(d) { + return d.message; + }); + var section = body.selectAll('div.commit-section') .data(['modified', 'deleted', 'created'].filter(changesLength)) .enter() .append('div').attr('class', 'commit-section modal-section fillL2'); - section.append('h3').text(String) + section.append('h3').text(function(d) { + return d.charAt(0).toUpperCase() + d.slice(1); + }) .append('small') .attr('class', 'count') .text(changesLength); diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 3af39c1b7..600538e4c 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -114,12 +114,14 @@ iD.ui.inspector = function() { inputs.append('input') .property('type', 'text') .attr('class', 'key') + .attr('maxlength', 255) .property('value', function(d) { return d.key; }) .on('change', function(d) { d.key = this.value; }); inputs.append('input') .property('type', 'text') .attr('class', 'value') + .attr('maxlength', 255) .property('value', function(d) { return d.value; }) .on('change', function(d) { d.value = this.value; }) .on('keydown.push-more', pushMore); @@ -271,7 +273,7 @@ iD.ui.inspector = function() { inspector.tags = function (tags) { if (!arguments.length) { - var tags = {}; + tags = {}; tagList.selectAll('li').each(function() { var row = d3.select(this), key = row.selectAll('.key').property('value'), diff --git a/js/id/ui/save.js b/js/id/ui/save.js index 27d6ef1e5..e40125e71 100644 --- a/js/id/ui/save.js +++ b/js/id/ui/save.js @@ -1,6 +1,6 @@ iD.ui.save = function() { - var map; + var map, controller; function save(selection) { @@ -59,6 +59,13 @@ iD.ui.save = function() { .on('cancel', function() { modal.remove(); }) + .on('fix', function(d) { + var ext = d.entity.extent(map.history().graph()); + map.extent(ext[0], ext[1]); + if (map.zoom() > 19) map.zoom(19); + controller.enter(iD.modes.Select(d.entity)); + modal.remove(); + }) .on('save', commit)); }); } else { @@ -91,5 +98,11 @@ iD.ui.save = function() { return save; }; + save.controller = function(_) { + if (!arguments.length) return controller; + controller = _; + return save; + }; + return save; };