Last active
May 20, 2016 05:58
-
-
Save MateoV/ba40e501d03416177e7a to your computer and use it in GitHub Desktop.
Use Tile Reduce and OSM QA Tiles to find gaps in road networks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//identify disconnected major roads | |
var turf = require('turf'); | |
var tilebelt = require('tilebelt'); | |
var flatten = require('geojson-flatten'); | |
module.exports = function(tileLayers, tile, done){ | |
var bbox = tilebelt.tileToBBOX(tile); | |
var minDistance = 50/5280; // 50 ft in miles | |
var disconnects = turf.featurecollection([]); | |
var caps = []; | |
// OSM tag we are looking for | |
var tag = 'highway'; | |
// Types of highways we are looking for disconnected ends for | |
var preserve_type = { "motorway" : true, "primary" : true, "secondary" : true, "tertiary" : true, "trunk": true, "residential": true }; | |
// First pass: finding road ends that don't connect to anything | |
for (var layer in tileLayers.osmdata) { | |
var i, j, k, f, g; | |
for (var i = 0; i < tileLayers.osmdata[layer].features.length; i++) { | |
var flat = flatten(tileLayers.osmdata[layer].features[i]); | |
for (var f = 0; f < flat.length; f++) { | |
var line = flat[f]; | |
if (preserve_type[line.properties[tag]] && (line.geometry.type === 'LineString')) { | |
var endps = [ 0, line.geometry.coordinates.length - 1 ]; | |
endps.forEach(function(endp) { | |
var end = line.geometry.coordinates[endp]; | |
// It's not dangling if it's a loop connecting back to itself | |
for (var g = 0; g < flat.length; g++) { | |
var line2 = flat[g]; | |
for (var j = 0; j < line2.geometry.coordinates.length; j++) { | |
if (end[0] == line2.geometry.coordinates[j][0] && end[1] == line2.geometry.coordinates[j][1]) { | |
if (f != g) { | |
return; | |
} else { | |
if (j != endp) { | |
return; | |
} | |
} | |
} | |
} | |
} | |
// It's not dangling if it's outside the bounding box and was just clipped here | |
if (end[0] < bbox[0] || end[0] > bbox[2] || end[1] < bbox[1] || end[1] > bbox[3]) { | |
return; | |
} | |
var dup = false; | |
// It's also not dangling if it connects to any other road | |
for (var layer2 in tileLayers.osmdata) { | |
for (var j = 0; j < tileLayers.osmdata[layer2].features.length; j++) { | |
var flat2 = flatten(tileLayers.osmdata[layer2].features[j]); | |
flat2.forEach(function(line2) { | |
if (layer === layer2 && i == j) { | |
return; | |
} | |
for (var k = 0; !dup && k < line2.geometry.coordinates.length; k++) { | |
if (end[0] === line2.geometry.coordinates[k][0] && end[1] === line2.geometry.coordinates[k][1]) { | |
dup = true; | |
} | |
} | |
}); | |
} | |
} | |
if (!dup) { | |
caps.push({layer: layer, i: i, point: turf.point(end), line: line}); | |
} | |
}); | |
} | |
} | |
} | |
} | |
// Second pass: find other roads that are near the dangling ends | |
caps.forEach(function(cap) { | |
var best = Number.MAX_VALUE; | |
var bestline = null; | |
for (var layer in tileLayers.osmdata) { | |
var i; | |
for (var i = 0; i < tileLayers.osmdata[layer].features.length; i++) { | |
var flat = flatten(tileLayers.osmdata[layer].features[i]); | |
flat.forEach(function(line) { | |
// Don't try to match an endpoint to the way that it came from | |
if (layer == cap.layer && i == cap.i) { | |
return; | |
} | |
if (line.properties.[tag] && line.geometry.type === 'LineString') { | |
var distance = turf.distance(cap.point, turf.pointOnLine(line, cap.point)); | |
if (distance < best) { | |
var already = false; | |
// Don't try to match an endpoint to a way that the | |
// way that it comes from already connects to | |
cap.line.geometry.coordinates.forEach(function(capp) { | |
line.geometry.coordinates.forEach(function(linep) { | |
if (capp[0] == linep[0] && capp[1] == linep[1]) { | |
already = true; | |
} | |
}); | |
}); | |
if (!already) { | |
best = distance; | |
bestline = line; | |
} | |
} | |
} | |
}); | |
} | |
} | |
if (best < minDistance) { | |
cap.point.properties.unconnected_way = cap.line.properties._osm_way_id; | |
cap.point.properties.unconnected_type = cap.line.properties[tag]; | |
cap.point.properties.unconnected_distance_feet = best * 5280; | |
cap.point.properties.possible_link = bestline.properties._osm_way_id; | |
disconnects.features.push(cap.point); | |
} | |
}); | |
// return points where distance is less than 50 feet | |
done(null, disconnects); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var TileReduce = require('tile-reduce'); | |
var turf = require('turf'); | |
var argv = require('minimist')(process.argv.slice(2)); | |
// Pass area and setup options | |
var area = JSON.parse(argv.area); | |
var opts = { | |
zoom: 15, | |
tileLayers: [ | |
{ | |
name: 'osmdata', | |
mbtiles: 'latest.planet.mbtiles', | |
layers: ['osm'] | |
} | |
], | |
map: __dirname+'/disconnect.js' | |
}; | |
// Initialize TileReduce | |
var tilereduce = TileReduce(area, opts); | |
// Create featurecollection | |
var gaps = turf.featurecollection([]); | |
// Collect results from TileReduce workers | |
tilereduce.on('reduce', function(result){ | |
gaps.features = gaps.features.concat(result.features); | |
}); | |
// Out put results when TileReduce completes | |
tilereduce.on('end', function(error){ | |
console.log(JSON.stringify(gaps)); | |
}); | |
// run TileReduce | |
tilereduce.run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment