diff --git a/data/core.yaml b/data/core.yaml
index d3478c6b7..311849e44 100644
--- a/data/core.yaml
+++ b/data/core.yaml
@@ -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
diff --git a/dist/locales/en.json b/dist/locales/en.json
index 4b6362c75..69a228e50 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -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"
diff --git a/modules/core/history.js b/modules/core/history.js
index 239034733..9df59db4b 100644
--- a/modules/core/history.js
+++ b/modules/core/history.js
@@ -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);
}
));
},
diff --git a/modules/validations/crossing_ways.js b/modules/validations/crossing_ways.js
new file mode 100644
index 000000000..c991b58bb
--- /dev/null
+++ b/modules/validations/crossing_ways.js
@@ -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;
+}
diff --git a/modules/validations/index.js b/modules/validations/index.js
index ead99f117..51b3300c3 100644
--- a/modules/validations/index.js
+++ b/modules/validations/index.js
@@ -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';
diff --git a/modules/validations/validation_issue.js b/modules/validations/validation_issue.js
index a3aad341b..dd265309c 100644
--- a/modules/validations/validation_issue.js
+++ b/modules/validations/validation_issue.js
@@ -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
}
diff --git a/test/index.html b/test/index.html
index 30f39cf6c..21a6ea7c5 100644
--- a/test/index.html
+++ b/test/index.html
@@ -144,6 +144,7 @@
+