Reorganize validation code

Rename IssueManager to coreValidator
Rename disconnected highway to disconnected way
Rename highway almost junction to almost junction
Rename mapcss checks to maprules
Rename deprecated tags to deprecated tag
This commit is contained in:
Quincy Morgan
2019-02-01 11:20:51 -05:00
parent e60dafa540
commit 46f3cea33a
25 changed files with 312 additions and 301 deletions
@@ -19,13 +19,13 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
/**
* Look for roads that can be connected to other roads with a short extension
*/
export function validationHighwayAlmostJunction() {
export function validationAlmostJunction() {
function isHighway(entity) {
return entity.type === 'way' && entity.tags.highway && entity.tags.highway !== 'no';
@@ -146,13 +146,13 @@ export function validationHighwayAlmostJunction() {
}));
}
issues.push(new validationIssue({
type: ValidationIssueType.highway_almost_junction,
type: ValidationIssueType.almost_junction,
severity: ValidationIssueSeverity.warning,
message: t('issues.highway_almost_junction.message', {
highway: utilDisplayLabel(endHighway, context),
highway2: utilDisplayLabel(edgeHighway, context)
message: t('issues.almost_junction.message', {
feature: utilDisplayLabel(endHighway, context),
feature2: utilDisplayLabel(edgeHighway, context)
}),
tooltip: t('issues.highway_almost_junction.tip'),
tooltip: t('issues.almost_junction.highway-highway.tip'),
entities: [endHighway, node, edgeHighway],
coordinates: extendableNodes[j].node.loc,
info: {
@@ -166,7 +166,7 @@ export function validationHighwayAlmostJunction() {
return issues;
};
validation.type = ValidationIssueType.highway_almost_junction;
validation.type = ValidationIssueType.almost_junction;
return validation;
}
+2 -2
View File
@@ -10,13 +10,13 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import { osmNode } from '../osm';
import { actionAddMidpoint } from '../actions';
import { geoChooseEdge } from '../geo';
export function validationHighwayCrossingOtherWays() {
export function validationCrossingWays() {
// 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) {
+2 -2
View File
@@ -9,7 +9,7 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import {
actionChangeTags
} from '../actions';
@@ -82,7 +82,7 @@ export function validationDeprecatedTag() {
return issues;
};
validation.type = ValidationIssueType.deprecated_tags;
validation.type = ValidationIssueType.deprecated_tag;
return validation;
}
@@ -7,11 +7,11 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import { operationDelete } from '../operations/index';
import { modeDrawLine } from '../modes';
export function validationDisconnectedHighway() {
export function validationDisconnectedWay() {
function isDisconnectedHighway(entity, graph) {
if (!entity.tags.highway) return false;
@@ -39,10 +39,10 @@ export function validationDisconnectedHighway() {
var entityLabel = utilDisplayLabel(entity, context);
issues.push(new validationIssue({
type: ValidationIssueType.disconnected_highway,
type: ValidationIssueType.disconnected_way,
severity: ValidationIssueSeverity.warning,
message: t('issues.disconnected_highway.message', {highway: entityLabel}),
tooltip: t('issues.disconnected_highway.tip'),
message: t('issues.disconnected_way.highway.message', { highway: entityLabel }),
tooltip: t('issues.disconnected_way.highway.tip'),
entities: [entity],
fixes: [
new validationIssueFix({
@@ -81,7 +81,7 @@ export function validationDisconnectedHighway() {
return issues;
};
validation.type = ValidationIssueType.disconnected_highway;
validation.type = ValidationIssueType.disconnected_way;
return validation;
}
+1 -1
View File
@@ -8,7 +8,7 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import {
actionChangeTags
} from '../actions';
+4 -5
View File
@@ -1,11 +1,10 @@
export { validationDeprecatedTag } from './deprecated_tag';
export { validationDisconnectedHighway } from './disconnected_highway';
export { validationHighwayCrossingOtherWays } from './crossing_ways';
export { validationHighwayAlmostJunction } from './highway_almost_junction';
export { ValidationIssueType, ValidationIssueSeverity } from './validation_issue';
export { validationDisconnectedWay } from './disconnected_way';
export { validationCrossingWays } from './crossing_ways';
export { validationAlmostJunction } from './almost_junction';
export { validationGenericName } from './generic_name.js';
export { validationManyDeletions } from './many_deletions';
export { validationMapCSSChecks } from './mapcss_checks';
export { validationMaprules } from './maprules';
export { validationMissingTag } from './missing_tag';
export { validationOldMultipolygon } from './old_multipolygon';
export { validationTagSuggestsArea } from './tag_suggests_area';
-129
View File
@@ -1,129 +0,0 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import _map from 'lodash-es/map';
import _flatten from 'lodash-es/flatten';
import _flattenDeep from 'lodash-es/flattenDeep';
import _uniq from 'lodash-es/uniq';
import _uniqWith from 'lodash-es/uniqWith';
import { utilRebind } from '../util/rebind';
import * as validations from '../validations/index';
export function IssueManager(context) {
var dispatch = d3_dispatch('reload'),
self = {},
issues = [],
issuesByEntityId = {};
self.featureApplicabilityOptions = ['edited', 'all'];
var featureApplicability = context.storage('issue-features') || 'edited';
self.getFeatureApplicability = function() {
return featureApplicability;
};
self.setFeatureApplicability = function(applicability) {
featureApplicability = applicability;
context.storage('issue-features', applicability);
};
self.getIssues = function() {
return issues;
};
self.getWarnings = function() {
return issues.filter(function(issue) {
return issue.severity === 'warning';
});
};
self.getErrors = function() {
return issues.filter(function(issue) {
return issue.severity === 'error';
});
};
self.getIssuesForEntityWithID = function(entityID) {
if (!context.hasEntity(entityID)) {
return [];
}
if (!issuesByEntityId[entityID]) {
var entity = context.entity(entityID);
issuesByEntityId[entityID] = validateEntity(entity);
}
return issuesByEntityId[entityID];
};
var genericEntityValidations = [
validations.validationDeprecatedTag(),
validations.validationGenericName(),
validations.validationMapCSSChecks(),
validations.validationOldMultipolygon()
];
function validateEntity(entity) {
var issues = [];
// runs validation and appends resulting issues, returning true if validation passed
function runValidation(fn) {
var typeIssues = fn(entity, context);
issues = issues.concat(typeIssues);
return typeIssues.length === 0;
}
// other validations require feature to be tagged
if (!runValidation(validations.validationMissingTag())) return issues;
if (entity.type === 'way') {
if (runValidation(validations.validationHighwayAlmostJunction())) {
// only check for disconnected highway if no almost junctions
runValidation(validations.validationDisconnectedHighway());
}
runValidation(validations.validationHighwayCrossingOtherWays());
runValidation(validations.validationTagSuggestsArea());
}
genericEntityValidations.forEach(function(fn) {
runValidation(fn);
})
return issues;
}
self.validate = function() {
// clear cached issues
issuesByEntityId = {};
issues = [];
var history = context.history();
var changes = history.changes();
var entitiesToCheck = changes.created.concat(changes.modified);
var graph = history.graph();
issues = issues.concat(validations.validationManyDeletions()(changes, context));
entitiesToCheck = _uniq(_flattenDeep(_map(entitiesToCheck, function(entity) {
var entities = [entity];
if (entity.type === 'node') {
// validate ways if their nodes have changed
entities = entities.concat(graph.parentWays(entity));
}
entities = _map(entities, function(entity) {
if (entity.type !== 'relation') {
// validate relations if their geometries have changed
return [entity].concat(graph.parentRelations(entity));
}
return entity;
});
return entities;
})));
for (var entityIndex in entitiesToCheck) {
var entity = entitiesToCheck[entityIndex];
var entityIssues = validateEntity(entity);
issuesByEntityId[entity.id] = entityIssues;
issues = issues.concat(entityIssues);
}
issues = _uniqWith(issues, function(issue1, issue2) {
return issue1.id() === issue2.id();
});
dispatch.call('reload', self, issues);
};
return utilRebind(self, dispatch, 'on');
}
+2 -2
View File
@@ -3,7 +3,7 @@ import {
ValidationIssueType,
ValidationIssueSeverity,
validationIssue,
} from './validation_issue';
} from '../core/validator';
export function validationManyDeletions() {
@@ -35,7 +35,7 @@ export function validationManyDeletions() {
return issues;
};
validation.type = ValidationIssueType.map_rule_issue;
validation.type = ValidationIssueType.many_deletions;
return validation;
}
@@ -1,8 +1,8 @@
import { services } from '../services';
import {
ValidationIssueType
} from './validation_issue';
export function validationMapCSSChecks() {
} from '../core/validator';
export function validationMaprules() {
var validation = function(entity, context) {
if (!services.maprules) return [];
@@ -19,7 +19,7 @@ export function validationMapCSSChecks() {
return issues;
};
validation.type = ValidationIssueType.map_rule_issue;
validation.type = ValidationIssueType.maprules;
return validation;
}
+1 -1
View File
@@ -9,7 +9,7 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import { operationDelete } from '../operations/index';
export function validationMissingTag() {
+1 -1
View File
@@ -6,7 +6,7 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import {
actionChangeTags
} from '../actions';
+1 -2
View File
@@ -9,7 +9,7 @@ import {
ValidationIssueSeverity,
validationIssue,
validationIssueFix
} from './validation_issue';
} from '../core/validator';
import {
actionChangeTags
} from '../actions';
@@ -39,7 +39,6 @@ export function validationTagSuggestsArea() {
return false;
}
var validation = function(entity, context) {
var issues = [];
var graph = context.graph();
-101
View File
@@ -1,101 +0,0 @@
import _isObject from 'lodash-es/isObject';
import _map from 'lodash-es/map';
import { osmEntity } from '../osm';
var ValidationIssueType = Object.freeze({
deprecated_tags: 'deprecated_tags',
disconnected_highway: 'disconnected_highway',
many_deletions: 'many_deletions',
missing_tag: 'missing_tag',
old_multipolygon: 'old_multipolygon',
tag_suggests_area: 'tag_suggests_area',
map_rule_issue: 'map_rule_issue',
crossing_ways: 'crossing_ways',
highway_almost_junction: 'highway_almost_junction',
generic_name: 'generic_name'
});
var ValidationIssueSeverity = Object.freeze({
warning: 'warning',
error: 'error',
});
export { ValidationIssueType, ValidationIssueSeverity };
export function validationIssue(attrs) {
// A unique, deterministic string hash.
// Issues with identical id values are considered identical.
this.id = function () {
var id = this.type;
if (this.hash) {
// subclasses can pass in their own differentiator
id += this.hash;
}
// issue subclasses set the entity order but it must be deterministic
var entityKeys = _map(this.entities, function(entity) {
// use the key since it factors in the entity's local version
return osmEntity.key(entity);
});
// factor in the entities this issue is for
id += entityKeys.join();
if (this.coordinates) {
// factor in coordinates since two separate issues can have an
// idential type and entities, e.g. in crossing_ways
id += this.coordinates.join();
}
return id;
};
if (!_isObject(attrs)) throw new Error('Input attrs is not an object');
if (!attrs.type || !ValidationIssueType.hasOwnProperty(attrs.type)) {
throw new Error('Invalid attrs.type: ' + attrs.type);
}
if (!attrs.severity || !ValidationIssueSeverity.hasOwnProperty(attrs.severity)) {
throw new Error('Invalid attrs.severity: ' + attrs.severity);
}
if (!attrs.message) throw new Error('attrs.message is empty');
this.type = attrs.type;
this.severity = attrs.severity;
this.message = attrs.message;
this.tooltip = attrs.tooltip;
this.entities = attrs.entities; // expect an array of entities
this.coordinates = attrs.coordinates; // expect a [lon, lat] array
this.info = attrs.info; // an object containing arbitrary extra information
this.fixes = attrs.fixes; // expect an array of functions for possible fixes
this.hash = attrs.hash; // an optional string to further differentiate the issue
this.loc = function() {
if (this.coordinates && Array.isArray(this.coordinates) && this.coordinates.length === 2) {
return this.coordinates;
}
if (this.entities && this.entities.length > 0) {
if (this.entities[0].loc) {
return this.entities[0].loc;
}
}
};
if (this.fixes) {
for (var i=0; i<this.fixes.length; i++) {
// add a reference in the fix to the issue for use in fix actions
this.fixes[i].issue = this;
}
}
}
export function validationIssueFix(attrs) {
this.title = attrs.title;
this.onClick = attrs.onClick;
// the issue this fix is for
this.issue = null;
}