blob: 1a5a5d5518591f36545f23ec5a47dd3d590b9e56 [file] [log] [blame]
evansirokyd401c892016-06-16 00:05:14 -07001var fs = require('fs'),
2 http = require('http')
3
4var async = require('async'),
evansiroky63d35e12016-06-16 10:08:15 -07005 jsts = require('jsts'),
evansirokyd401c892016-06-16 00:05:14 -07006 overpass = require('query-overpass'),
evansiroky4be1c7a2016-06-16 18:23:34 -07007 polygon = require('turf-polygon'),
evansirokyd401c892016-06-16 00:05:14 -07008 shp = require('shpjs')
9
10
evansiroky63d35e12016-06-16 10:08:15 -070011var osmBoundarySources = require('./osmBoundarySources.json'),
evansiroky50216c62016-06-16 17:41:47 -070012 zoneCfg = require('./timezones.json'),
evansiroky63d35e12016-06-16 10:08:15 -070013 geoJsonReader = new jsts.io.GeoJSONReader(),
evansiroky4be1c7a2016-06-16 18:23:34 -070014 geoJsonWriter = new jsts.io.GeoJSONWriter(),
evansiroky35f64342016-06-16 22:17:04 -070015 efeleGeoms, efeleLookup = {}
evansiroky63d35e12016-06-16 10:08:15 -070016
17
evansiroky4be1c7a2016-06-16 18:23:34 -070018var safeMkdir = function(dirname, callback) {
19 fs.mkdir(dirname, function(err) {
20 if(err && err.code === 'EEXIST') {
21 callback()
22 } else {
23 callback(err)
24 }
25 })
26}
27
evansirokyd401c892016-06-16 00:05:14 -070028var toArrayBuffer = function(buffer) {
29 var ab = new ArrayBuffer(buffer.length)
30 var view = new Uint8Array(ab)
31 for (var i = 0; i < buffer.length; ++i) {
32 view[i] = buffer[i]
33 }
34 return view
35}
36
37var extractToGeoJson = function(callback) {
38 shp(toArrayBuffer(fs.readFileSync('./downloads/tz_world_mp.zip')))
evansiroky35f64342016-06-16 22:17:04 -070039 .then(function(geojson) { console.log('extract success'); return callback(null, geojson) })
evansirokyd401c892016-06-16 00:05:14 -070040 .catch(function(e){ console.log('extract err', e); callback(e) })
41}
42
evansiroky63d35e12016-06-16 10:08:15 -070043var union = function(a, b) {
44 var _a = geoJsonReader.read(JSON.stringify(a)),
45 _b = geoJsonReader.read(JSON.stringify(b))
46
47 var result = _a.union(_b)
48
evansiroky35f64342016-06-16 22:17:04 -070049 return geoJsonWriter.write(result)
evansiroky63d35e12016-06-16 10:08:15 -070050
51}
52
evansiroky4be1c7a2016-06-16 18:23:34 -070053// copied and modified from turf-intersect
54var intersection = function(a, b) {
55 var _a = geoJsonReader.read(JSON.stringify(a)),
56 _b = geoJsonReader.read(JSON.stringify(b))
57
58 var result = _a.intersection(_b)
59
evansiroky35f64342016-06-16 22:17:04 -070060 return geoJsonWriter.write(result)
evansiroky4be1c7a2016-06-16 18:23:34 -070061}
62
63var diff = function(a, b) {
64 var _a = geoJsonReader.read(JSON.stringify(a)),
65 _b = geoJsonReader.read(JSON.stringify(b))
66
67 var result = _a.difference(_b)
68
evansiroky35f64342016-06-16 22:17:04 -070069 return geoJsonWriter.write(result)
evansiroky4be1c7a2016-06-16 18:23:34 -070070}
71
evansiroky50216c62016-06-16 17:41:47 -070072var fetchIfNeeded = function(file, superCallback, fetchFn) {
73 fs.stat(file, function(err) {
74 if(!err) { return superCallback() }
75 fetchFn()
76 })
77}
78
evansiroky63d35e12016-06-16 10:08:15 -070079var downloadOsmBoundary = function(boundaryId, boundaryCallback) {
80 var cfg = osmBoundarySources[boundaryId],
81 query = '[out:json][timeout:60];',
82 boundaryFilename = './downloads/' + cfg.type,
83 debug = 'getting data for '
84
85 if(cfg.type === 'ISO3166-1') {
86 query += '(relation["boundary"="administrative"]' +
87 '["admin_level"="2"]' +
88 '["ISO3166-1"="' + cfg.code + '"]);' +
89 'out body;>;out meta qt;'
90 boundaryFilename += '_' + cfg.code
91 debug += 'country: ' + cfg.code
evansiroky5d008132016-06-17 08:37:51 -070092 } else if(cfg.type === 'city') {
93 query += '(relation["boundary"="administrative"]' +
94 '["admin_level"="8"]' +
95 '["name"="' + cfg.name + '"]);' +
96 'out body;>;out meta qt;'
97 boundaryFilename += '_' + cfg.name
98 debug += 'city: ' + cfg.name
evansiroky63d35e12016-06-16 10:08:15 -070099 }
100
evansiroky4be1c7a2016-06-16 18:23:34 -0700101 boundaryFilename += '.json'
102
evansiroky63d35e12016-06-16 10:08:15 -0700103 console.log(debug)
104
105 async.auto({
evansiroky5d008132016-06-17 08:37:51 -0700106 downloadFromOverpass: function(cb) {
evansiroky50216c62016-06-16 17:41:47 -0700107 console.log('downloading from overpass')
evansiroky4be1c7a2016-06-16 18:23:34 -0700108 fetchIfNeeded(boundaryFilename, boundaryCallback, function() {
evansiroky50216c62016-06-16 17:41:47 -0700109 overpass(query, cb, { flatProperties: true })
evansiroky63d35e12016-06-16 10:08:15 -0700110 })
111 },
evansiroky63d35e12016-06-16 10:08:15 -0700112 validateOverpassResult: ['downloadFromOverpass', function(results, cb) {
113 var data = results.downloadFromOverpass
114 if(!data.features || data.features.length == 0) {
115 err = new Error('Invalid geojson for boundary: ' + boundaryId)
116 return cb(err)
117 }
118 cb()
119 }],
120 saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) {
121 var data = results.downloadFromOverpass,
122 combined
123
124 // union all multi-polygons / polygons into one
125 for (var i = data.features.length - 1; i >= 0; i--) {
126 var curGeom = data.features[i].geometry
127 if(curGeom.type === 'Polygon' || curGeom.type === 'MultiPolygon') {
128 console.log('combining border')
129 if(!combined) {
130 combined = curGeom
131 } else {
132 combined = union(curGeom, combined)
133 }
134 }
135 }
136 fs.writeFile(boundaryFilename, JSON.stringify(combined, null, 2), cb)
137 }]
138 }, boundaryCallback)
139}
evansirokyd401c892016-06-16 00:05:14 -0700140
evansiroky4be1c7a2016-06-16 18:23:34 -0700141var getDataSource = function(source) {
142 if(source.source === 'efele') {
evansiroky35f64342016-06-16 22:17:04 -0700143 return efeleGeoms[efeleLookup[source.id]].geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700144 } else if(source.source === 'overpass') {
145 return require('./downloads/' + source.id + '.json')
evansiroky35f64342016-06-16 22:17:04 -0700146 } else if(source.source === 'manual-polygon') {
147 return polygon(source.data).geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700148 } else {
149 var err = new Error('unknown source: ' + source.source)
150 throw err
151 }
152}
153
154var makeTimezoneBoundary = function(tzid, callback) {
evansiroky35f64342016-06-16 22:17:04 -0700155 console.log('makeTimezoneBoundary for', tzid)
156
evansiroky4be1c7a2016-06-16 18:23:34 -0700157 var ops = zoneCfg[tzid],
158 geom
159
160 async.eachSeries(ops, function(task, cb) {
161 var taskData = getDataSource(task)
evansiroky35f64342016-06-16 22:17:04 -0700162 //console.log(task.op)
evansiroky4be1c7a2016-06-16 18:23:34 -0700163 if(task.op === 'init') {
164 geom = taskData
165 } else if(task.op === 'intersect') {
166 geom = intersection(geom, taskData)
167 } else if(task.op === 'difference') {
168 geom = diff(geom, taskData)
169 }
evansiroky35f64342016-06-16 22:17:04 -0700170 cb()
evansiroky4be1c7a2016-06-16 18:23:34 -0700171 }, function(err) {
172 if(err) { return callback(err) }
evansiroky35f64342016-06-16 22:17:04 -0700173 fs.writeFile('./dist/' + tzid.replace(/\//g, '__') + '.json', JSON.stringify(geom), callback)
evansiroky4be1c7a2016-06-16 18:23:34 -0700174 })
175}
176
evansirokyd401c892016-06-16 00:05:14 -0700177async.auto({
178 makeDownloadsDir: function(cb) {
179 console.log('creating downloads dir')
evansiroky4be1c7a2016-06-16 18:23:34 -0700180 safeMkdir('./downloads', cb)
181 },
182 makeDistDir: function(cb) {
183 console.log('createing dist dir')
184 safeMkdir('./dist', cb)
evansirokyd401c892016-06-16 00:05:14 -0700185 },
186 getEfeleShapefile: ['makeDownloadsDir', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700187 console.log('download efele.net shapefile')
188 var efeleFilename = './downloads/tz_world_mp.zip'
189 fetchIfNeeded(efeleFilename, cb, function() {
190 var file = fs.createWriteStream(efeleFilename)
191 http.get('http://efele.net/maps/tz/world/tz_world_mp.zip', function(response) {
192 response.pipe(file)
193 file
194 .on('finish', function() {
195 file.close(cb)
196 })
197 .on('error', cb)
198 })
evansirokyd401c892016-06-16 00:05:14 -0700199 })
200 }],
evansirokyd401c892016-06-16 00:05:14 -0700201 getOsmBoundaries: ['makeDownloadsDir', function(results, cb) {
202 console.log('downloading osm boundaries')
evansiroky63d35e12016-06-16 10:08:15 -0700203 async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb)
204 }],
205 extractEfeleNetShapefile: ['getOsmBoundaries', function(results, cb) {
evansiroky63d35e12016-06-16 10:08:15 -0700206 console.log('extracting efele.net shapefile')
207 extractToGeoJson(cb)
evansiroky50216c62016-06-16 17:41:47 -0700208 }],
209 dictifyEfeleNetData: ['extractEfeleNetShapefile', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700210 console.log('dictify efele.net')
evansiroky4be1c7a2016-06-16 18:23:34 -0700211 efeleGeoms = results.extractEfeleNetShapefile.features
212 for (var i = efeleGeoms.length - 1; i >= 0; i--) {
213 var curTz = efeleGeoms[i]
214 efeleLookup[curTz.properties.TZID] = i
evansiroky50216c62016-06-16 17:41:47 -0700215 }
evansiroky4be1c7a2016-06-16 18:23:34 -0700216 cb()
evansiroky50216c62016-06-16 17:41:47 -0700217 }],
evansiroky4be1c7a2016-06-16 18:23:34 -0700218 createZones: ['makeDistDir', 'dictifyEfeleNetData', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700219 console.log('createZones')
evansiroky50216c62016-06-16 17:41:47 -0700220 async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb)
221 }],
222 mergeZones: ['createZones', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700223 cb()
evansirokyd401c892016-06-16 00:05:14 -0700224 }]
evansiroky50216c62016-06-16 17:41:47 -0700225}, function(err, results) {
evansirokyd401c892016-06-16 00:05:14 -0700226 console.log('done')
227 if(err) {
228 console.log('error!', err)
229 return
230 }
evansirokyd401c892016-06-16 00:05:14 -0700231})