Enable the Extract operation for multiple selected extractable features (close #7600)

Allow extracting vertices that are `via` or `location_hint` relation members
This commit is contained in:
Quincy Morgan
2020-06-04 16:35:25 -04:00
parent 513f10a272
commit 6128c6514f
6 changed files with 82 additions and 85 deletions
+10 -7
View File
@@ -407,21 +407,24 @@ en:
description:
vertex:
single: Extract this point from its parent lines/areas.
multiple: Extract these points from their parent features.
line:
single: Extract a point from this line.
multiple: Extract points from these lines.
area:
single: Extract a point from this area.
multiple: Extract points from these areas.
feature:
multiple: Extract points from these features.
annotation:
single: Extracted a point.
multiple: "Extracted {n} points."
too_large:
area:
single: A point can't be extracted from this area because not enough of it is currently visible.
restriction:
vertex:
single: "This point can't be extracted because it would damage a \"{relation}\" relation."
single: A point can't be extracted because not enough of this feature is visible.
multiple: Points can't be extracted because not enough of these features are visible.
connected_to_hidden:
vertex:
single: This point can't be extracted because it is connected to a hidden feature.
single: This point can't be extracted because it is connected to a hidden feature.
multiple: Points can't be extracted from these features because some are connected to hidden features.
restriction:
controls:
distance: Distance
+15 -15
View File
@@ -531,32 +531,32 @@
"key": "E",
"description": {
"vertex": {
"single": "Extract this point from its parent lines/areas."
"single": "Extract this point from its parent lines/areas.",
"multiple": "Extract these points from their parent features."
},
"line": {
"single": "Extract a point from this line."
"single": "Extract a point from this line.",
"multiple": "Extract points from these lines."
},
"area": {
"single": "Extract a point from this area."
"single": "Extract a point from this area.",
"multiple": "Extract points from these areas."
},
"feature": {
"multiple": "Extract points from these features."
}
},
"annotation": {
"single": "Extracted a point."
"single": "Extracted a point.",
"multiple": "Extracted {n} points."
},
"too_large": {
"area": {
"single": "A point can't be extracted from this area because not enough of it is currently visible."
}
},
"restriction": {
"vertex": {
"single": "This point can't be extracted because it would damage a \"{relation}\" relation."
}
"single": "A point can't be extracted because not enough of this feature is visible.",
"multiple": "Points can't be extracted because not enough of these features are visible."
},
"connected_to_hidden": {
"vertex": {
"single": "This point can't be extracted because it is connected to a hidden feature."
}
"single": "This point can't be extracted because it is connected to a hidden feature.",
"multiple": "Points can't be extracted from these features because some are connected to hidden features."
}
}
},
-22
View File
@@ -99,27 +99,5 @@ export function actionExtract(entityID) {
return extractedNodeID;
};
action.disabled = function(graph) {
var entity = graph.entity(entityID);
if (entity.type === 'node') {
var parentRels = graph.parentRelations(entity);
for (var i = 0; i < parentRels.length; i++) {
var relation = parentRels[i];
if (!relation.hasFromViaTo()) continue;
for (var j = 0; j < relation.members.length; j++) {
var m = relation.members[j];
if (m.id === entityID && (m.role === 'via' || m.role === 'location_hint')) {
return 'restriction';
}
}
}
}
return false;
};
return action;
}
+47 -30
View File
@@ -3,47 +3,65 @@ import { behaviorOperation } from '../behavior/operation';
import { modeSelect } from '../modes/select';
import { t } from '../core/localizer';
import { presetManager } from '../presets';
import { utilArrayUniq } from '../util/array';
export function operationExtract(context, selectedIDs) {
var entityID = selectedIDs.length && selectedIDs[0];
var action = actionExtract(entityID);
var geometry = entityID && context.graph().hasEntity(entityID) && context.graph().geometry(entityID);
var extent = (geometry === 'area' || geometry === 'line') && context.graph().entity(entityID).extent(context.graph());
var _amount = selectedIDs.length === 1 ? 'single' : 'multiple';
var _geometries = utilArrayUniq(selectedIDs.map(function(entityID) {
return context.graph().hasEntity(entityID) && context.graph().geometry(entityID);
}).filter(Boolean));
var _geometryID = _geometries.length === 1 ? _geometries[0] : 'feature';
var _extent;
var _actions = selectedIDs.map(function(entityID) {
var graph = context.graph();
var entity = graph.hasEntity(entityID);
if (!entity || !entity.hasInterestingTags()) return;
if (entity.type === 'node' && graph.parentWays(entity).length === 0) return;
var geometry = graph.geometry(entityID);
if (geometry === 'area' || geometry === 'line') {
var preset = presetManager.match(entity, graph);
// only allow extraction from ways/multipolygons if the preset supports points
if (preset.geometry.indexOf('point') === -1) return;
}
_extent = _extent ? _extent.extend(entity.extent(graph)) : entity.extent(graph);
return actionExtract(entityID);
}).filter(Boolean);
var operation = function () {
context.perform(action, operation.annotation()); // do the extract
context.enter(modeSelect(context, [action.getExtractedNodeID()]));
var combinedAction = function(graph) {
_actions.forEach(function(action) {
graph = action(graph);
});
return graph;
};
context.perform(combinedAction, operation.annotation()); // do the extract
var extractedNodeIDs = _actions.map(function(action) {
return action.getExtractedNodeID();
});
context.enter(modeSelect(context, extractedNodeIDs));
};
operation.available = function () {
if (selectedIDs.length !== 1) return false;
var graph = context.graph();
var entity = graph.hasEntity(entityID);
if (!entity) return false;
if (!entity.hasInterestingTags()) return false;
if (geometry === 'area' || geometry === 'line') {
var preset = presetManager.match(entity, graph);
// only allow extraction from ways/multipolygons if the preset supports points
return preset.geometry.indexOf('point') !== -1;
}
return entity.type === 'node' && graph.parentWays(entity).length > 0;
return _actions.length && selectedIDs.length === _actions.length;
};
operation.disabled = function () {
var reason = action.disabled(context.graph());
if (reason) {
return reason;
} else if (geometry === 'vertex' && selectedIDs.some(context.hasHiddenConnections)) {
if (selectedIDs.some(function(entityID) {
return context.graph().geometry(entityID) === 'vertex' && context.hasHiddenConnections(entityID);
})) {
return 'connected_to_hidden';
} else if (extent && extent.area() && extent.percentContainedIn(context.map().extent()) < 0.8) {
} else if (_extent && _extent.area() && _extent.percentContainedIn(context.map().extent()) < 0.8) {
return 'too_large';
}
@@ -54,16 +72,15 @@ export function operationExtract(context, selectedIDs) {
operation.tooltip = function () {
var disableReason = operation.disabled();
if (disableReason) {
return t('operations.extract.' + disableReason + '.' + geometry + '.single',
{ relation: presetManager.item('type/restriction').name() });
return t('operations.extract.' + disableReason + '.' + _amount);
} else {
return t('operations.extract.description.' + geometry + '.single');
return t('operations.extract.description.' + _geometryID + '.' + _amount);
}
};
operation.annotation = function () {
return t('operations.extract.annotation.single');
return t('operations.extract.annotation.' + _amount, { n: selectedIDs.length });
};
+1 -2
View File
@@ -201,8 +201,7 @@ export function validationMismatchedGeometry() {
var entityId = this.entityIds[0];
var extractOnClick = null;
if (!context.hasHiddenConnections(entityId) &&
!actionExtract(entityId).disabled(context.graph())) {
if (!context.hasHiddenConnections(entityId)) {
extractOnClick = function(context) {
var entityId = this.issue.entityIds[0];
+9 -9
View File
@@ -41,11 +41,6 @@ describe('iD.operationExtract', function () {
expect(result).to.be.not.ok;
});
it('is not available for two selected ids', function () {
var result = iD.operationExtract(fakeContext, ['a', 'b']).available();
expect(result).to.be.not.ok;
});
it('is not available for unknown selected id', function () {
var result = iD.operationExtract(fakeContext, ['z']).available();
expect(result).to.be.not.ok;
@@ -85,6 +80,11 @@ describe('iD.operationExtract', function () {
var result = iD.operationExtract(fakeContext, ['b']).available();
expect(result).to.be.ok;
});
it('is available for two selected nodes with tags and parent ways', function () {
var result = iD.operationExtract(fakeContext, ['a', 'b']).available();
expect(result).to.be.ok;
});
});
@@ -112,7 +112,7 @@ describe('iD.operationExtract', function () {
expect(result).to.be.not.ok;
});
it('returns not-enabled for via node in restriction', function () {
it('returns enabled for via node in restriction', function () {
// https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that
// from & to roles are only appropriate for Ways
graph = iD.coreGraph([
@@ -134,10 +134,10 @@ describe('iD.operationExtract', function () {
})
]);
var result = iD.operationExtract(fakeContext, ['d']).disabled();
expect(result).to.eql('restriction');
expect(result).to.be.not.ok;
});
it('returns not-enabled for location_hint node in restriction', function () {
it('returns enabled for location_hint node in restriction', function () {
// https://wiki.openstreetmap.org/wiki/Relation:restriction indicates that
// from & to roles are only appropriate for Ways
graph = iD.coreGraph([
@@ -160,7 +160,7 @@ describe('iD.operationExtract', function () {
})
]);
var result = iD.operationExtract(fakeContext, ['d']).disabled();
expect(result).to.eql('restriction');
expect(result).to.be.not.ok;
});
});
});