mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
Merge branch 'master' into validation
This commit is contained in:
@@ -4167,6 +4167,10 @@ svg.mouseclick use.right {
|
||||
background: #ffb;
|
||||
}
|
||||
|
||||
.mode-save .error-section {
|
||||
background: #ffa5a5;
|
||||
}
|
||||
|
||||
.mode-save .warning-section .changeset-list button {
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,7 @@ en:
|
||||
cancel: Cancel
|
||||
changes: "{count} Changes"
|
||||
download_changes: Download osmChange file
|
||||
errors: Errors
|
||||
warnings: Warnings
|
||||
modified: Modified
|
||||
deleted: Deleted
|
||||
|
||||
Vendored
+1
@@ -408,6 +408,7 @@
|
||||
"cancel": "Cancel",
|
||||
"changes": "{count} Changes",
|
||||
"download_changes": "Download osmChange file",
|
||||
"errors": "Errors",
|
||||
"warnings": "Warnings",
|
||||
"modified": "Modified",
|
||||
"deleted": "Deleted",
|
||||
|
||||
+24
-4
@@ -21,7 +21,8 @@ import { rendererBackground, rendererFeatures, rendererMap } from '../renderer';
|
||||
import { services } from '../services';
|
||||
import { uiInit } from '../ui/init';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { utilCallWhenIdle, utilKeybinding, utilRebind } from '../util';
|
||||
import { utilCallWhenIdle, utilKeybinding, utilRebind, utilStringQs } from '../util';
|
||||
|
||||
|
||||
|
||||
export var areaKeys = {};
|
||||
@@ -470,6 +471,18 @@ export function coreContext() {
|
||||
features = rendererFeatures(context);
|
||||
presets = presetIndex();
|
||||
|
||||
if (services.maprules && utilStringQs(window.location.hash).validations) {
|
||||
var validations = utilStringQs(window.location.hash).validations;
|
||||
d3_json(validations, function (err, mapcss) {
|
||||
if (err) return;
|
||||
services.maprules.init(context.presets().areaKeys());
|
||||
_each(mapcss, function(mapcssSelector) {
|
||||
return services.maprules.addRule(mapcssSelector);
|
||||
});
|
||||
context.validationRules = true;
|
||||
});
|
||||
}
|
||||
|
||||
map = rendererMap(context);
|
||||
context.mouse = map.mouse;
|
||||
context.extent = map.extent;
|
||||
@@ -488,9 +501,16 @@ export function coreContext() {
|
||||
|
||||
background.init();
|
||||
features.init();
|
||||
presets.init();
|
||||
areaKeys = presets.areaKeys();
|
||||
|
||||
if (utilStringQs(window.location.hash).presets) {
|
||||
var external = utilStringQs(window.location.hash).presets;
|
||||
presets.fromExternal(external, function(externalPresets) {
|
||||
context.presets = function() { return externalPresets; }; // default + external presets...
|
||||
areaKeys = presets.areaKeys();
|
||||
});
|
||||
} else {
|
||||
presets.init();
|
||||
areaKeys = presets.areaKeys();
|
||||
}
|
||||
|
||||
return utilRebind(context, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -281,9 +281,9 @@ export function coreHistory(context) {
|
||||
|
||||
|
||||
validate: function(changes) {
|
||||
return _flatten(
|
||||
_map(Validations, function(fn) { return fn()(changes, _stack[_index].graph); })
|
||||
);
|
||||
return _flatten(_map(Validations, function(fn) {
|
||||
return fn()(changes, _stack[_index].graph);
|
||||
}));
|
||||
},
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import _filter from 'lodash-es/filter';
|
||||
import _find from 'lodash-es/find';
|
||||
import _findIndex from 'lodash-es/findIndex';
|
||||
import _some from 'lodash-es/some';
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
import _values from 'lodash-es/values';
|
||||
@@ -23,6 +24,11 @@ export function presetCollection(collection) {
|
||||
});
|
||||
},
|
||||
|
||||
index: function(id) {
|
||||
return _findIndex(this.collection, function(d) {
|
||||
return d.id === id;
|
||||
});
|
||||
},
|
||||
|
||||
matchGeometry: function(geometry) {
|
||||
return presetCollection(this.collection.filter(function(d) {
|
||||
|
||||
@@ -21,7 +21,7 @@ export function presetField(id, field) {
|
||||
|
||||
|
||||
field.label = function() {
|
||||
return field.t('label', {'default': id});
|
||||
return field.overrideLabel || field.t('label', {'default': id});
|
||||
};
|
||||
|
||||
|
||||
|
||||
+57
-13
@@ -3,6 +3,8 @@ import _forEach from 'lodash-es/forEach';
|
||||
import _reject from 'lodash-es/reject';
|
||||
import _uniq from 'lodash-es/uniq';
|
||||
|
||||
import { json as d3_json } from 'd3-request';
|
||||
|
||||
import { data } from '../../data/index';
|
||||
import { presetCategory } from './category';
|
||||
import { presetCollection } from './collection';
|
||||
@@ -70,7 +72,6 @@ export function presetIndex() {
|
||||
if (address && (!match || match.isFallback())) {
|
||||
match = address;
|
||||
}
|
||||
|
||||
return match || all.item(geometry);
|
||||
});
|
||||
};
|
||||
@@ -120,16 +121,7 @@ export function presetIndex() {
|
||||
return areaKeys;
|
||||
};
|
||||
|
||||
|
||||
all.init = function() {
|
||||
var d = data.presets;
|
||||
|
||||
all.collection = [];
|
||||
_recent.collection = [];
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
all.build = function(d, visible) {
|
||||
if (d.fields) {
|
||||
_forEach(d.fields, function(d, id) {
|
||||
_fields[id] = presetField(id, d);
|
||||
@@ -145,13 +137,23 @@ export function presetIndex() {
|
||||
|
||||
if (d.presets) {
|
||||
_forEach(d.presets, function(d, id) {
|
||||
all.collection.push(presetPreset(id, d, _fields));
|
||||
var existing = all.index(id);
|
||||
if (existing !== -1) {
|
||||
all.collection[existing] = presetPreset(id, d, _fields, visible);
|
||||
} else {
|
||||
all.collection.push(presetPreset(id, d, _fields, visible));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (d.categories) {
|
||||
_forEach(d.categories, function(d, id) {
|
||||
all.collection.push(presetCategory(id, d, all));
|
||||
var existing = all.index(id);
|
||||
if (existing !== -1) {
|
||||
all.collection[existing] = presetCategory(id, d, all);
|
||||
} else {
|
||||
all.collection.push(presetCategory(id, d, all));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -177,10 +179,52 @@ export function presetIndex() {
|
||||
}
|
||||
}
|
||||
}
|
||||
return all;
|
||||
};
|
||||
|
||||
all.init = function() {
|
||||
all.collection = [];
|
||||
_recent.collection = [];
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_index = { point: {}, vertex: {}, line: {}, area: {}, relation: {} };
|
||||
|
||||
return all.build(data.presets, true);
|
||||
};
|
||||
|
||||
|
||||
all.reset = function() {
|
||||
all.collection = [];
|
||||
_defaults = { area: all, line: all, point: all, vertex: all, relation: all };
|
||||
_fields = {};
|
||||
_universal = [];
|
||||
_recent = presetCollection([]);
|
||||
|
||||
// Index of presets by (geometry, tag key).
|
||||
_index = {
|
||||
point: {},
|
||||
vertex: {},
|
||||
line: {},
|
||||
area: {},
|
||||
relation: {}
|
||||
};
|
||||
|
||||
return all;
|
||||
};
|
||||
|
||||
all.fromExternal = function(external, done) {
|
||||
all.reset();
|
||||
d3_json(external, function(err, externalPresets) {
|
||||
if (err) {
|
||||
all.init();
|
||||
} else {
|
||||
all.build(data.presets, false); // make default presets hidden to begin
|
||||
all.build(externalPresets, true); // make the external visible
|
||||
}
|
||||
done(all);
|
||||
});
|
||||
};
|
||||
|
||||
all.field = function(id) {
|
||||
return _fields[id];
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { t } from '../util/locale';
|
||||
import { areaKeys } from '../core/context';
|
||||
|
||||
|
||||
export function presetPreset(id, preset, fields) {
|
||||
export function presetPreset(id, preset, fields, visible) {
|
||||
preset = _clone(preset);
|
||||
|
||||
preset.id = id;
|
||||
@@ -13,6 +13,7 @@ export function presetPreset(id, preset, fields) {
|
||||
preset.moreFields = (preset.moreFields || []).map(getFields);
|
||||
preset.geometry = (preset.geometry || []);
|
||||
|
||||
visible = visible || false;
|
||||
|
||||
function getFields(f) {
|
||||
return fields[f];
|
||||
@@ -71,6 +72,12 @@ export function presetPreset(id, preset, fields) {
|
||||
return tagCount === 0 || (tagCount === 1 && preset.tags.hasOwnProperty('area'));
|
||||
};
|
||||
|
||||
preset.visible = function(_) {
|
||||
if (!arguments.length) return visible;
|
||||
visible = _;
|
||||
return visible;
|
||||
};
|
||||
|
||||
|
||||
var reference = preset.reference || {};
|
||||
preset.reference = function(geometry) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import serviceMapillary from './mapillary';
|
||||
import serviceMapRules from './maprules';
|
||||
import serviceNominatim from './nominatim';
|
||||
import serviceOpenstreetcam from './openstreetcam';
|
||||
import serviceOsm from './osm';
|
||||
@@ -8,11 +9,13 @@ import serviceVectorTile from './vector_tile';
|
||||
import serviceWikidata from './wikidata';
|
||||
import serviceWikipedia from './wikipedia';
|
||||
|
||||
|
||||
export var services = {
|
||||
geocoder: serviceNominatim,
|
||||
mapillary: serviceMapillary,
|
||||
openstreetcam: serviceOpenstreetcam,
|
||||
osm: serviceOsm,
|
||||
maprules: serviceMapRules,
|
||||
streetside: serviceStreetside,
|
||||
taginfo: serviceTaginfo,
|
||||
vectorTile: serviceVectorTile,
|
||||
@@ -22,6 +25,7 @@ export var services = {
|
||||
|
||||
export {
|
||||
serviceMapillary,
|
||||
serviceMapRules,
|
||||
serviceNominatim,
|
||||
serviceOpenstreetcam,
|
||||
serviceOsm,
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import _isMatch from 'lodash-es/isMatch';
|
||||
import _intersection from 'lodash-es/intersection';
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _every from 'lodash-es/every';
|
||||
|
||||
var buildRuleChecks = function() {
|
||||
return {
|
||||
equals: function (equals) {
|
||||
return function(tags) {
|
||||
return _isMatch(tags, equals);
|
||||
};
|
||||
},
|
||||
notEquals: function (notEquals) {
|
||||
return function(tags) {
|
||||
return !_isMatch(tags, notEquals);
|
||||
};
|
||||
},
|
||||
absence: function(absence) {
|
||||
return function(tags) {
|
||||
return Object.keys(tags).indexOf(absence) === -1;
|
||||
};
|
||||
},
|
||||
presence: function(presence) {
|
||||
return function(tags) {
|
||||
return Object.keys(tags).indexOf(presence) > -1;
|
||||
};
|
||||
},
|
||||
greaterThan: function(greaterThan) {
|
||||
var key = Object.keys(greaterThan)[0];
|
||||
var value = greaterThan[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] > value;
|
||||
};
|
||||
},
|
||||
greaterThanEqual: function(greaterThanEqual) {
|
||||
var key = Object.keys(greaterThanEqual)[0];
|
||||
var value = greaterThanEqual[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] >= value;
|
||||
};
|
||||
},
|
||||
lessThan: function(lessThan) {
|
||||
var key = Object.keys(lessThan)[0];
|
||||
var value = lessThan[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] < value;
|
||||
};
|
||||
},
|
||||
lessThanEqual: function(lessThanEqual) {
|
||||
var key = Object.keys(lessThanEqual)[0];
|
||||
var value = lessThanEqual[key];
|
||||
|
||||
return function(tags) {
|
||||
return tags[key] <= value;
|
||||
};
|
||||
},
|
||||
positiveRegex: function(positiveRegex) {
|
||||
var tagKey = Object.keys(positiveRegex)[0];
|
||||
var expression = positiveRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
|
||||
return function(tags) {
|
||||
return regex.test(tags[tagKey]);
|
||||
};
|
||||
},
|
||||
negativeRegex: function(negativeRegex) {
|
||||
var tagKey = Object.keys(negativeRegex)[0];
|
||||
var expression = negativeRegex[tagKey].join('|');
|
||||
var regex = new RegExp(expression);
|
||||
|
||||
return function(tags) {
|
||||
return !regex.test(tags[tagKey]);
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var buildLineKeys = function() {
|
||||
return {
|
||||
highway: {
|
||||
rest_area: true,
|
||||
services: true
|
||||
},
|
||||
railway: {
|
||||
roundhouse: true,
|
||||
station: true,
|
||||
traverser: true,
|
||||
turntable: true,
|
||||
wash: true
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export default {
|
||||
init: function(areaKeys) {
|
||||
this._ruleChecks = buildRuleChecks();
|
||||
this._validationRules = [];
|
||||
this._areaKeys = areaKeys;
|
||||
this._lineKeys = buildLineKeys();
|
||||
},
|
||||
// list of rules only relevant to tag checks...
|
||||
filterRuleChecks: function(selector) {
|
||||
var _ruleChecks = this._ruleChecks;
|
||||
return _reduce(Object.keys(selector), function(rules, key) {
|
||||
if (['geometry', 'error', 'warning'].indexOf(key) === -1) {
|
||||
rules.push(_ruleChecks[key](selector[key]));
|
||||
}
|
||||
return rules;
|
||||
}, []);
|
||||
},
|
||||
// builds tagMap from mapcss-parse selector object...
|
||||
buildTagMap: function(selector) {
|
||||
var getRegexValues = function(regexes) {
|
||||
return regexes.map(function(regex) {
|
||||
return regex.replace(/\$|\^/g, '');
|
||||
});
|
||||
};
|
||||
|
||||
var selectorKeys = Object.keys(selector);
|
||||
var tagMap = _reduce(selectorKeys, function (expectedTags, key) {
|
||||
var values;
|
||||
var isRegex = /regex/gi.test(key);
|
||||
var isEqual = /equals/gi.test(key);
|
||||
|
||||
if (isRegex || isEqual) {
|
||||
Object.keys(selector[key]).forEach(function(selectorKey) {
|
||||
values = isEqual ? [selector[key][selectorKey]] : getRegexValues(selector[key][selectorKey]);
|
||||
|
||||
if (expectedTags.hasOwnProperty(selectorKey)) {
|
||||
values = values.concat(expectedTags[selectorKey]);
|
||||
}
|
||||
|
||||
expectedTags[selectorKey] = values;
|
||||
});
|
||||
|
||||
} else if (/(greater|less)Than(Equal)?|presence/g.test(key)) {
|
||||
var tagKey = /presence/.test(key) ? selector[key] : Object.keys(selector[key])[0];
|
||||
|
||||
values = [selector[key][tagKey]];
|
||||
|
||||
if (expectedTags.hasOwnProperty(tagKey)) {
|
||||
values = values.concat(expectedTags[tagKey]);
|
||||
}
|
||||
|
||||
expectedTags[tagKey] = values;
|
||||
}
|
||||
|
||||
return expectedTags;
|
||||
}, {});
|
||||
|
||||
return tagMap;
|
||||
},
|
||||
// inspired by osmWay#isArea()
|
||||
inferGeometry: function(tagMap) {
|
||||
var _lineKeys = this._lineKeys;
|
||||
var _areaKeys = this._areaKeys;
|
||||
|
||||
var isAreaKeyBlackList = function(key) {
|
||||
return _intersection(tagMap[key], Object.keys(_areaKeys[key])).length > 0;
|
||||
};
|
||||
var isLineKeysWhiteList = function(key) {
|
||||
return _intersection(tagMap[key], Object.keys(_lineKeys[key])).length > 0;
|
||||
};
|
||||
|
||||
if (tagMap.hasOwnProperty('area')) {
|
||||
if (tagMap.area.indexOf('yes') > -1) {
|
||||
return 'area';
|
||||
}
|
||||
if (tagMap.area.indexOf('no') > -1) {
|
||||
return 'line';
|
||||
}
|
||||
}
|
||||
|
||||
for (var key in tagMap) {
|
||||
if (key in _areaKeys && !isAreaKeyBlackList(key)) {
|
||||
return 'area';
|
||||
}
|
||||
if (key in _lineKeys && isLineKeysWhiteList(key)) {
|
||||
return 'area';
|
||||
}
|
||||
}
|
||||
|
||||
return 'line';
|
||||
},
|
||||
// adds from mapcss-parse selector check...
|
||||
addRule: function(selector) {
|
||||
var rule = {
|
||||
// checks relevant to mapcss-selector
|
||||
checks: this.filterRuleChecks(selector),
|
||||
// true if all conditions for a tag error are true..
|
||||
matches: function(entity) {
|
||||
return _every(this.checks, function(check) {
|
||||
return check(entity.tags);
|
||||
});
|
||||
},
|
||||
// borrowed from Way#isArea()
|
||||
inferredGeometry: this.inferGeometry(this.buildTagMap(selector), this._areaKeys),
|
||||
geometryMatches: function(entity, graph) {
|
||||
if (entity.type === 'node' || entity.type === 'relation') {
|
||||
return selector.geometry === entity.type;
|
||||
} else if (entity.type === 'way') {
|
||||
return this.inferredGeometry === entity.geometry(graph);
|
||||
}
|
||||
},
|
||||
// when geometries match and tag matches are present, return a warning...
|
||||
findWarnings: function (entity, graph, warnings) {
|
||||
if (this.geometryMatches(entity, graph) && this.matches(entity)) {
|
||||
var type = Object.keys(selector).indexOf('error') > -1 ? 'error' : 'warning';
|
||||
warnings.push({
|
||||
severity: type,
|
||||
message: selector[type],
|
||||
entity: entity
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
this._validationRules.push(rule);
|
||||
},
|
||||
clearRules: function() { this._validationRules = []; },
|
||||
// returns validationRules...
|
||||
validationRules: function() { return this._validationRules; },
|
||||
// returns ruleChecks
|
||||
ruleChecks: function() { return this._ruleChecks; }
|
||||
};
|
||||
@@ -3,91 +3,108 @@ import { modeSelect } from '../modes';
|
||||
import { svgIcon } from '../svg';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { utilEntityOrMemberSelector } from '../util';
|
||||
|
||||
import _reduce from 'lodash-es/reduce';
|
||||
import _forEach from 'lodash-es/forEach';
|
||||
import _uniqBy from 'lodash-es/uniqBy';
|
||||
|
||||
export function uiCommitWarnings(context) {
|
||||
|
||||
function commitWarnings(selection) {
|
||||
|
||||
var changes = context.history().changes();
|
||||
var warnings = context.history().validate(changes);
|
||||
var validations = context.history().validate(changes);
|
||||
|
||||
var container = selection.selectAll('.warning-section')
|
||||
.data(warnings.length ? [0] : []);
|
||||
|
||||
container.exit()
|
||||
.remove();
|
||||
|
||||
var containerEnter = container.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section warning-section fillL2');
|
||||
|
||||
containerEnter
|
||||
.append('h3')
|
||||
.text(t('commit.warnings'));
|
||||
|
||||
containerEnter
|
||||
.append('ul')
|
||||
.attr('class', 'changeset-list');
|
||||
|
||||
container = containerEnter
|
||||
.merge(container);
|
||||
|
||||
|
||||
var items = container.select('ul').selectAll('li')
|
||||
.data(warnings);
|
||||
|
||||
items.exit()
|
||||
.remove();
|
||||
|
||||
var itemsEnter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', 'warning-item');
|
||||
|
||||
itemsEnter
|
||||
.call(svgIcon('#iD-icon-alert', 'pre-text'));
|
||||
|
||||
itemsEnter
|
||||
.append('strong')
|
||||
.text(function(d) { return d.message; });
|
||||
|
||||
itemsEnter.filter(function(d) { return d.tooltip; })
|
||||
.call(tooltip()
|
||||
.title(function(d) { return d.tooltip; })
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
items = itemsEnter
|
||||
.merge(items);
|
||||
|
||||
items
|
||||
.on('mouseover', mouseover)
|
||||
.on('mouseout', mouseout)
|
||||
.on('click', warningClick);
|
||||
|
||||
|
||||
function mouseover(d) {
|
||||
if (d.entity) {
|
||||
context.surface().selectAll(
|
||||
utilEntityOrMemberSelector([d.entity.id], context.graph())
|
||||
).classed('hover', true);
|
||||
validations = _reduce(validations, function(validations, val) {
|
||||
var severity = val.severity;
|
||||
if (validations.hasOwnProperty(severity)) {
|
||||
validations[severity].push(val);
|
||||
} else {
|
||||
validations[severity] = [val];
|
||||
}
|
||||
}
|
||||
return validations;
|
||||
}, {});
|
||||
|
||||
_forEach(validations, function(instances, type) {
|
||||
instances = _uniqBy(instances, function(val) { return val.id + '_' + val.message.replace(/\s+/g,''); });
|
||||
var section = type + '-section';
|
||||
var instanceItem = type + '-item';
|
||||
|
||||
var container = selection.selectAll('.' + section)
|
||||
.data(instances.length ? [0] : []);
|
||||
|
||||
container.exit()
|
||||
.remove();
|
||||
|
||||
var containerEnter = container.enter()
|
||||
.append('div')
|
||||
.attr('class', 'modal-section ' + section + ' fillL2');
|
||||
|
||||
containerEnter
|
||||
.append('h3')
|
||||
.text(type === 'warning' ? t('commit.warnings') : t('commit.errors'));
|
||||
|
||||
containerEnter
|
||||
.append('ul')
|
||||
.attr('class', 'changeset-list');
|
||||
|
||||
container = containerEnter
|
||||
.merge(container);
|
||||
|
||||
|
||||
function mouseout() {
|
||||
context.surface().selectAll('.hover')
|
||||
.classed('hover', false);
|
||||
}
|
||||
var items = container.select('ul').selectAll('li')
|
||||
.data(instances);
|
||||
|
||||
items.exit()
|
||||
.remove();
|
||||
|
||||
var itemsEnter = items.enter()
|
||||
.append('li')
|
||||
.attr('class', instanceItem);
|
||||
|
||||
itemsEnter
|
||||
.call(svgIcon('#iD-icon-alert', 'pre-text'));
|
||||
|
||||
itemsEnter
|
||||
.append('strong')
|
||||
.text(function(d) { return d.message; });
|
||||
|
||||
itemsEnter.filter(function(d) { return d.tooltip; })
|
||||
.call(tooltip()
|
||||
.title(function(d) { return d.tooltip; })
|
||||
.placement('top')
|
||||
);
|
||||
|
||||
items = itemsEnter
|
||||
.merge(items);
|
||||
|
||||
items
|
||||
.on('mouseover', mouseover)
|
||||
.on('mouseout', mouseout)
|
||||
.on('click', warningClick);
|
||||
|
||||
|
||||
function warningClick(d) {
|
||||
if (d.entity) {
|
||||
context.map().zoomTo(d.entity);
|
||||
context.enter(modeSelect(context, [d.entity.id]));
|
||||
function mouseover(d) {
|
||||
if (d.entity) {
|
||||
context.surface().selectAll(
|
||||
utilEntityOrMemberSelector([d.entity.id], context.graph())
|
||||
).classed('hover', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mouseout() {
|
||||
context.surface().selectAll('.hover')
|
||||
.classed('hover', false);
|
||||
}
|
||||
|
||||
|
||||
function warningClick(d) {
|
||||
if (d.entity) {
|
||||
context.map().zoomTo(d.entity);
|
||||
context.enter(modeSelect(context, [d.entity.id]));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ export function uiEntityEditor(context) {
|
||||
var rawMemberEditor = uiRawMemberEditor(context);
|
||||
var rawMembershipEditor = uiRawMembershipEditor(context);
|
||||
|
||||
|
||||
function entityEditor(selection) {
|
||||
var entity = context.entity(_entityID);
|
||||
var tags = _clone(entity.tags);
|
||||
|
||||
@@ -467,7 +467,7 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
function renderDataLayers(selection) {
|
||||
var container = selection.selectAll('data-layer-container')
|
||||
var container = selection.selectAll('.data-layer-container')
|
||||
.data([0]);
|
||||
|
||||
_dataLayerContainer = container.enter()
|
||||
@@ -478,7 +478,7 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
function renderFillList(selection) {
|
||||
var container = selection.selectAll('layer-fill-list')
|
||||
var container = selection.selectAll('.layer-fill-list')
|
||||
.data([0]);
|
||||
|
||||
_fillList = container.enter()
|
||||
@@ -489,7 +489,7 @@ export function uiMapData(context) {
|
||||
|
||||
|
||||
function renderFeatureList(selection) {
|
||||
var container = selection.selectAll('layer-feature-list')
|
||||
var container = selection.selectAll('.layer-feature-list')
|
||||
.data([0]);
|
||||
|
||||
_featureList = container.enter()
|
||||
|
||||
@@ -14,7 +14,6 @@ import { svgIcon } from '../svg';
|
||||
import { tooltip } from '../util/tooltip';
|
||||
import { uiTooltipHtml } from './tooltipHtml';
|
||||
|
||||
|
||||
export function uiModes(context) {
|
||||
var modes = [
|
||||
modeAddPoint(context),
|
||||
@@ -23,7 +22,6 @@ export function uiModes(context) {
|
||||
modeAddNote(context)
|
||||
];
|
||||
|
||||
|
||||
function editable() {
|
||||
var mode = context.mode();
|
||||
return context.editable() && mode && mode.id !== 'save';
|
||||
@@ -39,7 +37,6 @@ export function uiModes(context) {
|
||||
return context.map().notesEditable() && mode && mode.id !== 'save';
|
||||
}
|
||||
|
||||
|
||||
return function(selection) {
|
||||
context
|
||||
.on('enter.editor', function(entered) {
|
||||
|
||||
@@ -151,9 +151,14 @@ export function uiPresetList(context) {
|
||||
|
||||
|
||||
function drawList(list, presets) {
|
||||
var collection = presets.collection.map(function(preset) {
|
||||
return preset.members ? CategoryItem(preset) : PresetItem(preset);
|
||||
});
|
||||
var collection = presets.collection.reduce(function(collection, preset) {
|
||||
if (preset.members) {
|
||||
collection.push(CategoryItem(preset));
|
||||
} else if (preset.visible()) {
|
||||
collection.push(PresetItem(preset));
|
||||
}
|
||||
return collection;
|
||||
}, []);
|
||||
|
||||
var items = list.selectAll('.preset-list-item')
|
||||
.data(collection, function(d) { return d.preset.id; });
|
||||
|
||||
@@ -8,6 +8,8 @@ export { utilEditDistance } from './util';
|
||||
export { utilEntitySelector } from './util';
|
||||
export { utilEntityOrMemberSelector } from './util';
|
||||
export { utilEntityOrDeepMemberSelector } from './util';
|
||||
export { utilExternalPresets } from './util';
|
||||
export { utilExternalValidationRules } from './util';
|
||||
export { utilFastMouse } from './util';
|
||||
export { utilFunctor } from './util';
|
||||
export { utilGetAllNodes } from './util';
|
||||
@@ -25,6 +27,7 @@ export { utilRebind } from './rebind';
|
||||
export { utilSetTransform } from './util';
|
||||
export { utilSessionMutex } from './session_mutex';
|
||||
export { utilStringQs } from './util';
|
||||
// export { utilSuggestNames } from './suggest_names';
|
||||
export { utilTagText } from './util';
|
||||
export { utilTiler } from './tiler';
|
||||
export { utilTriggerEvent } from './trigger_event';
|
||||
|
||||
@@ -293,6 +293,13 @@ export function utilNoAuto(selection) {
|
||||
.attr('spellcheck', isText ? 'true' : 'false');
|
||||
}
|
||||
|
||||
export function utilExternalPresets() {
|
||||
return utilStringQs(window.location.hash).hasOwnProperty('presets');
|
||||
}
|
||||
|
||||
export function utilExternalValidationRules() {
|
||||
return utilStringQs(window.location.hash).hasOwnProperty('validations');
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/194846/is-there-any-kind-of-hash-code-function-in-javascript
|
||||
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export { validationDeprecatedTag } from './deprecated_tag';
|
||||
export { validationDisconnectedHighway } from './disconnected_highway';
|
||||
export { validationManyDeletions } from './many_deletions';
|
||||
export { validationMapCSSChecks } from './mapcss_checks';
|
||||
export { validationMissingTag } from './missing_tag';
|
||||
export { validationOldMultipolygon } from './old_multipolygon';
|
||||
export { validationTagSuggestsArea } from './tag_suggests_area';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { services } from '../services';
|
||||
|
||||
export function validationMapCSSChecks() {
|
||||
var validation = function(changes, graph) {
|
||||
if (!services.maprules) return [];
|
||||
|
||||
var rules = services.maprules.validationRules();
|
||||
var warnings = [];
|
||||
var createdModified = ['created', 'modified'];
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
var rule = rules[i];
|
||||
for (var j = 0; j < createdModified.length; j++) {
|
||||
var type = createdModified[j];
|
||||
var entities = changes[type];
|
||||
for (var k = 0; k < entities.length; k++) {
|
||||
rule.findWarnings(entities[k], graph, warnings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
};
|
||||
return validation;
|
||||
}
|
||||
@@ -106,6 +106,7 @@
|
||||
<script src='spec/renderer/tile_layer.js'></script>
|
||||
|
||||
<script src='spec/services/mapillary.js'></script>
|
||||
<script src='spec/services/maprules.js'></script>
|
||||
<script src='spec/services/nominatim.js'></script>
|
||||
<script src='spec/services/openstreetcam.js'></script>
|
||||
<script src='spec/services/osm.js'></script>
|
||||
@@ -118,6 +119,7 @@
|
||||
<script src='spec/svg/layers.js'></script>
|
||||
<script src='spec/svg/lines.js'></script>
|
||||
<script src='spec/svg/midpoints.js'></script>
|
||||
<script src='spec/svg/mvt.js'></script>
|
||||
<script src='spec/svg/osm.js'></script>
|
||||
<script src='spec/svg/points.js'></script>
|
||||
<script src='spec/svg/svg.js'></script>
|
||||
|
||||
@@ -84,6 +84,15 @@ describe('iD.presetCollection', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#index', function() {
|
||||
it('returns preset position in the collection', function() {
|
||||
expect(c.index('point')).to.equal(0);
|
||||
});
|
||||
it('return -1 when given id for preset not in the collection', function() {
|
||||
expect(c.index('foobar')).to.equal(-1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#matchGeometry', function() {
|
||||
it('returns a new collection only containing presets matching a geometry', function() {
|
||||
expect(c.matchGeometry('area').collection).to.include.members(
|
||||
|
||||
+283
-23
@@ -1,5 +1,5 @@
|
||||
describe('iD.presetIndex', function() {
|
||||
var savedPresets;
|
||||
describe('iD.presetIndex', function () {
|
||||
var savedPresets, server;
|
||||
|
||||
before(function () {
|
||||
savedPresets = iD.data.presets;
|
||||
@@ -9,7 +9,7 @@ describe('iD.presetIndex', function() {
|
||||
iD.data.presets = savedPresets;
|
||||
});
|
||||
|
||||
describe('#match', function() {
|
||||
describe('#match', function () {
|
||||
var testPresets = {
|
||||
presets: {
|
||||
point: {
|
||||
@@ -35,7 +35,7 @@ describe('iD.presetIndex', function() {
|
||||
}
|
||||
};
|
||||
|
||||
it('returns a collection containing presets matching a geometry and tags', function() {
|
||||
it('returns a collection containing presets matching a geometry and tags', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
way = iD.Way({ tags: { highway: 'residential' } }),
|
||||
@@ -44,7 +44,7 @@ describe('iD.presetIndex', function() {
|
||||
expect(presets.match(way, graph).id).to.eql('residential');
|
||||
});
|
||||
|
||||
it('returns the appropriate fallback preset when no tags match', function() {
|
||||
it('returns the appropriate fallback preset when no tags match', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
point = iD.Node(),
|
||||
@@ -55,7 +55,7 @@ describe('iD.presetIndex', function() {
|
||||
expect(presets.match(line, graph).id).to.eql('line');
|
||||
});
|
||||
|
||||
it('matches vertices on a line as vertices', function() {
|
||||
it('matches vertices on a line as vertices', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
point = iD.Node({ tags: { leisure: 'park' } }),
|
||||
@@ -65,7 +65,7 @@ describe('iD.presetIndex', function() {
|
||||
expect(presets.match(point, graph).id).to.eql('vertex');
|
||||
});
|
||||
|
||||
it('matches vertices on an addr:interpolation line as points', function() {
|
||||
it('matches vertices on an addr:interpolation line as points', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
point = iD.Node({ tags: { leisure: 'park' } }),
|
||||
@@ -77,12 +77,12 @@ describe('iD.presetIndex', function() {
|
||||
});
|
||||
|
||||
|
||||
describe('#areaKeys', function() {
|
||||
describe('#areaKeys', function () {
|
||||
var testPresets = {
|
||||
presets: {
|
||||
'amenity/fuel/shell': {
|
||||
tags: { 'amenity': 'fuel' },
|
||||
geometry: ['point','area'],
|
||||
geometry: ['point', 'area'],
|
||||
suggestion: true
|
||||
},
|
||||
'highway/foo': {
|
||||
@@ -110,78 +110,338 @@ describe('iD.presetIndex', function() {
|
||||
geometry: ['point', 'area']
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
it('whitelists keys for presets with area geometry', function() {
|
||||
it('whitelists keys for presets with area geometry', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys()).to.include.keys('natural');
|
||||
});
|
||||
|
||||
it('blacklists key-values for presets with a line geometry', function() {
|
||||
it('blacklists key-values for presets with a line geometry', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys().natural).to.include.keys('tree_row');
|
||||
expect(presets.areaKeys().natural.tree_row).to.be.true;
|
||||
});
|
||||
|
||||
it('blacklists key-values for presets with both area and line geometry', function() {
|
||||
it('blacklists key-values for presets with both area and line geometry', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys().leisure).to.include.keys('track');
|
||||
});
|
||||
|
||||
it('does not blacklist key-values for presets with neither area nor line geometry', function() {
|
||||
it('does not blacklist key-values for presets with neither area nor line geometry', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys().natural).not.to.include.keys('peak');
|
||||
});
|
||||
|
||||
it('does not blacklist generic \'*\' key-values', function() {
|
||||
it('does not blacklist generic \'*\' key-values', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys().natural).not.to.include.keys('natural');
|
||||
});
|
||||
|
||||
it('ignores keys like \'highway\' that are assumed to be lines', function() {
|
||||
it('ignores keys like \'highway\' that are assumed to be lines', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys()).not.to.include.keys('highway');
|
||||
});
|
||||
|
||||
it('ignores suggestion presets', function() {
|
||||
it('ignores suggestion presets', function () {
|
||||
iD.data.presets = testPresets;
|
||||
var presets = iD.Context().presets();
|
||||
expect(presets.areaKeys()).not.to.include.keys('amenity');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#build', function () {
|
||||
it('builds presets from provided', function () {
|
||||
var surfShop = iD.Node({ tags: { amenity: 'shop', 'shop:type': 'surf' } }),
|
||||
graph = iD.Graph([surfShop]),
|
||||
presets = iD.Context().presets(),
|
||||
morePresets = {
|
||||
presets: {
|
||||
'amenity/shop/surf': {
|
||||
tags: { amenity: 'shop', 'shop:type': 'surf' },
|
||||
geometry: ['point', 'area']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('expected matches', function() {
|
||||
expect(presets.match(surfShop, graph)).to.eql(undefined); // no surfshop preset yet...
|
||||
presets.build(morePresets, true);
|
||||
expect(presets.match(surfShop, graph).addTags).to.eql({ amenity: 'shop', 'shop:type': 'surf' });
|
||||
});
|
||||
it('configures presets\' initial visibility', function () {
|
||||
var surfShop = iD.Node({ tags: { amenity: 'shop', 'shop:type': 'surf' } }),
|
||||
firstStreetJetty = iD.Node({ tags: { man_made: 'jetty' } }),
|
||||
entities = [surfShop, firstStreetJetty],
|
||||
graph = iD.Graph(entities),
|
||||
presets = iD.Context().presets(),
|
||||
morePresets = {
|
||||
presets: {
|
||||
'amenity/shop/surf': {
|
||||
tags: { amenity: 'shop', 'shop:type': 'surf' },
|
||||
geometry: ['point', 'area']
|
||||
},
|
||||
'man_made/jetty': {
|
||||
tags: { man_made: 'jetty' },
|
||||
geometry: ['point']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
it('prefers building to multipolygon', function() {
|
||||
presets.build(morePresets, false);
|
||||
entities.forEach(function (entity) {
|
||||
var preset = presets.match(entity, graph);
|
||||
expect(preset.visible()).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('expected matches', function () {
|
||||
|
||||
it('prefers building to multipolygon', function () {
|
||||
iD.data.presets = savedPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
relation = iD.Relation({ tags: { type: 'multipolygon', building: 'yes' }}),
|
||||
relation = iD.Relation({ tags: { type: 'multipolygon', building: 'yes' } }),
|
||||
graph = iD.Graph([relation]);
|
||||
expect(presets.match(relation, graph).id).to.eql('building');
|
||||
});
|
||||
|
||||
it('prefers building to address', function() {
|
||||
it('prefers building to address', function () {
|
||||
iD.data.presets = savedPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
way = iD.Way({ tags: { area: 'yes', building: 'yes', 'addr:housenumber': '1234' }}),
|
||||
way = iD.Way({ tags: { area: 'yes', building: 'yes', 'addr:housenumber': '1234' } }),
|
||||
graph = iD.Graph([way]);
|
||||
expect(presets.match(way, graph).id).to.eql('building');
|
||||
});
|
||||
|
||||
it('prefers pedestrian to area', function() {
|
||||
it('prefers pedestrian to area', function () {
|
||||
iD.data.presets = savedPresets;
|
||||
var presets = iD.Context().presets(),
|
||||
way = iD.Way({ tags: { area: 'yes', highway: 'pedestrian' }}),
|
||||
way = iD.Way({ tags: { area: 'yes', highway: 'pedestrian' } }),
|
||||
graph = iD.Graph([way]);
|
||||
expect(presets.match(way, graph).id).to.eql('highway/pedestrian_area');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fromExternal', function () {
|
||||
var morePresets;
|
||||
before(function () {
|
||||
morePresets = {
|
||||
'categories': {
|
||||
'category-area': {
|
||||
'icon': 'maki-natural',
|
||||
'geometry': 'area',
|
||||
'name': 'MapRules area Features',
|
||||
'members': [
|
||||
'8bc64d6d-1dbb-44a8-a2f9-80d41d067d78',
|
||||
'a9b78746-ca8a-4380-b340-157414f1464d'
|
||||
]
|
||||
},
|
||||
'category-point': {
|
||||
'icon': 'maki-natural',
|
||||
'geometry': 'point',
|
||||
'name': 'MapRules point Features',
|
||||
'members': [
|
||||
'8bc64d6d-1dbb-44a8-a2f9-80d41d067d78',
|
||||
'8f83ed0b-6514-4772-a644-f04aad9d2308'
|
||||
]
|
||||
}
|
||||
},
|
||||
'presets': {
|
||||
'8bc64d6d-1dbb-44a8-a2f9-80d41d067d78': {
|
||||
'geometry': ['area', 'point'],
|
||||
'tags': { 'amenity': 'shop', 'shop:type': 'surf' },
|
||||
'icon': 'maki-natural',
|
||||
'name': 'Surf Shop',
|
||||
'fields': ['358f404a-c7d5-4267-94ed-41f789b16228'],
|
||||
'matchScore': 0.99
|
||||
},
|
||||
'a9b78746-ca8a-4380-b340-157414f1464d': {
|
||||
'geometry': ['area'],
|
||||
'tags': { 'amenity': 'marketplace' },
|
||||
'icon': 'maki-natural',
|
||||
'name': 'Market',
|
||||
'fields': [
|
||||
'name',
|
||||
'source',
|
||||
'2161a712-f67f-4759-92fa-f5d9488ba969',
|
||||
'368ecbdf-bc02-4de2-a82e-d51c250602da',
|
||||
'1887834c-0cdd-4d40-852b-d29b8df94567'
|
||||
],
|
||||
'matchScore': 0.99
|
||||
},
|
||||
'8f83ed0b-6514-4772-a644-f04aad9d2308': {
|
||||
'geometry': ['point'],
|
||||
'tags': {
|
||||
'amenity': 'drinking_water',
|
||||
'man_made': 'water_tap'
|
||||
},
|
||||
'icon': 'maki-natural',
|
||||
'name': 'Water Tap',
|
||||
'fields': ['name'],
|
||||
'matchScore': 0.99
|
||||
}
|
||||
},
|
||||
'fields': {
|
||||
'358f404a-c7d5-4267-94ed-41f789b16228': {
|
||||
'key': 'healthcare',
|
||||
'label': 'Healthcare',
|
||||
'overrideLabel': 'Healthcare',
|
||||
'placeholder': '...',
|
||||
'type': 'text'
|
||||
},
|
||||
'name': {
|
||||
'key': 'name',
|
||||
'type': 'localized',
|
||||
'label': 'Name',
|
||||
'universal': true,
|
||||
'placeholder': 'Common name (if any)'
|
||||
},
|
||||
'source': {
|
||||
'key': 'source',
|
||||
'type': 'semiCombo',
|
||||
'icon': 'source',
|
||||
'universal': true,
|
||||
'label': 'Sources',
|
||||
'snake_case': false,
|
||||
'caseSensitive': true,
|
||||
'options': [
|
||||
'survey',
|
||||
'local knowledge',
|
||||
'gps',
|
||||
'aerial imagery',
|
||||
'streetlevel imagery'
|
||||
]
|
||||
},
|
||||
'2161a712-f67f-4759-92fa-f5d9488ba969': {
|
||||
'key': 'building',
|
||||
'label': 'Building',
|
||||
'overrideLabel': 'Building',
|
||||
'placeholder': '...',
|
||||
'type': 'text'
|
||||
},
|
||||
'368ecbdf-bc02-4de2-a82e-d51c250602da': {
|
||||
'key': 'opening_hours',
|
||||
'label': 'Opening Hours',
|
||||
'overrideLabel': 'Opening Hours',
|
||||
'placeholder': '24/7, sunrise to sunset...',
|
||||
'strings': {
|
||||
'options': {
|
||||
'24/7': '24/7',
|
||||
'sunrise to sunset': 'sunrise to sunset'
|
||||
}
|
||||
},
|
||||
'type': 'combo'
|
||||
},
|
||||
'1887834c-0cdd-4d40-852b-d29b8df94567': {
|
||||
'key': 'height',
|
||||
'label': 'Height',
|
||||
'overrideLabel': 'Height',
|
||||
'placeholder': '...',
|
||||
'minValue': 1, 'type': 'number'
|
||||
},
|
||||
'relation': {
|
||||
'key': 'type',
|
||||
'type': 'combo',
|
||||
'label': 'Type'
|
||||
},
|
||||
'comment': {
|
||||
'key': 'comment',
|
||||
'type': 'textarea',
|
||||
'label': 'Changeset Comment',
|
||||
'placeholder': 'Brief description of your contributions (required)'
|
||||
},
|
||||
'hashtags': {
|
||||
'key': 'hashtags',
|
||||
'type': 'semiCombo',
|
||||
'label': 'Suggested Hashtags',
|
||||
'placeholder': '#example'
|
||||
}
|
||||
},
|
||||
'defaults': {
|
||||
'point': [
|
||||
'point',
|
||||
'8bc64d6d-1dbb-44a8-a2f9-80d41d067d78',
|
||||
'8f83ed0b-6514-4772-a644-f04aad9d2308'
|
||||
],
|
||||
'line': ['line'],
|
||||
'area': [
|
||||
'area',
|
||||
'8bc64d6d-1dbb-44a8-a2f9-80d41d067d78',
|
||||
'a9b78746-ca8a-4380-b340-157414f1464d'
|
||||
],
|
||||
'vertex': ['vertex'],
|
||||
'relation': ['relation']
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
beforeEach(function () {
|
||||
server = sinon.fakeServer.create();
|
||||
});
|
||||
afterEach(function () {
|
||||
server.restore();
|
||||
});
|
||||
it('builds presets w/external sources set to visible', function () {
|
||||
var surfShop = iD.Node({ tags: { amenity: 'shop', 'shop:type': 'surf' } }),
|
||||
graph = iD.Graph([surfShop]),
|
||||
maprules = 'https://fakemaprules.io',
|
||||
presetLocation = '/config/dfcfac13-ba7c-4223-8880-c856180e5c5b/presets/iD/',
|
||||
match = new RegExp(presetLocation),
|
||||
external = maprules + presetLocation;
|
||||
|
||||
// no exernal presets yet
|
||||
expect(iD.Context().presets().match(surfShop, graph).id).to.eql('amenity');
|
||||
// reset graph...
|
||||
graph = iD.Graph([surfShop]);
|
||||
|
||||
// add the validations query param...
|
||||
iD.Context().presets().fromExternal(external, function (externalPresets) {
|
||||
// includes newer presets...
|
||||
expect(externalPresets.match(surfShop, graph).id).to.eql('8bc64d6d-1dbb-44a8-a2f9-80d41d067d78');
|
||||
});
|
||||
|
||||
server.respondWith('GET', match,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(morePresets)]
|
||||
);
|
||||
server.respond();
|
||||
});
|
||||
it('makes only the external presets initially visible', function () {
|
||||
var maprules = 'https://fakemaprules.io',
|
||||
presetLocation = '/config/dfcfac13-ba7c-4223-8880-c856180e5c5b/presets/iD/',
|
||||
match = new RegExp(presetLocation),
|
||||
external = maprules + presetLocation;
|
||||
|
||||
iD.Context().presets().fromExternal(external, function(externalPresets) {
|
||||
var external = externalPresets.collection.reduce(function(presets, preset) {
|
||||
if (!preset.hasOwnProperty('members') && preset.visible()) {
|
||||
presets.push(preset.id);
|
||||
}
|
||||
return presets;
|
||||
}, []);
|
||||
|
||||
var morePresetKeys = Object.keys(morePresets.presets);
|
||||
|
||||
expect(morePresetKeys.length).to.eql(external.length);
|
||||
|
||||
morePresetKeys.forEach(function(presetId) {
|
||||
expect(external.indexOf(presetId)).to.be.at.least(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
server.respondWith('GET', match,
|
||||
[200, { 'Content-Type': 'application/json' }, JSON.stringify(morePresets)]
|
||||
);
|
||||
server.respond();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -149,4 +149,13 @@ describe('iD.presetPreset', function() {
|
||||
expect(preset.unsetTags({a: 'b'}, 'area')).to.eql({a: 'b'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#visible', function() {
|
||||
it('sets/gets visibility of preset', function() {
|
||||
var preset = iD.presetPreset('test', {}, false);
|
||||
expect(preset.visible()).to.be.false;
|
||||
preset.visible(true);
|
||||
expect(preset.visible()).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,569 @@
|
||||
describe('maprules', function() {
|
||||
var _ruleChecks, validationRules;
|
||||
|
||||
before(function() {
|
||||
iD.services.maprules = iD.serviceMapRules;
|
||||
var areaKeys = iD.Context().presets().areaKeys();
|
||||
iD.serviceMapRules.init(areaKeys);
|
||||
_ruleChecks = iD.serviceMapRules.ruleChecks();
|
||||
});
|
||||
|
||||
after(function() {
|
||||
delete iD.services.maprules;
|
||||
});
|
||||
|
||||
describe('#filterRuleChecks', function() {
|
||||
it('returns shortlist of mapcss checks relevant to provided selector', function() {
|
||||
var selector = {
|
||||
geometry: 'closedway',
|
||||
equals: {amenity: 'marketplace'},
|
||||
absence: 'name',
|
||||
error: '\'Marketplace\' preset must be coupled with name'
|
||||
};
|
||||
var filteredChecks = iD.serviceMapRules.filterRuleChecks(selector);
|
||||
var equalsCheck = filteredChecks[0];
|
||||
var absenceCheck = filteredChecks[1];
|
||||
var entityTags = {amenity: 'marketplace'};
|
||||
|
||||
expect(filteredChecks.length).eql(2);
|
||||
expect(equalsCheck(entityTags)).to.be.true;
|
||||
expect(absenceCheck(entityTags)).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#buildTagMap', function() {
|
||||
it('builds a map of tag keys/values found in mapcss selector', function() {
|
||||
[
|
||||
{
|
||||
t: {
|
||||
equals: {
|
||||
man_made: 'tower',
|
||||
'tower:type': 'communication'
|
||||
}
|
||||
},
|
||||
r: {
|
||||
man_made: ['tower'],
|
||||
'tower:type': ['communication']
|
||||
}
|
||||
},
|
||||
{
|
||||
t: {
|
||||
equals: {
|
||||
building: 'yes',
|
||||
amenity: 'school'
|
||||
},
|
||||
positiveRegex: {
|
||||
opening_hours: [
|
||||
'24/7',
|
||||
'sunrise_sundown'
|
||||
]
|
||||
},
|
||||
negativeRegex: {
|
||||
source: [
|
||||
'missing_maps',
|
||||
'american_red_cross'
|
||||
]
|
||||
},
|
||||
greaterThanEqual: { floors: 2 },
|
||||
lessThanEqual: { floors: 4 }
|
||||
|
||||
},
|
||||
r: {
|
||||
building: ['yes'],
|
||||
amenity: ['school'],
|
||||
opening_hours: ['24/7', 'sunrise_sundown'],
|
||||
source: ['missing_maps', 'american_red_cross'],
|
||||
floors: [4, 2]
|
||||
}
|
||||
},
|
||||
{
|
||||
t: {
|
||||
equals: { highway: 'yes' },
|
||||
greaterThan: { lanes: 1 },
|
||||
lessThan: { lanes: 4 }
|
||||
},
|
||||
r: {
|
||||
highway: ['yes'],
|
||||
lanes: [4, 1]
|
||||
}
|
||||
}
|
||||
].forEach(function(test) {
|
||||
expect(iD.serviceMapRules.buildTagMap(test.t)).to.eql(test.r);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#inferGeometry', function() {
|
||||
it('infers geometry using selector keys', function() {
|
||||
|
||||
var amenityDerivedArea = {
|
||||
geometry: 'closedway',
|
||||
presence: 'amenity',
|
||||
positiveRegex: { amenity: ['^school$', '^healthcare$'] },
|
||||
error: 'amenity cannot be healthcare or school!'
|
||||
};
|
||||
|
||||
var areaDerivedArea = {
|
||||
geometry: 'closedway',
|
||||
equals: { area: 'yes' },
|
||||
};
|
||||
|
||||
var badAreaDerivedLine = {
|
||||
geometry: 'closedway',
|
||||
equals: { 'area': 'no' }
|
||||
};
|
||||
|
||||
var roundHouseRailwayDerivedArea = {
|
||||
geometry: 'closedway',
|
||||
equals: { 'railway': 'roundhouse' }
|
||||
};
|
||||
|
||||
var justClosedWayDerivedLine = {
|
||||
geometry: 'closedway'
|
||||
};
|
||||
|
||||
var tagMap, geom;
|
||||
tagMap = iD.serviceMapRules.buildTagMap(amenityDerivedArea);
|
||||
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
||||
expect(geom).to.be.eql('area');
|
||||
|
||||
tagMap = iD.serviceMapRules.buildTagMap(areaDerivedArea);
|
||||
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
||||
expect(geom).to.be.eql('area');
|
||||
|
||||
tagMap = iD.serviceMapRules.buildTagMap(badAreaDerivedLine);
|
||||
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
||||
expect(geom).to.be.eql('line');
|
||||
|
||||
tagMap = iD.serviceMapRules.buildTagMap(roundHouseRailwayDerivedArea);
|
||||
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
||||
expect(geom).to.be.eql('area');
|
||||
|
||||
tagMap = iD.serviceMapRules.buildTagMap(justClosedWayDerivedLine);
|
||||
geom = iD.serviceMapRules.inferGeometry(tagMap);
|
||||
expect(geom).to.be.eql('line');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addRule', function() {
|
||||
it ('builds a rule from provided selector and adds it to _validationRules', function () {
|
||||
var selector = {
|
||||
geometry:'node',
|
||||
equals: {amenity:'marketplace'},
|
||||
absence:'name',
|
||||
warning:'\'Marketplace\' preset must be coupled with name'
|
||||
};
|
||||
expect(iD.serviceMapRules.validationRules()).to.be.empty;
|
||||
iD.serviceMapRules.addRule(selector);
|
||||
expect(iD.serviceMapRules.validationRules().length).to.eql(1);
|
||||
});
|
||||
});
|
||||
describe('#clearRules', function() {
|
||||
it ('clears _validationRules array', function() {
|
||||
expect(iD.serviceMapRules.validationRules().length).to.eql(1);
|
||||
iD.serviceMapRules.clearRules();
|
||||
expect(iD.serviceMapRules.validationRules()).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#validationRules', function() {
|
||||
it('returns _validationRules array', function() {
|
||||
var selector = {
|
||||
geometry: 'closedway',
|
||||
equals: {amenity: 'marketplace'},
|
||||
absence: 'name',
|
||||
error: '\'Marketplace\' preset must be coupled with name'
|
||||
};
|
||||
iD.serviceMapRules.addRule(selector);
|
||||
var rules = iD.serviceMapRules.validationRules();
|
||||
expect(rules).instanceof(Array);
|
||||
expect(rules.length).to.eql(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('_ruleChecks', function () {
|
||||
describe('#equals', function() {
|
||||
it('is true when two tag maps intersect', function() {
|
||||
var a = { amenity: 'school'};
|
||||
var b = { amenity: 'school' };
|
||||
expect(_ruleChecks.equals(a)(b)).to.be.true;
|
||||
});
|
||||
it('is false when two tag maps intersect', function() {
|
||||
var a = { man_made: 'water_tap'};
|
||||
var b = { amenity: 'school'};
|
||||
expect(_ruleChecks.equals(a)(b)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('#notEquals', function() {
|
||||
it('is true when two tag maps do not intersect', function() {
|
||||
var a = { man_made: 'water_tap'};
|
||||
var b = { amenity: 'school' };
|
||||
expect(_ruleChecks.notEquals(a)(b)).to.be.true;
|
||||
});
|
||||
it('is not true when two tag maps intersect', function() {
|
||||
var a = { amenity: 'school' };
|
||||
var b = { amenity: 'school', opening_hours: '9-5' };
|
||||
expect(_ruleChecks.notEquals(a)(b)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('absence', function() {
|
||||
it('is true when tag map keys does not include key in question', function() {
|
||||
var key = 'amenity';
|
||||
var map = { building: 'yes' };
|
||||
expect(_ruleChecks.absence(key)(map)).to.be.true;
|
||||
});
|
||||
it('is false when tag map keys does include key in question', function() {
|
||||
var key = 'amenity';
|
||||
var map = { amenity: 'school' };
|
||||
expect(_ruleChecks.absence(key)(map)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('presence', function() {
|
||||
it('is true when tag map keys includes key in question', function() {
|
||||
var key = 'amenity';
|
||||
var map = { amenity: 'school'};
|
||||
expect(_ruleChecks.presence(key)(map)).to.be.true;
|
||||
});
|
||||
it('is false when tag map keys do not include key in question', function() {
|
||||
var key = 'amenity';
|
||||
var map = { building: 'yes'};
|
||||
expect(_ruleChecks.presence(key)(map)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('greaterThan', function() {
|
||||
it ('is true when a tag value is greater than the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
var tags = { lanes : 6 };
|
||||
expect(_ruleChecks.greaterThan(selectorTags)(tags)).to.be.true;
|
||||
});
|
||||
it ('is false when a tag value is less than or equal to the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
[4, 5].forEach(function(val) {
|
||||
expect(_ruleChecks.greaterThan(selectorTags)({ lanes: val })).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('greaterThanEqual', function() {
|
||||
it ('is true when a tag value is greater than or equal to the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
[5, 6].forEach(function(val) {
|
||||
expect(_ruleChecks.greaterThanEqual(selectorTags)({ lanes: val })).to.be.true;
|
||||
});
|
||||
});
|
||||
it ('is false when a tag value is less than the selector value', function () {
|
||||
var selectorTags = { lanes: 5 };
|
||||
var tags = { lanes: 4 };
|
||||
expect(_ruleChecks.greaterThanEqual(selectorTags)(tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('lessThan', function() {
|
||||
it ('is true when a tag value is less than the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
var tags = { lanes: 4 };
|
||||
expect(_ruleChecks.lessThan(selectorTags)(tags)).to.be.true;
|
||||
});
|
||||
it ('is false when a tag value is greater than or equal to the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
[6, 7].forEach(function(val) {
|
||||
expect(_ruleChecks.lessThan(selectorTags)({ lanes: val })).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('lessThanEqual', function() {
|
||||
it ('is true when a tag value is less than or equal to the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
[4, 5].forEach(function(val) {
|
||||
expect(_ruleChecks.lessThanEqual(selectorTags)({ lanes: val })).to.be.true;
|
||||
});
|
||||
});
|
||||
it ('is false when a tag value is greater than the selector value', function() {
|
||||
var selectorTags = { lanes: 5 };
|
||||
var tags = { lanes: 6 };
|
||||
expect(_ruleChecks.lessThanEqual(selectorTags)(tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('positiveRegex', function() {
|
||||
var positiveRegex = { amenity: ['^hospital$','^clinic$']};
|
||||
it ('is true when tag value matches positiveRegex', function() {
|
||||
var tags = { amenity: 'hospital' };
|
||||
expect(_ruleChecks.positiveRegex(positiveRegex)(tags)).to.be.true;
|
||||
});
|
||||
it ('is false when tag value does not match negative regex', function() {
|
||||
var tags = { amenity: 'school' };
|
||||
expect(_ruleChecks.positiveRegex(positiveRegex)(tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('negativeRegex', function() {
|
||||
var negativeRegex = { bicycle: [ 'use_path', 'designated' ] };
|
||||
it ('is true when tag value does not match negativeRegex', function() {
|
||||
var tags = { bicycle: 'yes' };
|
||||
expect(_ruleChecks.negativeRegex(negativeRegex)(tags)).to.be.true;
|
||||
});
|
||||
it ('is false when tag value matches negativeRegex', function() {
|
||||
var tags = { bicycle: 'designated' };
|
||||
expect(_ruleChecks.negativeRegex(negativeRegex)(tags)).to.be.false;
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('rule', function() {
|
||||
var selectors;
|
||||
before(function() {
|
||||
selectors = [
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {amenity:'marketplace'},
|
||||
absence:'name',
|
||||
error:'\'Marketplace\' preset must be coupled with name'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
notEquals: { building: 'yes', amenity: 'clinic' },
|
||||
error: '\'Clinic\' preset must be coupled with building=yes'
|
||||
},
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
||||
presence: 'height',
|
||||
error:'\'Communication Tower\' preset must not be coupled with height'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThanEqual: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThanEqual: { height: 9 },
|
||||
error: '\'Tower\' preset height must be less than 9'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThan: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than or equal to 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThan: { height: 9 },
|
||||
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
equals: { amenity: 'clinic' },
|
||||
negativeRegex: { emergency: ['yes', 'no'] },
|
||||
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
||||
},
|
||||
{
|
||||
geometry: 'way',
|
||||
equals: { highway: 'residential' },
|
||||
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
||||
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
||||
}
|
||||
];
|
||||
|
||||
iD.serviceMapRules.clearRules();
|
||||
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
||||
validationRules = iD.serviceMapRules.validationRules();
|
||||
});
|
||||
describe('#matches', function() {
|
||||
var selectors, entities;
|
||||
before(function() {
|
||||
selectors = [
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {amenity:'marketplace'},
|
||||
absence:'name',
|
||||
error:'\'Marketplace\' preset must be coupled with name'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
notEquals: { building: 'yes', amenity: 'clinic' },
|
||||
error: '\'Clinic\' preset must be coupled with building=yes'
|
||||
},
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
||||
presence: 'height',
|
||||
error:'\'Communication Tower\' preset must not be coupled with height'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThanEqual: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThanEqual: { height: 9 },
|
||||
error: '\'Tower\' preset height must be less than 9'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThan: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than or equal to 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThan: { height: 9 },
|
||||
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
equals: { amenity: 'clinic' },
|
||||
negativeRegex: { emergency: ['yes', 'no'] },
|
||||
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
||||
},
|
||||
{
|
||||
geometry: 'way',
|
||||
equals: { highway: 'residential' },
|
||||
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
||||
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
||||
}
|
||||
];
|
||||
entities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'marketplace' }}),
|
||||
iD.Way({ tags: { building: 'house', amenity: 'clinic' }, nodes: [ 'a', 'b', 'c', 'a' ]}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', 'tower:type': 'communication', height: 5 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 6 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 9 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 5 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 10 }}),
|
||||
iD.Way({ tags: { amenity: 'clinic', emergency: 'definitely' }, nodes: [ 'd', 'e', 'f', 'd' ]}),
|
||||
iD.Way({ tags: { highway: 'residential', structure: 'bridge' }}),
|
||||
];
|
||||
|
||||
iD.serviceMapRules.clearRules();
|
||||
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
||||
validationRules = iD.serviceMapRules.validationRules();
|
||||
});
|
||||
it('is true when each rule check is \'true\'', function() {
|
||||
validationRules.forEach(function(rule, i) {
|
||||
expect(rule.matches(entities[i])).to.be.true;
|
||||
});
|
||||
});
|
||||
it ('is true when at least one rule check is \'false\'', function() {
|
||||
var selector = {
|
||||
geometry: 'way',
|
||||
equals: { highway: 'residential' },
|
||||
positiveRegex: { structure: ['embarkment', 'bridge'] },
|
||||
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
||||
};
|
||||
var entity = iD.Way({ tags: { highway: 'residential', structure: 'tunnel' }});
|
||||
iD.serviceMapRules.clearRules();
|
||||
iD.serviceMapRules.addRule(selector);
|
||||
var rule = iD.serviceMapRules.validationRules()[0];
|
||||
|
||||
expect(rule.matches(entity)).to.be.false;
|
||||
});
|
||||
});
|
||||
describe('#findWarnings', function() {
|
||||
var selectors, entities, _graph;
|
||||
|
||||
before(function() {
|
||||
selectors = [
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {amenity:'marketplace'},
|
||||
absence:'name',
|
||||
error:'\'Marketplace\' preset must be coupled with name'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
notEquals: { building: 'yes', amenity: 'clinic' },
|
||||
error: '\'Clinic\' preset must be coupled with building=yes'
|
||||
},
|
||||
{
|
||||
geometry:'node',
|
||||
equals: {man_made: 'tower', 'tower:type': 'communication'},
|
||||
presence: 'height',
|
||||
error:'\'Communication Tower\' preset must not be coupled with height'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThanEqual: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThanEqual: { height: 9 },
|
||||
error: '\'Tower\' preset height must be less than 9'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
lessThan: { height: 6 },
|
||||
error: '\'Tower\' preset height must be greater than or equal to 6'
|
||||
},
|
||||
{
|
||||
geometry: 'node',
|
||||
equals: { man_made: 'tower' },
|
||||
greaterThan: { height: 9 },
|
||||
error: '\'Tower\' preset height must be greater less than or equal to 9'
|
||||
},
|
||||
{
|
||||
geometry: 'closedway',
|
||||
equals: { amenity: 'clinic' },
|
||||
negativeRegex: { emergency: ['yes', 'no'] },
|
||||
error: '\'Clinic\' preset\'s emergency tag must be equal to \'yes\' or \'no\''
|
||||
},
|
||||
{
|
||||
geometry: 'way',
|
||||
equals: { highway: 'residential' },
|
||||
positiveRegex: { structure: ['bridge', 'tunnel'] },
|
||||
error: '\'suburban road\' structure tag cannot be \'bridge\' or \'tunnel\''
|
||||
}
|
||||
];
|
||||
entities = [
|
||||
iD.Entity({ type: 'node', tags: { amenity: 'marketplace' }}),
|
||||
iD.Way({ tags: { building: 'house', amenity: 'clinic' }, nodes: [ 'a', 'b', 'c', 'a' ]}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', 'tower:type': 'communication', height: 5 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 6 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 9 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 5 }}),
|
||||
iD.Entity({ type: 'node', tags: { man_made: 'tower', height: 10 }}),
|
||||
iD.Way({ tags: { amenity: 'clinic', emergency: 'definitely' }, nodes: [ 'd', 'e', 'f', 'd' ]}),
|
||||
iD.Way({ tags: { highway: 'residential', structure: 'bridge' }}),
|
||||
];
|
||||
|
||||
var wayNodes = [
|
||||
iD.osmNode({ id: 'a' }),
|
||||
iD.osmNode({ id: 'b' }),
|
||||
iD.osmNode({ id: 'c' }),
|
||||
iD.osmNode({ id: 'd' }),
|
||||
iD.osmNode({ id: 'e' }),
|
||||
iD.osmNode({ id: 'f' }),
|
||||
];
|
||||
_graph = iD.Graph(entities.concat(wayNodes));
|
||||
iD.serviceMapRules.clearRules();
|
||||
selectors.forEach(function(selector) { iD.serviceMapRules.addRule(selector); });
|
||||
validationRules = iD.serviceMapRules.validationRules();
|
||||
});
|
||||
it('finds warnings', function() {
|
||||
validationRules.forEach(function(rule, i) {
|
||||
var warnings = [];
|
||||
var entity = entities[i];
|
||||
var selector = selectors[i];
|
||||
|
||||
rule.findWarnings(entity, _graph, warnings);
|
||||
|
||||
var warning = warnings[0];
|
||||
var type = Object.keys(selector).indexOf('error') ? 'error' : 'warning';
|
||||
|
||||
expect(warnings.length).to.eql(1);
|
||||
expect(warning.entity).to.eql(entity);
|
||||
expect(warning.message).to.eql(selector[type]);
|
||||
expect(type).to.eql(warning.severity);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* globals chai:false */
|
||||
/* eslint no-extend-native:off */
|
||||
|
||||
iD.debug = true;
|
||||
|
||||
// disable things that use the network
|
||||
|
||||
Reference in New Issue
Block a user