mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
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:
@@ -1151,9 +1151,10 @@ en:
|
||||
severity:
|
||||
error: error
|
||||
warning: warning
|
||||
disconnected_highway:
|
||||
message: "{highway} is disconnected from other highways."
|
||||
tip: Roads should be connected to other roads or building entrances.
|
||||
disconnected_way:
|
||||
highway:
|
||||
message: "{highway} is disconnected from other roads and paths."
|
||||
tip: Highways should connect to other highways or building entrances.
|
||||
old_multipolygon:
|
||||
message: "{multipolygon} has misplaced tags."
|
||||
tip: Multipolygons should be tagged on their relation, not their outer way.
|
||||
@@ -1202,9 +1203,10 @@ en:
|
||||
tip: Crossing bridges should use different layers.
|
||||
bridge-bridge_connectable:
|
||||
tip: Crossing bridges should be connected or use different layers.
|
||||
highway_almost_junction:
|
||||
message: "{highway} is very close but not connected to {highway2}."
|
||||
tip: Intersecting highways should share a junction vertex.
|
||||
almost_junction:
|
||||
message: "{feature} is very close but not connected to {feature2}."
|
||||
highway-highway:
|
||||
tip: Intersecting highways should share a junction vertex.
|
||||
fix:
|
||||
add_connection_vertex:
|
||||
title: Connect the features
|
||||
|
||||
16
dist/locales/en.json
vendored
16
dist/locales/en.json
vendored
@@ -1395,9 +1395,11 @@
|
||||
"error": "error",
|
||||
"warning": "warning"
|
||||
},
|
||||
"disconnected_highway": {
|
||||
"message": "{highway} is disconnected from other highways.",
|
||||
"tip": "Roads should be connected to other roads or building entrances."
|
||||
"disconnected_way": {
|
||||
"highway": {
|
||||
"message": "{highway} is disconnected from other roads and paths.",
|
||||
"tip": "Highways should connect to other highways or building entrances."
|
||||
}
|
||||
},
|
||||
"old_multipolygon": {
|
||||
"message": "{multipolygon} has misplaced tags.",
|
||||
@@ -1468,9 +1470,11 @@
|
||||
"tip": "Crossing bridges should be connected or use different layers."
|
||||
}
|
||||
},
|
||||
"highway_almost_junction": {
|
||||
"message": "{highway} is very close but not connected to {highway2}.",
|
||||
"tip": "Intersecting highways should share a junction vertex."
|
||||
"almost_junction": {
|
||||
"message": "{feature} is very close but not connected to {feature2}.",
|
||||
"highway-highway": {
|
||||
"tip": "Intersecting highways should share a junction vertex."
|
||||
}
|
||||
},
|
||||
"fix": {
|
||||
"add_connection_vertex": {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { select as d3_select } from 'd3-selection';
|
||||
import { t, currentLocale, addTranslation, setLocale } from '../util/locale';
|
||||
|
||||
import { coreHistory } from './history';
|
||||
import { IssueManager } from '../validations/issue_manager';
|
||||
import { coreValidator } from './validator';
|
||||
import { dataLocales, dataEn } from '../../data';
|
||||
import { geoRawMercator } from '../geo/raw_mercator';
|
||||
import { modeSelect } from '../modes/select';
|
||||
@@ -96,10 +96,10 @@ export function coreContext() {
|
||||
|
||||
|
||||
/* Straight accessors. Avoid using these if you can. */
|
||||
var connection, history, issueManager;
|
||||
var connection, history, validator;
|
||||
context.connection = function() { return connection; };
|
||||
context.history = function() { return history; };
|
||||
context.issueManager = function() { return issueManager; };
|
||||
context.validator = function() { return validator; };
|
||||
|
||||
/* Connection */
|
||||
context.preauth = function(options) {
|
||||
@@ -457,22 +457,22 @@ export function coreContext() {
|
||||
context.changes = history.changes;
|
||||
context.intersects = history.intersects;
|
||||
|
||||
issueManager = IssueManager(context);
|
||||
validator = coreValidator(context);
|
||||
|
||||
// run validation upon restoring from page reload
|
||||
history.on('restore', function() {
|
||||
issueManager.validate();
|
||||
validator.validate();
|
||||
});
|
||||
// re-run validation upon a significant graph change
|
||||
history.on('annotatedChange', function(difference) {
|
||||
if (difference) {
|
||||
issueManager.validate();
|
||||
validator.validate();
|
||||
}
|
||||
});
|
||||
// re-run validation upon merging fetched data
|
||||
history.on('merge', function(entities) {
|
||||
if (entities && entities.length > 0) {
|
||||
issueManager.validate();
|
||||
validator.validate();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -3,3 +3,4 @@ export { coreDifference } from './difference';
|
||||
export { coreGraph } from './graph';
|
||||
export { coreHistory } from './history';
|
||||
export { coreTree } from './tree';
|
||||
export { coreValidator } from './validator';
|
||||
|
||||
236
modules/core/validator.js
Normal file
236
modules/core/validator.js
Normal file
@@ -0,0 +1,236 @@
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
|
||||
import _isObject from 'lodash-es/isObject';
|
||||
import _isFunction from 'lodash-es/isFunction';
|
||||
import _map from 'lodash-es/map';
|
||||
import _filter from 'lodash-es/filter';
|
||||
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 { osmEntity } from '../osm';
|
||||
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import * as Validations from '../validations/index';
|
||||
|
||||
export var ValidationIssueType = {
|
||||
deprecated_tag: 'deprecated_tag',
|
||||
disconnected_way: 'disconnected_way',
|
||||
many_deletions: 'many_deletions',
|
||||
missing_tag: 'missing_tag',
|
||||
old_multipolygon: 'old_multipolygon',
|
||||
tag_suggests_area: 'tag_suggests_area',
|
||||
maprules: 'maprules',
|
||||
crossing_ways: 'crossing_ways',
|
||||
almost_junction: 'almost_junction',
|
||||
generic_name: 'generic_name'
|
||||
};
|
||||
|
||||
export var ValidationIssueSeverity = {
|
||||
warning: 'warning',
|
||||
error: 'error',
|
||||
};
|
||||
|
||||
export function coreValidator(context) {
|
||||
var dispatch = d3_dispatch('reload'),
|
||||
self = {},
|
||||
issues = [],
|
||||
issuesByEntityId = {};
|
||||
|
||||
var validations = _filter(Validations, _isFunction).reduce(function(obj, validation) {
|
||||
var func = validation();
|
||||
if (!func.type) {
|
||||
throw new Error('Validation type not found: ' + validation);
|
||||
}
|
||||
obj[func.type] = func;
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
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 = [
|
||||
ValidationIssueType.deprecated_tag,
|
||||
ValidationIssueType.generic_name,
|
||||
ValidationIssueType.maprules,
|
||||
ValidationIssueType.old_multipolygon
|
||||
];
|
||||
|
||||
function validateEntity(entity) {
|
||||
var issues = [];
|
||||
// runs validation and appends resulting issues, returning true if validation passed
|
||||
function runValidation(type) {
|
||||
var fn = validations[type];
|
||||
var typeIssues = fn(entity, context);
|
||||
issues = issues.concat(typeIssues);
|
||||
return typeIssues.length === 0;
|
||||
}
|
||||
// other validations require feature to be tagged
|
||||
if (!runValidation(ValidationIssueType.missing_tag)) return issues;
|
||||
if (entity.type === 'way') {
|
||||
if (runValidation(ValidationIssueType.almost_junction)) {
|
||||
// only check for disconnected highway if no almost junctions
|
||||
runValidation(ValidationIssueType.disconnected_way);
|
||||
}
|
||||
runValidation(ValidationIssueType.crossing_ways);
|
||||
runValidation(ValidationIssueType.tag_suggests_area);
|
||||
}
|
||||
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.many_deletions(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');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export * from './ui/settings/index';
|
||||
export * from './ui/index';
|
||||
export * from './util/index';
|
||||
export * from './validations/index';
|
||||
export { IssueManager } from './validations/issue_manager';
|
||||
export { coreValidator } from './core/validator';
|
||||
|
||||
/* export some legacy symbols: */
|
||||
import { services } from './services/index';
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ValidationIssueType,
|
||||
ValidationIssueSeverity,
|
||||
validationIssue
|
||||
} from '../validations/validation_issue';
|
||||
} from '../core/validator';
|
||||
|
||||
var buildRuleChecks = function() {
|
||||
return {
|
||||
@@ -224,7 +224,7 @@ export default {
|
||||
? ValidationIssueSeverity.error
|
||||
: ValidationIssueSeverity.warning;
|
||||
issues.push(new validationIssue({
|
||||
type: ValidationIssueType.map_rule_issue,
|
||||
type: ValidationIssueType.maprules,
|
||||
severity: severity,
|
||||
message: selector[severity],
|
||||
entities: [entity],
|
||||
|
||||
@@ -334,7 +334,7 @@ export function uiCommit(context) {
|
||||
|
||||
|
||||
function getUploadBlockerMessage() {
|
||||
var errorCount = context.issueManager().getErrors().length;
|
||||
var errorCount = context.validator().getErrors().length;
|
||||
if (errorCount > 0) {
|
||||
return t('commit.outstanding_errors_message', { count: errorCount });
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export function uiCommitWarnings(context) {
|
||||
|
||||
function commitWarnings(selection) {
|
||||
|
||||
var issues = context.issueManager().getIssues();
|
||||
var issues = context.validator().getIssues();
|
||||
|
||||
issues = _reduce(issues, function(issues, val) {
|
||||
var severity = val.severity;
|
||||
|
||||
@@ -16,7 +16,7 @@ export function uiEntityIssues(context) {
|
||||
var dispatch = d3_dispatch('change');
|
||||
var _entityID;
|
||||
|
||||
context.issueManager().on('reload.entity_issues', update);
|
||||
context.validator().on('reload.entity_issues', update);
|
||||
|
||||
function update() {
|
||||
var selection = d3_select('.entity-issues .disclosure-wrap');
|
||||
@@ -33,7 +33,7 @@ export function uiEntityIssues(context) {
|
||||
|
||||
function render(selection) {
|
||||
|
||||
var issues = context.issueManager().getIssuesForEntityWithID(_entityID);
|
||||
var issues = context.validator().getIssuesForEntityWithID(_entityID);
|
||||
|
||||
if (issues.length > 0) {
|
||||
d3_select('.entity-issues')
|
||||
|
||||
@@ -21,7 +21,7 @@ export function uiIssues(context) {
|
||||
var pane = d3_select(null);
|
||||
var _shown = false;
|
||||
|
||||
context.issueManager().on('reload.issues_pane', update);
|
||||
context.validator().on('reload.issues_pane', update);
|
||||
|
||||
function renderIssuesOptions(selection) {
|
||||
var container = selection.selectAll('.issues-options-container')
|
||||
@@ -100,7 +100,7 @@ export function uiIssues(context) {
|
||||
|
||||
function drawIssuesList(selection) {
|
||||
|
||||
var issues = context.issueManager().getIssues();
|
||||
var issues = context.validator().getIssues();
|
||||
|
||||
/*validations = _reduce(issues, function(validations, val) {
|
||||
var severity = val.severity;
|
||||
@@ -175,11 +175,11 @@ export function uiIssues(context) {
|
||||
}
|
||||
|
||||
function showsFeatureApplicability(d) {
|
||||
return context.issueManager().getFeatureApplicability() === d;
|
||||
return context.validator().getFeatureApplicability() === d;
|
||||
}
|
||||
|
||||
function setFeatureApplicability(d) {
|
||||
context.issueManager().setFeatureApplicability(d);
|
||||
context.validator().setFeatureApplicability(d);
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ export function uiIssues(context) {
|
||||
_featureApplicabilityList
|
||||
.call(
|
||||
drawListItems,
|
||||
context.issueManager().featureApplicabilityOptions,
|
||||
context.validator().featureApplicabilityOptions,
|
||||
'radio',
|
||||
'features_to_validate',
|
||||
setFeatureApplicability,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
ValidationIssueSeverity,
|
||||
validationIssue,
|
||||
validationIssueFix
|
||||
} from './validation_issue';
|
||||
} from '../core/validator';
|
||||
import {
|
||||
actionChangeTags
|
||||
} from '../actions';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ValidationIssueSeverity,
|
||||
validationIssue,
|
||||
validationIssueFix
|
||||
} from './validation_issue';
|
||||
} from '../core/validator';
|
||||
import { operationDelete } from '../operations/index';
|
||||
|
||||
export function validationMissingTag() {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
ValidationIssueSeverity,
|
||||
validationIssue,
|
||||
validationIssueFix
|
||||
} from './validation_issue';
|
||||
} from '../core/validator';
|
||||
import {
|
||||
actionChangeTags
|
||||
} from '../actions';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
describe('iD.validations.IssueManager', function () {
|
||||
describe('iD.validations.coreValidator', function () {
|
||||
var context;
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -19,19 +19,19 @@ describe('iD.validations.IssueManager', function () {
|
||||
}
|
||||
|
||||
it('has no issues on init', function() {
|
||||
var issueManager = new iD.IssueManager(context);
|
||||
var issues = issueManager.getIssues();
|
||||
var validator = new iD.coreValidator(context);
|
||||
var issues = validator.getIssues();
|
||||
expect(issues).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('populates issues on validate', function() {
|
||||
createInvalidWay();
|
||||
var issueManager = new iD.IssueManager(context);
|
||||
var issues = issueManager.getIssues();
|
||||
var validator = new iD.coreValidator(context);
|
||||
var issues = validator.getIssues();
|
||||
expect(issues).to.have.lengthOf(0);
|
||||
|
||||
issueManager.validate();
|
||||
issues = issueManager.getIssues();
|
||||
validator.validate();
|
||||
issues = validator.getIssues();
|
||||
expect(issues).to.have.lengthOf(1);
|
||||
var issue = issues[0];
|
||||
expect(issue.type).to.eql(iD.ValidationIssueType.missing_tag);
|
||||
|
||||
Reference in New Issue
Block a user