Make validation functions take a single entity rather than an array of them

Pass context into validation functions directly rather than initializer
Don't initiate validation functions for every pass
This commit is contained in:
Quincy Morgan
2019-01-30 16:39:37 -05:00
parent 49d62721d8
commit 6aea578f92
11 changed files with 287 additions and 302 deletions
+6 -3
View File
@@ -16,7 +16,7 @@ import { actionAddMidpoint } from '../actions';
import { geoChooseEdge } from '../geo';
export function validationHighwayCrossingOtherWays(context) {
export function validationHighwayCrossingOtherWays() {
// Check if the edge going from n1 to n2 crosses (without a connection node)
// any edge on way. Return the corss point if so.
function findEdgeToWayCrossCoords(n1, n2, way, graph, edgePairsVisited) {
@@ -236,11 +236,14 @@ export function validationHighwayCrossingOtherWays(context) {
return edgeCrossInfos;
}
var validation = function(entitiesToCheck, graph, tree) {
var validation = function(entity, context) {
var graph = context.graph();
var tree = context.history().tree();
// create one issue per crossing point
var edgePairsVisited = d3_set(),
issues = [];
var waysToCheck = _flattenDeep(_map(entitiesToCheck, function(entity) {
var waysToCheck = _flattenDeep(_map([entity], function(entity) {
if (!getFeatureTypeForTags(entity.tags)) {
return [];
}
+57 -60
View File
@@ -15,70 +15,67 @@ import {
} from '../actions';
export function validationDeprecatedTag(context) {
export function validationDeprecatedTag() {
var validation = function(entitiesToCheck) {
var validation = function(change, context) {
var issues = [];
for (var changeIndex in entitiesToCheck) {
var change = entitiesToCheck[changeIndex];
var deprecatedTagsArray = change.deprecatedTags();
if (deprecatedTagsArray.length > 0) {
for (var deprecatedTagIndex in deprecatedTagsArray) {
var deprecatedTags = deprecatedTagsArray[deprecatedTagIndex];
var tagsLabel = utilTagText({ tags: deprecatedTags.old });
var featureLabel = utilDisplayLabel(change, context);
issues.push(new validationIssue({
type: ValidationIssueType.deprecated_tags,
severity: ValidationIssueSeverity.warning,
message: t('issues.deprecated_tags.message', { feature: featureLabel, tags: tagsLabel }),
tooltip: t('issues.deprecated_tags.tip'),
entities: [change],
hash: tagsLabel,
info: {
oldTags: deprecatedTags.old,
replaceTags: deprecatedTags.replace
},
fixes: [
new validationIssueFix({
title: t('issues.fix.upgrade_tags.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
var replaceTags = this.issue.info.replaceTags;
var oldTags = this.issue.info.oldTags;
var transferValue;
for (var oldTagKey in oldTags) {
if (oldTags[oldTagKey] === '*') {
transferValue = tags[oldTagKey];
}
delete tags[oldTagKey];
var deprecatedTagsArray = change.deprecatedTags();
if (deprecatedTagsArray.length > 0) {
for (var deprecatedTagIndex in deprecatedTagsArray) {
var deprecatedTags = deprecatedTagsArray[deprecatedTagIndex];
var tagsLabel = utilTagText({ tags: deprecatedTags.old });
var featureLabel = utilDisplayLabel(change, context);
issues.push(new validationIssue({
type: ValidationIssueType.deprecated_tags,
severity: ValidationIssueSeverity.warning,
message: t('issues.deprecated_tags.message', { feature: featureLabel, tags: tagsLabel }),
tooltip: t('issues.deprecated_tags.tip'),
entities: [change],
hash: tagsLabel,
info: {
oldTags: deprecatedTags.old,
replaceTags: deprecatedTags.replace
},
fixes: [
new validationIssueFix({
title: t('issues.fix.upgrade_tags.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
var replaceTags = this.issue.info.replaceTags;
var oldTags = this.issue.info.oldTags;
var transferValue;
for (var oldTagKey in oldTags) {
if (oldTags[oldTagKey] === '*') {
transferValue = tags[oldTagKey];
}
for (var replaceKey in replaceTags) {
var replaceValue = replaceTags[replaceKey];
if (replaceValue === '*') {
if (tags[replaceKey]) {
// any value is okay and there already
// is one, so don't update it
continue;
} else {
// otherwise assume `yes` is okay
tags[replaceKey] = 'yes';
}
} else if (replaceValue === '$1') {
tags[replaceKey] = transferValue;
} else {
tags[replaceKey] = replaceValue;
}
}
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.upgrade_tags.undo_redo')
);
delete tags[oldTagKey];
}
})
]
}));
}
for (var replaceKey in replaceTags) {
var replaceValue = replaceTags[replaceKey];
if (replaceValue === '*') {
if (tags[replaceKey]) {
// any value is okay and there already
// is one, so don't update it
continue;
} else {
// otherwise assume `yes` is okay
tags[replaceKey] = 'yes';
}
} else if (replaceValue === '$1') {
tags[replaceKey] = transferValue;
} else {
tags[replaceKey] = replaceValue;
}
}
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.upgrade_tags.undo_redo')
);
}
})
]
}));
}
}
+42 -44
View File
@@ -11,7 +11,7 @@ import {
import { operationDelete } from '../operations/index';
import { modeDrawLine } from '../modes';
export function validationDisconnectedHighway(context) {
export function validationDisconnectedHighway() {
function isDisconnectedHighway(entity, graph) {
if (!entity.tags.highway) return false;
@@ -32,52 +32,50 @@ export function validationDisconnectedHighway(context) {
}
var validation = function(entitiesToCheck, graph) {
var validation = function(entity, context) {
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var entity = entitiesToCheck[i];
if (isDisconnectedHighway(entity, graph)) {
var entityLabel = utilDisplayLabel(entity, context);
var graph = context.graph();
if (isDisconnectedHighway(entity, graph)) {
var entityLabel = utilDisplayLabel(entity, context);
issues.push(new validationIssue({
type: ValidationIssueType.disconnected_highway,
severity: ValidationIssueSeverity.warning,
message: t('issues.disconnected_highway.message', {highway: entityLabel}),
tooltip: t('issues.disconnected_highway.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.continue_feature.title'),
onClick: function() {
var way = this.issue.entities[0];
var childNodes = context.graph().childNodes(way);
var endNodes = [childNodes[0], childNodes[childNodes.length-1]];
var exclusiveEndNodes = endNodes.filter(function(vertex) {
return graph.parentWays(vertex).length === 1;
});
var vertex;
if (exclusiveEndNodes.length === 1) {
// prefer an endpoint with no connecting ways
vertex = exclusiveEndNodes[0];
} else {
// prefer the terminating node
vertex = endNodes[1];
}
context.enter(
modeDrawLine(context, way.id, context.graph(), way.affix(vertex.id), true)
);
issues.push(new validationIssue({
type: ValidationIssueType.disconnected_highway,
severity: ValidationIssueSeverity.warning,
message: t('issues.disconnected_highway.message', {highway: entityLabel}),
tooltip: t('issues.disconnected_highway.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.continue_feature.title'),
onClick: function() {
var way = this.issue.entities[0];
var childNodes = context.graph().childNodes(way);
var endNodes = [childNodes[0], childNodes[childNodes.length-1]];
var exclusiveEndNodes = endNodes.filter(function(vertex) {
return graph.parentWays(vertex).length === 1;
});
var vertex;
if (exclusiveEndNodes.length === 1) {
// prefer an endpoint with no connecting ways
vertex = exclusiveEndNodes[0];
} else {
// prefer the terminating node
vertex = endNodes[1];
}
}),
new validationIssueFix({
title: t('issues.fix.delete_feature.title'),
onClick: function() {
var id = this.issue.entities[0].id;
operationDelete([id], context)();
}
})
]
}));
}
context.enter(
modeDrawLine(context, way.id, context.graph(), way.affix(vertex.id), true)
);
}
}),
new validationIssueFix({
title: t('issues.fix.delete_feature.title'),
onClick: function() {
var id = this.issue.entities[0].id;
operationDelete([id], context)();
}
})
]
}));
}
return issues;
+25 -29
View File
@@ -44,36 +44,32 @@ export function validationGenericName(context) {
}
return function validation(entitiesToCheck) {
return function validation(entity) {
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var change = entitiesToCheck[i];
var generic = isGenericName(change);
if (generic) {
var preset = utilPreset(change, context);
issues.push(new validationIssue({
type: ValidationIssueType.generic_name,
severity: ValidationIssueSeverity.warning,
message: t('issues.generic_name.message', {feature: preset.name(), name: generic}),
tooltip: t('issues.generic_name.tip'),
entities: [change],
fixes: [
new validationIssueFix({
title: t('issues.fix.remove_generic_name.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
delete tags.name;
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.remove_generic_name.undo_redo')
);
}
})
]
}));
}
var generic = isGenericName(entity);
if (generic) {
var preset = utilPreset(entity, context);
issues.push(new validationIssue({
type: ValidationIssueType.generic_name,
severity: ValidationIssueSeverity.warning,
message: t('issues.generic_name.message', {feature: preset.name(), name: generic}),
tooltip: t('issues.generic_name.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.remove_generic_name.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
delete tags.name;
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.remove_generic_name.undo_redo')
);
}
})
]
}));
}
return issues;
+50 -51
View File
@@ -25,7 +25,7 @@ import {
/**
* Look for roads that can be connected to other roads with a short extension
*/
export function validationHighwayAlmostJunction(context) {
export function validationHighwayAlmostJunction() {
function isHighway(entity) {
return entity.type === 'way' && entity.tags.highway && entity.tags.highway !== 'no';
@@ -108,60 +108,59 @@ export function validationHighwayAlmostJunction(context) {
return null;
}
var validation = function(entitiesToCheck, graph, tree) {
var validation = function(endHighway, context) {
var graph = context.graph();
var tree = context.history().tree();
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var endHighway = entitiesToCheck[i];
if (!isHighway(endHighway)) continue;
var extendableNodes = findConnectableEndNodesByExtension(endHighway, graph, tree);
for (var j = 0; j < extendableNodes.length; j++) {
var node = extendableNodes[j].node;
var edgeHighway = graph.entity(extendableNodes[j].wid);
if (!isHighway(endHighway)) return issues;
var extendableNodes = findConnectableEndNodesByExtension(endHighway, graph, tree);
for (var j = 0; j < extendableNodes.length; j++) {
var node = extendableNodes[j].node;
var edgeHighway = graph.entity(extendableNodes[j].wid);
var fixes = [
new validationIssueFix({
title: t('issues.fix.connect_almost_junction.title'),
onClick: function() {
var endNode = this.issue.entities[1],
targetEdge = this.issue.info.edge,
crossLoc = this.issue.info.cross_loc;
context.perform(
actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode),
t('issues.fix.connect_almost_junction.undo_redo')
);
}
})
];
if (Object.keys(node.tags).length === 0) {
// node has no tags, suggest noexit fix
fixes.push(new validationIssueFix({
title: t('issues.fix.tag_as_disconnected.title'),
onClick: function() {
var nodeID = this.issue.entities[1].id;
context.perform(
actionChangeTags(nodeID, {noexit: 'yes'}),
t('issues.fix.tag_as_disconnected.undo_redo')
);
}
}));
}
issues.push(new validationIssue({
type: ValidationIssueType.highway_almost_junction,
severity: ValidationIssueSeverity.warning,
message: t('issues.highway_almost_junction.message', {
highway: utilDisplayLabel(endHighway, context),
highway2: utilDisplayLabel(edgeHighway, context)
}),
tooltip: t('issues.highway_almost_junction.tip'),
entities: [endHighway, node, edgeHighway],
coordinates: extendableNodes[j].node.loc,
info: {
edge: extendableNodes[j].edge,
cross_loc: extendableNodes[j].cross_loc
},
fixes: fixes
var fixes = [
new validationIssueFix({
title: t('issues.fix.connect_almost_junction.title'),
onClick: function() {
var endNode = this.issue.entities[1],
targetEdge = this.issue.info.edge,
crossLoc = this.issue.info.cross_loc;
context.perform(
actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode),
t('issues.fix.connect_almost_junction.undo_redo')
);
}
})
];
if (Object.keys(node.tags).length === 0) {
// node has no tags, suggest noexit fix
fixes.push(new validationIssueFix({
title: t('issues.fix.tag_as_disconnected.title'),
onClick: function() {
var nodeID = this.issue.entities[1].id;
context.perform(
actionChangeTags(nodeID, {noexit: 'yes'}),
t('issues.fix.tag_as_disconnected.undo_redo')
);
}
}));
}
issues.push(new validationIssue({
type: ValidationIssueType.highway_almost_junction,
severity: ValidationIssueSeverity.warning,
message: t('issues.highway_almost_junction.message', {
highway: utilDisplayLabel(endHighway, context),
highway2: utilDisplayLabel(edgeHighway, context)
}),
tooltip: t('issues.highway_almost_junction.tip'),
entities: [endHighway, node, edgeHighway],
coordinates: extendableNodes[j].node.loc,
info: {
edge: extendableNodes[j].edge,
cross_loc: extendableNodes[j].cross_loc
},
fixes: fixes
}));
}
return issues;
+11 -12
View File
@@ -55,23 +55,22 @@ export function IssueManager(context) {
};
var entityValidations = [
validations.validationDeprecatedTag,
validations.validationDisconnectedHighway,
validations.validationGenericName,
validations.validationHighwayCrossingOtherWays,
validations.validationHighwayAlmostJunction,
validations.validationMapCSSChecks,
validations.validationMissingTag,
validations.validationOldMultipolygon,
validations.validationTagSuggestsArea
validations.validationDeprecatedTag(),
validations.validationDisconnectedHighway(),
validations.validationGenericName(),
validations.validationHighwayCrossingOtherWays(),
validations.validationHighwayAlmostJunction(),
validations.validationMapCSSChecks(),
validations.validationMissingTag(),
validations.validationOldMultipolygon(),
validations.validationTagSuggestsArea()
];
function validateEntity(entity) {
var history = context.history();
return _flatten(_map(
entityValidations,
function(fn) {
return fn(context)([entity], history.graph(), history.tree());
return fn(entity, context);
}
));
}
@@ -86,7 +85,7 @@ export function IssueManager(context) {
var entitiesToCheck = changes.created.concat(changes.modified);
var graph = history.graph();
issues = issues.concat(validations.validationManyDeletions(context)(changes, graph));
issues = issues.concat(validations.validationManyDeletions()(changes, context));
entitiesToCheck = _uniq(_flattenDeep(_map(entitiesToCheck, function(entity) {
var entities = [entity];
+2 -2
View File
@@ -8,10 +8,10 @@ import {
export function validationManyDeletions() {
var threshold = 100;
var validation = function(changes, graph) {
var validation = function(changes, context) {
var issues = [];
var nodes = 0, ways = 0, areas = 0, relations = 0;
var graph = context.graph();
changes.deleted.forEach(function(c) {
if (c.type === 'node') { nodes++; }
else if (c.type === 'way' && c.geometry(graph) === 'line') { ways++; }
+4 -4
View File
@@ -1,17 +1,17 @@
import { services } from '../services';
export function validationMapCSSChecks() {
var validation = function(entitiesToCheck, graph) {
var validation = function(entity, context) {
if (!services.maprules) return [];
var graph = context.graph();
var rules = services.maprules.validationRules();
var issues = [];
for (var i = 0; i < rules.length; i++) {
var rule = rules[i];
for (var j = 0; j < entitiesToCheck.length; j++) {
rule.findIssues(entitiesToCheck[j], graph, issues);
}
rule.findIssues(entity, graph, issues);
}
return issues;
+30 -33
View File
@@ -12,7 +12,7 @@ import {
} from './validation_issue';
import { operationDelete } from '../operations/index';
export function validationMissingTag(context) {
export function validationMissingTag() {
function hasDescriptiveTags(entity) {
var keys = _without(Object.keys(entity.tags), 'area', 'name').filter(osmIsInterestingTag);
@@ -22,40 +22,37 @@ export function validationMissingTag(context) {
return keys.length > 0;
}
var validation = function(entitiesToCheck, graph) {
var validation = function(entity, context) {
var types = ['point', 'line', 'area', 'relation'];
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var entity = entitiesToCheck[i];
var geometry = entity.geometry(graph);
// ignore vertex features
if (types.indexOf(geometry) !== -1 &&
!(hasDescriptiveTags(entity) || entity.hasParentRelations(graph))) {
var entityLabel = utilDisplayLabel(entity, context);
issues.push(new validationIssue({
type: ValidationIssueType.missing_tag,
severity: ValidationIssueSeverity.error,
message: t('issues.untagged_feature.message', {feature: entityLabel}),
tooltip: t('issues.untagged_feature.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.select_preset.title'),
onClick: function() {
context.ui().sidebar.showPresetList();
}
}),
new validationIssueFix({
title: t('issues.fix.delete_feature.title'),
onClick: function() {
var id = this.issue.entities[0].id;
operationDelete([id], context)();
}
})
]
}));
}
var graph = context.graph();
var geometry = entity.geometry(graph);
// ignore vertex features
if (types.indexOf(geometry) !== -1 &&
!(hasDescriptiveTags(entity) || entity.hasParentRelations(graph))) {
var entityLabel = utilDisplayLabel(entity, context);
issues.push(new validationIssue({
type: ValidationIssueType.missing_tag,
severity: ValidationIssueSeverity.error,
message: t('issues.untagged_feature.message', {feature: entityLabel}),
tooltip: t('issues.untagged_feature.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.select_preset.title'),
onClick: function() {
context.ui().sidebar.showPresetList();
}
}),
new validationIssueFix({
title: t('issues.fix.delete_feature.title'),
onClick: function() {
var id = this.issue.entities[0].id;
operationDelete([id], context)();
}
})
]
}));
}
return issues;
+31 -33
View File
@@ -11,41 +11,39 @@ import {
actionChangeTags
} from '../actions';
export function validationOldMultipolygon(context) {
export function validationOldMultipolygon() {
return function validation(entitiesToCheck, graph) {
return function validation(entity, context) {
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var entity = entitiesToCheck[i];
var mistaggedMultipolygon = osmIsSimpleMultipolygonOuterMember(entity, graph);
if (mistaggedMultipolygon) {
var multipolygonLabel = utilDisplayLabel(mistaggedMultipolygon, context);
issues.push(new validationIssue({
type: ValidationIssueType.old_multipolygon,
severity: ValidationIssueSeverity.warning,
message: t('issues.old_multipolygon.message', {multipolygon: multipolygonLabel}),
tooltip: t('issues.old_multipolygon.tip'),
entities: [entity, mistaggedMultipolygon],
fixes: [
new validationIssueFix({
title: t('issues.fix.move_tags.title'),
onClick: function() {
var outerWay = this.issue.entities[0];
var multipolygon = this.issue.entities[1];
context.perform(
function(graph) {
multipolygon = multipolygon.mergeTags(outerWay.tags);
graph = graph.replace(multipolygon);
graph = actionChangeTags(outerWay.id, {})(graph);
return graph;
},
t('issues.fix.move_tags.undo_redo')
);
}
})
]
}));
}
var graph = context.graph();
var mistaggedMultipolygon = osmIsSimpleMultipolygonOuterMember(entity, graph);
if (mistaggedMultipolygon) {
var multipolygonLabel = utilDisplayLabel(mistaggedMultipolygon, context);
issues.push(new validationIssue({
type: ValidationIssueType.old_multipolygon,
severity: ValidationIssueSeverity.warning,
message: t('issues.old_multipolygon.message', {multipolygon: multipolygonLabel}),
tooltip: t('issues.old_multipolygon.tip'),
entities: [entity, mistaggedMultipolygon],
fixes: [
new validationIssueFix({
title: t('issues.fix.move_tags.title'),
onClick: function() {
var outerWay = this.issue.entities[0];
var multipolygon = this.issue.entities[1];
context.perform(
function(graph) {
multipolygon = multipolygon.mergeTags(outerWay.tags);
graph = graph.replace(multipolygon);
graph = actionChangeTags(outerWay.id, {})(graph);
return graph;
},
t('issues.fix.move_tags.undo_redo')
);
}
})
]
}));
}
return issues;
};
+29 -31
View File
@@ -17,7 +17,7 @@ import {
// https://github.com/openstreetmap/josm/blob/mirror/src/org/
// openstreetmap/josm/data/validation/tests/UnclosedWays.java#L80
export function validationTagSuggestsArea(context) {
export function validationTagSuggestsArea() {
function tagSuggestsArea(tags) {
if (_isEmpty(tags)) return false;
@@ -40,39 +40,37 @@ export function validationTagSuggestsArea(context) {
}
var validation = function(entitiesToCheck, graph) {
var validation = function(entity, context) {
var issues = [];
for (var i = 0; i < entitiesToCheck.length; i++) {
var entity = entitiesToCheck[i];
var geometry = entity.geometry(graph);
var suggestingTags = (geometry === 'line' ? tagSuggestsArea(entity.tags) : undefined);
var graph = context.graph();
var geometry = entity.geometry(graph);
var suggestingTags = (geometry === 'line' ? tagSuggestsArea(entity.tags) : undefined);
if (suggestingTags) {
var tagText = utilTagText({ tags: suggestingTags });
issues.push(new validationIssue({
type: ValidationIssueType.tag_suggests_area,
severity: ValidationIssueSeverity.warning,
message: t('issues.tag_suggests_area.message', { tag: tagText }),
tooltip: t('issues.tag_suggests_area.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.remove_tags.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
for (var key in suggestingTags) {
delete tags[key];
}
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.remove_tags.undo_redo')
);
if (suggestingTags) {
var tagText = utilTagText({ tags: suggestingTags });
issues.push(new validationIssue({
type: ValidationIssueType.tag_suggests_area,
severity: ValidationIssueSeverity.warning,
message: t('issues.tag_suggests_area.message', { tag: tagText }),
tooltip: t('issues.tag_suggests_area.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
title: t('issues.fix.remove_tags.title'),
onClick: function() {
var entity = this.issue.entities[0];
var tags = _clone(entity.tags);
for (var key in suggestingTags) {
delete tags[key];
}
})
]
}));
}
context.perform(
actionChangeTags(entity.id, tags),
t('issues.fix.remove_tags.undo_redo')
);
}
})
]
}));
}
return issues;