From dcc9812986c3f1b1fc3cfd4bc2c7dbffe963c8f6 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Sat, 11 Oct 2014 00:58:08 -0400 Subject: [PATCH] prevent most operations on things connected to hidden features.. --- data/core.yaml | 8 +++++ dist/locales/en.json | 24 ++++++++----- js/id/core/graph.js | 6 ++-- js/id/id.js | 4 +++ js/id/modes/drag_node.js | 2 +- js/id/operations/circularize.js | 2 ++ js/id/operations/continue.js | 3 +- js/id/operations/delete.js | 6 +++- js/id/operations/disconnect.js | 6 +++- js/id/operations/move.js | 2 ++ js/id/operations/orthogonalize.js | 2 ++ js/id/operations/rotate.js | 2 ++ js/id/operations/split.js | 6 +++- js/id/operations/straighten.js | 6 +++- js/id/renderer/features.js | 58 +++++++++++++++++++++---------- 15 files changed, 102 insertions(+), 35 deletions(-) diff --git a/data/core.yaml b/data/core.yaml index 7f5a8f245..0d7c35ff9 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -55,6 +55,7 @@ en: area: Made an area circular. not_closed: This can't be made circular because it's not a loop. too_large: This can't be made circular because not enough of it is currently visible. + connected_to_hidden: This can't be made circular because it is connected to a hidden feature. orthogonalize: title: Square description: @@ -66,12 +67,14 @@ en: area: Squared the corners of an area. not_squarish: This can't be made square because it is not squarish. too_large: This can't be made square because not enough of it is currently visible. + connected_to_hidden: This can't be made square because it is connected to a hidden feature. straighten: title: Straighten description: Straighten this line. key: S annotation: Straightened a line. too_bendy: This can't be straightened because it bends too much. + connected_to_hidden: This line can't be straightened because it is connected to a hidden feature. delete: title: Delete description: Delete object permanently. @@ -83,6 +86,7 @@ en: relation: Deleted a relation. multiple: "Deleted {n} objects." incomplete_relation: This feature can't be deleted because it hasn't been fully downloaded. + connected_to_hidden: This can't be deleted because it is connected to a hidden feature. add_member: annotation: Added a member to a relation. delete_member: @@ -99,6 +103,7 @@ en: key: D annotation: Disconnected lines/areas. not_connected: There aren't enough lines/areas here to disconnect. + connected_to_hidden: This can't be disconnected because it is connected to a hidden feature. merge: title: Merge description: Merge these lines. @@ -120,6 +125,7 @@ en: multiple: Moved multiple objects. incomplete_relation: This feature can't be moved because it hasn't been fully downloaded. too_large: This can't be moved because not enough of it is currently visible. + connected_to_hidden: This can't be moved because it is connected to a hidden feature. rotate: title: Rotate description: Rotate this object around its center point. @@ -128,6 +134,7 @@ en: line: Rotated a line. area: Rotated an area. too_large: This can't be rotated because not enough of it is currently visible. + connected_to_hidden: This can't be rotated because it is connected to a hidden feature. reverse: title: Reverse description: Make this line go in the opposite direction. @@ -146,6 +153,7 @@ en: multiple: "Split {n} lines/area boundaries." not_eligible: Lines can't be split at their beginning or end. multiple_ways: There are too many lines here to split. + connected_to_hidden: This can't be split because it is connected to a hidden feature. restriction: help: select: Click to select a road segment. diff --git a/dist/locales/en.json b/dist/locales/en.json index fcb5e38d2..476187314 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -72,7 +72,8 @@ "area": "Made an area circular." }, "not_closed": "This can't be made circular because it's not a loop.", - "too_large": "This can't be made circular because not enough of it is currently visible." + "too_large": "This can't be made circular because not enough of it is currently visible.", + "connected_to_hidden": "This can't be made circular because it is connected to a hidden feature." }, "orthogonalize": { "title": "Square", @@ -86,14 +87,16 @@ "area": "Squared the corners of an area." }, "not_squarish": "This can't be made square because it is not squarish.", - "too_large": "This can't be made square because not enough of it is currently visible." + "too_large": "This can't be made square because not enough of it is currently visible.", + "connected_to_hidden": "This can't be made square because it is connected to a hidden feature." }, "straighten": { "title": "Straighten", "description": "Straighten this line.", "key": "S", "annotation": "Straightened a line.", - "too_bendy": "This can't be straightened because it bends too much." + "too_bendy": "This can't be straightened because it bends too much.", + "connected_to_hidden": "This line can't be straightened because it is connected to a hidden feature." }, "delete": { "title": "Delete", @@ -106,7 +109,8 @@ "relation": "Deleted a relation.", "multiple": "Deleted {n} objects." }, - "incomplete_relation": "This feature can't be deleted because it hasn't been fully downloaded." + "incomplete_relation": "This feature can't be deleted because it hasn't been fully downloaded.", + "connected_to_hidden": "This can't be deleted because it is connected to a hidden feature." }, "add_member": { "annotation": "Added a member to a relation." @@ -127,7 +131,8 @@ "description": "Disconnect these lines/areas from each other.", "key": "D", "annotation": "Disconnected lines/areas.", - "not_connected": "There aren't enough lines/areas here to disconnect." + "not_connected": "There aren't enough lines/areas here to disconnect.", + "connected_to_hidden": "This can't be disconnected because it is connected to a hidden feature." }, "merge": { "title": "Merge", @@ -151,7 +156,8 @@ "multiple": "Moved multiple objects." }, "incomplete_relation": "This feature can't be moved because it hasn't been fully downloaded.", - "too_large": "This can't be moved because not enough of it is currently visible." + "too_large": "This can't be moved because not enough of it is currently visible.", + "connected_to_hidden": "This can't be moved because it is connected to a hidden feature." }, "rotate": { "title": "Rotate", @@ -161,7 +167,8 @@ "line": "Rotated a line.", "area": "Rotated an area." }, - "too_large": "This can't be rotated because not enough of it is currently visible." + "too_large": "This can't be rotated because not enough of it is currently visible.", + "connected_to_hidden": "This can't be rotated because it is connected to a hidden feature." }, "reverse": { "title": "Reverse", @@ -183,7 +190,8 @@ "multiple": "Split {n} lines/area boundaries." }, "not_eligible": "Lines can't be split at their beginning or end.", - "multiple_ways": "There are too many lines here to split." + "multiple_ways": "There are too many lines here to split.", + "connected_to_hidden": "This can't be split because it is connected to a hidden feature." }, "restriction": { "help": { diff --git a/js/id/core/graph.js b/js/id/core/graph.js index 3f9ab7c9c..8455d5096 100644 --- a/js/id/core/graph.js +++ b/js/id/core/graph.js @@ -72,8 +72,10 @@ iD.Graph.prototype = { return this._childNodes[entity.id]; var nodes = []; - for (var i = 0, l = entity.nodes.length; i < l; i++) { - nodes[i] = this.entity(entity.nodes[i]); + if (entity.nodes) { + for (var i = 0, l = entity.nodes.length; i < l; i++) { + nodes[i] = this.entity(entity.nodes[i]); + } } if (iD.debug) Object.freeze(nodes); diff --git a/js/id/id.js b/js/id/id.js index 5ea20ca86..fd18f9a5a 100644 --- a/js/id/id.js +++ b/js/id/id.js @@ -206,6 +206,10 @@ window.iD = function () { /* Features */ var features = iD.Features(context); context.features = function() { return features; }; + context.hasHiddenConnections = function(id) { + var entity = history.graph().entity(id); + return features.hasHiddenConnections(entity); + }; /* Map */ var map = iD.Map(context); diff --git a/js/id/modes/drag_node.js b/js/id/modes/drag_node.js index be2956181..aa0176288 100644 --- a/js/id/modes/drag_node.js +++ b/js/id/modes/drag_node.js @@ -48,7 +48,7 @@ iD.modes.DragNode = function(context) { } function start(entity) { - cancelled = d3.event.sourceEvent.shiftKey; + cancelled = d3.event.sourceEvent.shiftKey || context.features().hasHiddenConnections(entity); if (cancelled) return behavior.cancel(); wasMidpoint = entity.type === 'midpoint'; diff --git a/js/id/operations/circularize.js b/js/id/operations/circularize.js index a786627c9..f15a11d8f 100644 --- a/js/id/operations/circularize.js +++ b/js/id/operations/circularize.js @@ -20,6 +20,8 @@ iD.operations.Circularize = function(selectedIDs, context) { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { reason = 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; } return action.disabled(context.graph()) || reason; }; diff --git a/js/id/operations/continue.js b/js/id/operations/continue.js index 9ebcdac1e..bc1f7ba9a 100644 --- a/js/id/operations/continue.js +++ b/js/id/operations/continue.js @@ -23,7 +23,8 @@ iD.operations.Continue = function(selectedIDs, context) { }; operation.available = function() { - return geometries.vertex.length === 1 && geometries.line.length <= 1; + return geometries.vertex.length === 1 && geometries.line.length <= 1 && + !context.features().hasHiddenConnections(vertex); }; operation.disabled = function() { diff --git a/js/id/operations/delete.js b/js/id/operations/delete.js index 15c41c067..bdd1483a4 100644 --- a/js/id/operations/delete.js +++ b/js/id/operations/delete.js @@ -52,7 +52,11 @@ iD.operations.Delete = function(selectedIDs, context) { }; operation.disabled = function() { - return action.disabled(context.graph()); + var reason; + if (_.any(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; }; operation.tooltip = function() { diff --git a/js/id/operations/disconnect.js b/js/id/operations/disconnect.js index ea12b0419..baa1ff1d4 100644 --- a/js/id/operations/disconnect.js +++ b/js/id/operations/disconnect.js @@ -19,7 +19,11 @@ iD.operations.Disconnect = function(selectedIDs, context) { }; operation.disabled = function() { - return action.disabled(context.graph()); + var reason; + if (_.any(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; }; operation.tooltip = function() { diff --git a/js/id/operations/move.js b/js/id/operations/move.js index 525ee7047..6f877a0cc 100644 --- a/js/id/operations/move.js +++ b/js/id/operations/move.js @@ -16,6 +16,8 @@ iD.operations.Move = function(selectedIDs, context) { var reason; if (extent.area() && extent.percentContainedIn(context.extent()) < 0.8) { reason = 'too_large'; + } else if (_.any(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; } return iD.actions.Move(selectedIDs).disabled(context.graph()) || reason; }; diff --git a/js/id/operations/orthogonalize.js b/js/id/operations/orthogonalize.js index 851e7e248..851f3b7a9 100644 --- a/js/id/operations/orthogonalize.js +++ b/js/id/operations/orthogonalize.js @@ -21,6 +21,8 @@ iD.operations.Orthogonalize = function(selectedIDs, context) { var reason; if (extent.percentContainedIn(context.extent()) < 0.8) { reason = 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; } return action.disabled(context.graph()) || reason; }; diff --git a/js/id/operations/rotate.js b/js/id/operations/rotate.js index 3edc3e4d9..485d05d24 100644 --- a/js/id/operations/rotate.js +++ b/js/id/operations/rotate.js @@ -22,6 +22,8 @@ iD.operations.Rotate = function(selectedIDs, context) { operation.disabled = function() { if (extent.percentContainedIn(context.extent()) < 0.8) { return 'too_large'; + } else if (context.hasHiddenConnections(entityId)) { + return 'connected_to_hidden'; } else { return false; } diff --git a/js/id/operations/split.js b/js/id/operations/split.js index 9a9130df1..43bc72946 100644 --- a/js/id/operations/split.js +++ b/js/id/operations/split.js @@ -29,7 +29,11 @@ iD.operations.Split = function(selectedIDs, context) { }; operation.disabled = function() { - return action.disabled(context.graph()); + var reason; + if (_.any(selectedIDs, context.hasHiddenConnections)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; }; operation.tooltip = function() { diff --git a/js/id/operations/straighten.js b/js/id/operations/straighten.js index 85e9bcd17..fd0a5e823 100644 --- a/js/id/operations/straighten.js +++ b/js/id/operations/straighten.js @@ -16,7 +16,11 @@ iD.operations.Straighten = function(selectedIDs, context) { }; operation.disabled = function() { - return action.disabled(context.graph()); + var reason; + if (context.hasHiddenConnections(entityId)) { + reason = 'connected_to_hidden'; + } + return action.disabled(context.graph()) || reason; }; operation.tooltip = function() { diff --git a/js/id/renderer/features.js b/js/id/renderer/features.js index 12a3a9c11..a43f3e64c 100644 --- a/js/id/renderer/features.js +++ b/js/id/renderer/features.js @@ -41,8 +41,7 @@ iD.Features = function(context) { 'obliterated': true }; - var graph = context.graph(), - dispatch = d3.dispatch('change', 'redraw'), + var dispatch = d3.dispatch('change', 'redraw'), feature = {}; function defineFeature(k, filter, max) { @@ -64,24 +63,24 @@ iD.Features = function(context) { } defineFeature('points', function(entity) { - return entity.geometry(graph) === 'point'; + return entity.geometry(context.graph()) === 'point'; }, 100); defineFeature('major_roads', function(entity) { - return entity.geometry(graph) === 'line' && major_roads[entity.tags.highway]; + return entity.geometry(context.graph()) === 'line' && major_roads[entity.tags.highway]; }); defineFeature('minor_roads', function(entity) { - return entity.geometry(graph) === 'line' && minor_roads[entity.tags.highway]; + return entity.geometry(context.graph()) === 'line' && minor_roads[entity.tags.highway]; }); defineFeature('paths', function(entity) { - return entity.geometry(graph) === 'line' && paths[entity.tags.highway]; + return entity.geometry(context.graph()) === 'line' && paths[entity.tags.highway]; }); defineFeature('buildings', function(entity) { return ( - entity.geometry(graph) === 'area' && ( + entity.geometry(context.graph()) === 'area' && ( (!!entity.tags.building && entity.tags.building !== 'no') || entity.tags.amenity === 'shelter' || entity.tags.parking === 'multi-storey' || @@ -93,7 +92,7 @@ iD.Features = function(context) { }, 100); defineFeature('landuse', function(entity) { - return entity.geometry(graph) === 'area' && + return entity.geometry(context.graph()) === 'area' && !feature.buildings.filter(entity) && !feature.water.filter(entity); }); @@ -138,8 +137,8 @@ iD.Features = function(context) { // lines or areas that don't match another feature filter. defineFeature('others', function(entity) { return ( - entity.geometry(graph) === 'line' || - entity.geometry(graph) === 'area' + entity.geometry(context.graph()) === 'line' || + entity.geometry(context.graph()) === 'area' ) && _.reduce(_.omit(feature, 'others'), function(result, v) { return result && !v.filter(entity); @@ -244,17 +243,38 @@ iD.Features = function(context) { return stats; }; - features.isHidden = function(entity) { - var hidden = features.hidden(); + features.isHiddenFeature = function(entity) { + return _.any(features.hidden(), function(k) { return feature[k].filter(entity); }); + }; - function isHiddenFeature(entity) { - return _.any(hidden, function(k) { return feature[k].filter(entity); }); + features.isHiddenChild = function(entity) { + var g = context.graph(), + parents = _.union(g.parentWays(entity), g.parentRelations(entity)); + return parents.length ? _.all(parents, features.isHidden) : false; + }; + + features.hasHiddenConnections = function(entity) { + var g = context.graph(), + childNodes, connections; + + if (entity.type === 'midpoint') { + childNodes = [context.entity(entity.edge[0]), context.entity(entity.edge[1])]; + } else { + childNodes = g.childNodes(entity); } - function isHiddenChild(entity) { - var parents = _.union(graph.parentWays(entity), graph.parentRelations(entity)); - return parents.length ? _.all(parents, features.isHidden) : false; - } - return isHiddenFeature(entity) || isHiddenChild(entity); + + // gather parents.. + connections = _.union(g.parentWays(entity), g.parentRelations(entity)); + // gather ways connected to child nodes.. + connections = _.reduce(childNodes, function(result, e) { + return g.isShared(e) ? _.union(result, g.parentWays(e)) : result; + }, connections); + + return connections.length ? _.any(connections, features.isHidden) : false; + }; + + features.isHidden = function(entity) { + return features.isHiddenFeature(entity) || features.isHiddenChild(entity); }; features.filter = function(d) {