mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
add crossing_way validation
Tests are also added and passed. One thing to note: I had to add the tree() function to history so that I can use the tree in the test; hope that's fine.
This commit is contained in:
@@ -638,6 +638,8 @@ en:
|
||||
many_deletions: "You're deleting {n} features: {p} nodes, {l} lines, {a} areas, {r} relations. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org."
|
||||
tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area"
|
||||
deprecated_tags: "Deprecated tags: {tags}"
|
||||
crossing_ways: Corssing ways without connection
|
||||
crossing_ways_tooltip: "Roads are crossing other roads, buildings, railroads, or waterways without connection nodes or a bridge tag."
|
||||
zoom:
|
||||
in: Zoom in
|
||||
out: Zoom out
|
||||
|
||||
66
dist/locales/en.json
vendored
66
dist/locales/en.json
vendored
@@ -774,7 +774,9 @@
|
||||
"untagged_relation_tooltip": "Select a feature type that describes what this relation is.",
|
||||
"many_deletions": "You're deleting {n} features: {p} nodes, {l} lines, {a} areas, {r} relations. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.",
|
||||
"tag_suggests_area": "The tag {tag} suggests line should be area, but it is not an area",
|
||||
"deprecated_tags": "Deprecated tags: {tags}"
|
||||
"deprecated_tags": "Deprecated tags: {tags}",
|
||||
"crossing_ways": "Corssing ways without connection",
|
||||
"crossing_ways_tooltip": "Roads are crossing other roads, buildings, railroads, or waterways without connection nodes or a bridge tag."
|
||||
},
|
||||
"zoom": {
|
||||
"in": "Zoom in",
|
||||
@@ -7590,9 +7592,6 @@
|
||||
"SPW_PICC": {
|
||||
"name": "SPW(allonie) PICC numerical imagery"
|
||||
},
|
||||
"US-TIGER-Roads-2012": {
|
||||
"name": "TIGER Roads 2012"
|
||||
},
|
||||
"US-TIGER-Roads-2014": {
|
||||
"description": "At zoom level 16+, public domain map data from the US Census. At lower zooms, only changes since 2006 minus changes already incorporated into OpenStreetMap",
|
||||
"name": "TIGER Roads 2014"
|
||||
@@ -7601,6 +7600,10 @@
|
||||
"description": "Yellow = Public domain map data from the US Census. Red = Data not found in OpenStreetMap",
|
||||
"name": "TIGER Roads 2017"
|
||||
},
|
||||
"US-TIGER-Roads-2018": {
|
||||
"description": "Yellow = Public domain map data from the US Census. Red = Data not found in OpenStreetMap",
|
||||
"name": "TIGER Roads 2018"
|
||||
},
|
||||
"US_Forest_Service_roads_overlay": {
|
||||
"description": "Highway: Green casing = unclassified. Brown casing = track. Surface: gravel = light brown fill, Asphalt = black, paved = gray, ground =white, concrete = blue, grass = green. Seasonal = white bars",
|
||||
"name": "U.S. Forest Roads Overlay"
|
||||
@@ -7617,6 +7620,12 @@
|
||||
},
|
||||
"name": "UrbIS-Ortho 2017"
|
||||
},
|
||||
"UrbISOrtho2018": {
|
||||
"attribution": {
|
||||
"text": "Realized by means of Brussels UrbIS®© - Distribution & Copyright CIRB"
|
||||
},
|
||||
"name": "UrbIS-Ortho 2018"
|
||||
},
|
||||
"UrbisAdmFR": {
|
||||
"attribution": {
|
||||
"text": "Realized by means of Brussels UrbIS®© - Distribution & Copyright CIRB"
|
||||
@@ -7701,11 +7710,33 @@
|
||||
"description": "Japan GSI Standard Map. Widely covered.",
|
||||
"name": "Japan GSI Standard Map"
|
||||
},
|
||||
"hike_n_bike": {
|
||||
"helsingborg-orto": {
|
||||
"attribution": {
|
||||
"text": "© OpenStreetMap contributors"
|
||||
"text": "© Helsingborg municipality"
|
||||
},
|
||||
"name": "Hike & Bike"
|
||||
"description": "Orthophotos from the municipality of Helsingborg 2016, public domain",
|
||||
"name": "Helsingborg Orthophoto"
|
||||
},
|
||||
"kalmar-orto-2014": {
|
||||
"attribution": {
|
||||
"text": "© Kalmar municipality"
|
||||
},
|
||||
"description": "Orthophotos for the north coast of the municipality of Kalmar 2014",
|
||||
"name": "Kalmar North Orthophoto 2014"
|
||||
},
|
||||
"kalmar-orto-2016": {
|
||||
"attribution": {
|
||||
"text": "© Kalmar municipality"
|
||||
},
|
||||
"description": "Orthophotos for the south coast of the municipality of Kalmar 2016",
|
||||
"name": "Kalmar South Orthophoto 2016"
|
||||
},
|
||||
"kalmar-orto-2018": {
|
||||
"attribution": {
|
||||
"text": "© Kalmar municipality"
|
||||
},
|
||||
"description": "Orthophotos for urban areas of the municipality of Kalmar 2018",
|
||||
"name": "Kalmar Urban Orthophoto 2018"
|
||||
},
|
||||
"kelkkareitit": {
|
||||
"attribution": {
|
||||
@@ -7728,6 +7759,20 @@
|
||||
"description": "Mosaic of Swedish orthophotos from the period 1970–1980. Is under construction.",
|
||||
"name": "Lantmäteriet Historic Orthophoto 1975"
|
||||
},
|
||||
"lantmateriet-topowebb": {
|
||||
"attribution": {
|
||||
"text": "© Lantmäteriet, CC0"
|
||||
},
|
||||
"description": "Topographic map of Sweden 1:50 000",
|
||||
"name": "Lantmäteriet Topographic Map"
|
||||
},
|
||||
"linkoping-orto": {
|
||||
"attribution": {
|
||||
"text": "© Linköping municipality"
|
||||
},
|
||||
"description": "Orthophotos from the municipality of Linköping 2010, open data",
|
||||
"name": "Linköping Orthophoto"
|
||||
},
|
||||
"mapbox_locator_overlay": {
|
||||
"attribution": {
|
||||
"text": "Terms & Feedback"
|
||||
@@ -7792,6 +7837,13 @@
|
||||
},
|
||||
"name": "Stamen Terrain"
|
||||
},
|
||||
"stockholm-orto": {
|
||||
"attribution": {
|
||||
"text": "© Stockholm municipality, CC0"
|
||||
},
|
||||
"description": "Orthophotos from the municipality of Stockholm 2015, CC0 license",
|
||||
"name": "Stockholm Orthophoto"
|
||||
},
|
||||
"tf-cycle": {
|
||||
"attribution": {
|
||||
"text": "Maps © Thunderforest, Data © OpenStreetMap contributors"
|
||||
|
||||
@@ -122,6 +122,11 @@ export function coreHistory(context) {
|
||||
},
|
||||
|
||||
|
||||
tree: function() {
|
||||
return _tree;
|
||||
},
|
||||
|
||||
|
||||
base: function() {
|
||||
return _stack[0].graph;
|
||||
},
|
||||
@@ -285,7 +290,7 @@ export function coreHistory(context) {
|
||||
return _flatten(_map(
|
||||
validations,
|
||||
function(fn) {
|
||||
return fn(context)(changes, _stack[_index].graph);
|
||||
return fn(context)(changes, _stack[_index].graph, _tree);
|
||||
}
|
||||
));
|
||||
},
|
||||
|
||||
112
modules/validations/crossing_ways.js
Normal file
112
modules/validations/crossing_ways.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { geoExtent, geoLineIntersection } from '../geo';
|
||||
import { set as d3_set } from 'd3-collection';
|
||||
import { t } from '../util/locale';
|
||||
import {
|
||||
ValidationIssueType,
|
||||
ValidationIssueSeverity,
|
||||
validationIssue,
|
||||
} from './validation_issue';
|
||||
|
||||
|
||||
export function validationHighwayCrossingOtherWays() {
|
||||
// Check if the edge going from n1 to n2 crosses (without a connection node)
|
||||
// any edge on way. Return the corss point if so.
|
||||
function findEdgeToWayCrossCoords(n1, n2, way, graph, edgePairsVisited) {
|
||||
var crossCoords = [];
|
||||
for (var j = 0; j < way.nodes.length - 1; j++) {
|
||||
var nidA = way.nodes[j],
|
||||
nidB = way.nodes[j + 1];
|
||||
if (nidA === n1.id || nidA === n2.id ||
|
||||
nidB === n1.id || nidB === n2.id) {
|
||||
// n1 or n2 is a connection node; skip
|
||||
continue;
|
||||
}
|
||||
|
||||
var edgePair = edgePairString(n1.id, n2.id, nidA, nidB);
|
||||
if (edgePairsVisited.has(edgePair)) continue;
|
||||
edgePairsVisited.add(edgePair);
|
||||
|
||||
var nA = graph.entity(nidA),
|
||||
nB = graph.entity(nidB),
|
||||
point = geoLineIntersection([n1.loc, n2.loc], [nA.loc, nB.loc]);
|
||||
if (point) crossCoords.push(point);
|
||||
}
|
||||
return crossCoords;
|
||||
}
|
||||
|
||||
// n1 and n2 from one edge, nA and nB from the other edge
|
||||
function edgePairString(n1, n2, nA, nB) {
|
||||
return n1 > nA ? n1 + n2 + nA + nB : nA + nB + n1 + n2;
|
||||
}
|
||||
|
||||
function findCrossingsByWay(entity, graph, tree, edgePairsVisited) {
|
||||
var edgeCrossInfos = [];
|
||||
if (entity.type !== 'way' || !entity.tags.highway) return edgeCrossInfos;
|
||||
if (entity.geometry(graph) !== 'line') return edgeCrossInfos;
|
||||
|
||||
for (var i = 0; i < entity.nodes.length - 1; i++) {
|
||||
var nid1 = entity.nodes[i],
|
||||
nid2 = entity.nodes[i + 1],
|
||||
n1 = graph.entity(nid1),
|
||||
n2 = graph.entity(nid2),
|
||||
extent = geoExtent([
|
||||
[
|
||||
Math.min(n1.loc[0], n2.loc[0]),
|
||||
Math.min(n1.loc[1], n2.loc[1])
|
||||
],
|
||||
[
|
||||
Math.max(n1.loc[0], n2.loc[0]),
|
||||
Math.max(n1.loc[1], n2.loc[1])
|
||||
]
|
||||
]),
|
||||
intersected = tree.intersects(extent, graph);
|
||||
for (var j = 0; j < intersected.length; j++) {
|
||||
if (intersected[j].type !== 'way') continue;
|
||||
|
||||
// only check crossing highway, waterway, building, and railway
|
||||
var way = intersected[j];
|
||||
if (!(way.tags.highway || way.tags.building ||
|
||||
way.tags.railway || way.tags.waterway)) {
|
||||
continue;
|
||||
}
|
||||
if (way.tags.waterway && entity.tags.bridge === 'yes') {
|
||||
continue;
|
||||
}
|
||||
var crossCoords = findEdgeToWayCrossCoords(n1, n2, way, graph, edgePairsVisited);
|
||||
for (var k = 0; k < crossCoords.length; k++) {
|
||||
edgeCrossInfos.push({
|
||||
ways: [entity, way],
|
||||
cross_point: crossCoords[k],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return edgeCrossInfos;
|
||||
}
|
||||
|
||||
|
||||
var validation = function(changes, graph, tree) {
|
||||
// create one issue per crossing point
|
||||
var edited = changes.created.concat(changes.modified),
|
||||
edgePairsVisited = d3_set(),
|
||||
issues = [];
|
||||
for (var i = 0; i < edited.length; i++) {
|
||||
var crosses = findCrossingsByWay(edited[i], graph, tree, edgePairsVisited);
|
||||
for (var j = 0; j < crosses.length; j++) {
|
||||
issues.push(new validationIssue({
|
||||
type: ValidationIssueType.crossing_ways,
|
||||
severity: ValidationIssueSeverity.error,
|
||||
message: t('validations.crossing_ways'),
|
||||
tooltip: t('validations.crossing_ways_tooltip'),
|
||||
entities: crosses[j].ways,
|
||||
coordinates: crosses[j].cross_point,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return issues;
|
||||
};
|
||||
|
||||
|
||||
return validation;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export { validationDeprecatedTag } from './deprecated_tag';
|
||||
export { validationDisconnectedHighway } from './disconnected_highway';
|
||||
export { validationHighwayCrossingOtherWays } from './crossing_ways';
|
||||
export { ValidationIssueType, ValidationIssueSeverity } from './validation_issue';
|
||||
export { validationManyDeletions } from './many_deletions';
|
||||
export { validationMapCSSChecks } from './mapcss_checks';
|
||||
|
||||
@@ -9,6 +9,7 @@ var ValidationIssueType = Object.freeze({
|
||||
old_multipolygon: 'old_multipolygon',
|
||||
tag_suggests_area: 'tag_suggests_area',
|
||||
map_rule_issue: 'map_rule_issue',
|
||||
crossing_ways: 'crossing_ways'
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +42,6 @@ export function validationIssue(attrs) {
|
||||
this.message = attrs.message;
|
||||
this.tooltip = attrs.tooltip;
|
||||
this.entities = attrs.entities; // expect an array of entities
|
||||
this.coordinates = attrs.coordinates; // expect an array of [lon, lat]
|
||||
this.coordinates = attrs.coordinates; // expect a [lon, lat] array
|
||||
this.fixes = attrs.fixes; // expect an array of functions for possible fixes
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@
|
||||
<script src='spec/util/util.js'></script>
|
||||
|
||||
<script src='spec/validations/missing_tag.js'></script>
|
||||
<script src='spec/validations/crossing_ways.js'></script>
|
||||
<script src='spec/operations/detach_node.js'></script>
|
||||
<script>
|
||||
window.mocha.run();
|
||||
|
||||
106
test/spec/validations/crossing_ways.js
Normal file
106
test/spec/validations/crossing_ways.js
Normal file
@@ -0,0 +1,106 @@
|
||||
describe('iD.validations.crossing_ways', function () {
|
||||
var context;
|
||||
|
||||
beforeEach(function() {
|
||||
context = iD.Context();
|
||||
});
|
||||
|
||||
function createWaysWithOneCrossingPoint() {
|
||||
var n1 = iD.Node({id: 'n-1', loc: [1,1]});
|
||||
var n2 = iD.Node({id: 'n-2', loc: [2,2]});
|
||||
var w1 = iD.Way({id: 'w-1', nodes: ['n-1', 'n-2'], tags: { highway: 'residential' }});
|
||||
|
||||
context.perform(
|
||||
iD.actionAddEntity(n1),
|
||||
iD.actionAddEntity(n2),
|
||||
iD.actionAddEntity(w1)
|
||||
);
|
||||
|
||||
var n3 = iD.Node({id: 'n-3', loc: [1,2]});
|
||||
var n4 = iD.Node({id: 'n-4', loc: [2,1]});
|
||||
var w2 = iD.Way({id: 'w-2', nodes: ['n-3', 'n-4'], tags: { highway: 'residential' }});
|
||||
|
||||
context.perform(
|
||||
iD.actionAddEntity(n3),
|
||||
iD.actionAddEntity(n4),
|
||||
iD.actionAddEntity(w2)
|
||||
);
|
||||
}
|
||||
|
||||
function createWaysWithTwoCrossingPoint() {
|
||||
var n1 = iD.Node({id: 'n-1', loc: [1,1]});
|
||||
var n2 = iD.Node({id: 'n-2', loc: [3,3]});
|
||||
var w1 = iD.Way({id: 'w-1', nodes: ['n-1', 'n-2'], tags: { highway: 'residential' }});
|
||||
|
||||
context.perform(
|
||||
iD.actionAddEntity(n1),
|
||||
iD.actionAddEntity(n2),
|
||||
iD.actionAddEntity(w1)
|
||||
);
|
||||
|
||||
var n3 = iD.Node({id: 'n-3', loc: [1,2]});
|
||||
var n4 = iD.Node({id: 'n-4', loc: [2,1]});
|
||||
var n5 = iD.Node({id: 'n-5', loc: [3,2]});
|
||||
var n6 = iD.Node({id: 'n-6', loc: [2,3]});
|
||||
var w2 = iD.Way({id: 'w-2', nodes: ['n-3', 'n-4', 'n-5', 'n-6'], tags: { highway: 'residential' }});
|
||||
|
||||
context.perform(
|
||||
iD.actionAddEntity(n3),
|
||||
iD.actionAddEntity(n4),
|
||||
iD.actionAddEntity(n5),
|
||||
iD.actionAddEntity(n6),
|
||||
iD.actionAddEntity(w2)
|
||||
);
|
||||
}
|
||||
|
||||
function validate() {
|
||||
var validator = iD.validationHighwayCrossingOtherWays();
|
||||
var changes = context.history().changes();
|
||||
return validator(changes, context.graph(), context.history().tree());
|
||||
}
|
||||
|
||||
it('has no errors on init', function() {
|
||||
var issues = validate();
|
||||
expect(issues).to.have.lengthOf(0);
|
||||
});
|
||||
|
||||
it('one cross point between two ways', function() {
|
||||
createWaysWithOneCrossingPoint();
|
||||
var issues = validate();
|
||||
expect(issues).to.have.lengthOf(1);
|
||||
var issue = issues[0];
|
||||
expect(issue.type).to.eql(iD.ValidationIssueType.crossing_ways);
|
||||
expect(issue.entities).to.have.lengthOf(2);
|
||||
expect(issue.entities[0].id).to.eql('w-1');
|
||||
expect(issue.entities[1].id).to.eql('w-2');
|
||||
|
||||
expect(issue.coordinates).to.have.lengthOf(2);
|
||||
expect(issue.coordinates[0]).to.eql(1.5);
|
||||
expect(issue.coordinates[1]).to.eql(1.5);
|
||||
});
|
||||
|
||||
it('two cross points between two ways', function() {
|
||||
createWaysWithTwoCrossingPoint();
|
||||
var issues = validate();
|
||||
expect(issues).to.have.lengthOf(2);
|
||||
var issue = issues[0];
|
||||
expect(issue.type).to.eql(iD.ValidationIssueType.crossing_ways);
|
||||
expect(issue.entities).to.have.lengthOf(2);
|
||||
expect(issue.entities[0].id).to.eql('w-1');
|
||||
expect(issue.entities[1].id).to.eql('w-2');
|
||||
|
||||
expect(issue.coordinates).to.have.lengthOf(2);
|
||||
expect(issue.coordinates[0]).to.eql(1.5);
|
||||
expect(issue.coordinates[1]).to.eql(1.5);
|
||||
|
||||
issue = issues[1];
|
||||
expect(issue.type).to.eql(iD.ValidationIssueType.crossing_ways);
|
||||
expect(issue.entities).to.have.lengthOf(2);
|
||||
expect(issue.entities[0].id).to.eql('w-1');
|
||||
expect(issue.entities[1].id).to.eql('w-2');
|
||||
|
||||
expect(issue.coordinates).to.have.lengthOf(2);
|
||||
expect(issue.coordinates[0]).to.eql(2.5);
|
||||
expect(issue.coordinates[1]).to.eql(2.5);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user