|
const fs = require('fs'); |
|
const request = require('request'); |
|
const _ = require('underscore'); |
|
|
|
// Lat, lng coordinates the export is centered around |
|
// const location = [40.729337, -73.983557] |
|
const location = [40.710591, -73.999032] |
|
|
|
// Number of tiles to fetch in x and y direction. |
|
const numTiles = [10, 10]; |
|
// Zoom level to fetch tiles at (high number = more details) |
|
const zoom = 16; |
|
// Get api key from https://mapzen.com/developers |
|
const api_key = 'vector-tiles-LM25tq4' |
|
const svgScale = 0.01 |
|
|
|
// Write stream setup |
|
const stream = fs.createWriteStream('output.svg', {flags: 'w'}); |
|
stream.write(`<svg width="1000" height="1000" xmlns="http://www.w3.org/2000/svg">`) |
|
|
|
// Functions to calculate tile number |
|
function long2tile(lon,zoom) { return (Math.floor((lon+180)/360*Math.pow(2,zoom))); } |
|
function lat2tile(lat,zoom) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom))); } |
|
function tile2long(x,z) { return (x/Math.pow(2,z)*360-180); } |
|
function tile2lat(y,z) { var n=Math.PI-2*Math.PI*y/Math.pow(2,z); return (180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n)))); } |
|
function tile(latlng) { return [lat2tile(location[0], zoom), long2tile(location[1], zoom)] } |
|
|
|
// Calculate tiles to download |
|
var x = tile(location)[1]-numTiles[0]/2; // tile number x |
|
var y = tile(location)[0]-numTiles[1]/2; // tile number y |
|
const tx = x+numTiles[0]; // target tile number x |
|
const ty = y+numTiles[1]; // target tile number y |
|
const bx = x; // beginning tile number x |
|
|
|
// Coordinate conversion |
|
const SphericalMercator = require('SphericalMercator'); |
|
const merc = new SphericalMercator({ |
|
size: 200000 |
|
}); |
|
|
|
// Origin px position |
|
var z = 14; |
|
var o = merc.px([tile2long(x,zoom), tile2lat(y,zoom)], z) |
|
// Transform lat lng to px position using mercator projection |
|
function transform(l){ |
|
var p = merc.px([l[0], l[1]], z) |
|
return [ (p[0]-o[0]) * svgScale , (p[1]-o[1]) * svgScale ] |
|
} |
|
|
|
// Download tile |
|
function download(){ |
|
const url = "https://tile.mapzen.com/mapzen/vector/v1/buildings/" + zoom + "/" + x + "/" + y + ".json?api_key="+api_key |
|
console.log("Downloading tile",url) |
|
request(url, (error, response, body)=> { |
|
if (!error && response.statusCode === 200) { |
|
const response = JSON.parse(body) |
|
|
|
// Render the response to svg |
|
render(response, x, y) |
|
|
|
// Download next tile, or end |
|
if(x < tx){ |
|
x++ |
|
download() |
|
} else if (y<ty){ |
|
x = bx; |
|
y++; |
|
download() |
|
} else { |
|
stream.write("</svg>") |
|
|
|
console.log("Optimized percentage: ", _totalLinesOptimized / _totalLines) |
|
console.log("Saved: ", _totalLines - _totalLinesOptimized, " of", _totalLines,"lines") |
|
|
|
console.log("Traveled: ", _totalTraveled, "optimized:",_totalTraveledOptimized) |
|
} |
|
} else { |
|
console.log("Got an error: ", error, ", status code: ", response.statusCode) |
|
} |
|
}) |
|
} |
|
|
|
var _tileNum = 0; |
|
var _linesCache = {} |
|
var _totalLines = 0; |
|
var _totalLinesOptimized = 0 |
|
var _totalTraveled = 0; |
|
var _totalTraveledOptimized = 0; |
|
// Takes json and renders svg |
|
function render(json, x, y){ |
|
let lines = []; |
|
for(var i=0;i<json.features.length;i++){ |
|
var feature = json.features[i] |
|
|
|
// console.log(feature.properties) |
|
if(feature.properties.kind != 'building') continue; |
|
|
|
if(feature.geometry && (feature.geometry.type == 'LineString' )){ |
|
lines.push(feature.geometry.coordinates) |
|
} |
|
if(feature.geometry && (feature.geometry.type == 'MultiLineString' || feature.geometry.type == 'Polygon')){ |
|
for(var u=0;u<feature.geometry.coordinates.length; u++){ |
|
lines.push(feature.geometry.coordinates[u]) |
|
} |
|
} |
|
} |
|
|
|
console.log(`${_tileNum} (${x}|${y}): ${lines.length} lines`) |
|
|
|
_totalLines += lines.length; |
|
|
|
// Remove duplicate lines |
|
let filterDuplicates = (_lines, otherLines)=>{ |
|
return _.filter(_lines, (line)=>{ |
|
return !_.some(otherLines, (otherLine)=> _.isEqual(line, otherLine)) |
|
}) |
|
} |
|
|
|
lines = _.unique(lines, (line)=>{ |
|
return JSON.stringify(line) |
|
}) |
|
|
|
if(_linesCache[x-1]){ |
|
lines = filterDuplicates(lines, _linesCache[x-1][y]) |
|
} |
|
if(_linesCache[x] && _linesCache[x][y-1]){ |
|
lines = filterDuplicates(lines, _linesCache[x][y-1]) |
|
} |
|
if(_linesCache[x-1] && _linesCache[x-1][y-1]){ |
|
lines = filterDuplicates(lines, _linesCache[x-1][y-1]) |
|
} |
|
|
|
_totalLinesOptimized += lines.length; |
|
|
|
|
|
let distance = (p1, p2) =>{ |
|
let dX = p1[0] - p2[0]; |
|
let dY = p1[1] - p2[1]; |
|
return dX*dX + dY*dY |
|
} |
|
// Distance optimization |
|
let calcDistance = (lines)=>{ |
|
let _distance = 0; |
|
let _last |
|
_.each(lines, (line, idx)=>{ |
|
if(idx > 0){ |
|
_distance += Math.sqrt(distance(line[0], _last)) |
|
} |
|
_last = _.last(line) |
|
}) |
|
return _distance; |
|
} |
|
_totalTraveled += calcDistance(lines) |
|
|
|
// lines = _.sortBy(lines, (line)=>{ |
|
// return -line[0][1] + line[0][0] |
|
// }) |
|
|
|
|
|
let linesNotAdded = lines.slice(); |
|
lines = [] |
|
for(var i=0;i<linesNotAdded.length;i){ |
|
if(lines.length == 0){ |
|
lines.push(linesNotAdded.shift()) |
|
} else { |
|
let p = _.last(_.last(lines)) |
|
let lowestDist; |
|
let lowestIdx = -1; |
|
_.each(linesNotAdded, (line, idx) =>{ |
|
let dist = distance(line[0], p) |
|
if(lowestIdx == -1 || lowestDist > dist){ |
|
lowestIdx = idx; |
|
lowestDist = dist; |
|
} |
|
}) |
|
|
|
|
|
lines.push(linesNotAdded[lowestIdx]) |
|
linesNotAdded.splice(lowestIdx,1) |
|
} |
|
} |
|
|
|
_totalTraveledOptimized += calcDistance(lines) |
|
console.log(`${_tileNum} (${x}|${y}) optimized: ${lines.length} lines`) |
|
|
|
|
|
if(!_linesCache[x]) _linesCache[x] = {} |
|
if(!_linesCache[x][y]) _linesCache[x][y] = lines.slice(); |
|
|
|
// var colors = ['rgba(255,0,0,0.2)', 'rgba(0,255,0,0.2)', 'rgba(0,0,255,0.2)'] |
|
// var color = colors[_tileNum%3] |
|
var color = 'rgba(0,0,0,1)' |
|
|
|
let addPolyline = (line, color='black')=>{ |
|
stream.write('<polyline fill="none" stroke="'+color+'" points="') |
|
stream.write(_.map(line, (c)=> transform(c).join(',')).join(' ')) |
|
stream.write('"/>') |
|
} |
|
|
|
for(var i=0;i<lines.length;i++){ |
|
// var color = 'rgba(0,0,0,'+i/lines.length+')' |
|
addPolyline(lines[i],'black') |
|
// if(i>0){ |
|
// addPolyline([_.last(lines[i-1]), _.first(lines[i])], 'magenta') |
|
// } |
|
} |
|
|
|
_tileNum ++; |
|
} |
|
|
|
// Start the downloading |
|
download() |