blob: 47d8ab16390854260419c5e4e2d39b4d9594f00a [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
evansiroky6f9d8f72016-06-21 16:27:54 -070043debugGeo = function(op, a, b) {
44 try {
45 switch(op) {
46 case 'union':
47 return union(a, b)
48 break
49 case 'intersection':
50 return intersection(a, b)
51 break
52 case 'diff':
53 return diff(a, b)
54 break
55 default:
56 var err = new Error('invalid op: ' + op)
57 throw err
58 }
59 } catch(e) {
60 console.log('op err')
61 fs.writeFileSync('debug_' + op + '_a.json', JSON.stringify(a))
62 fs.writeFileSync('debug_' + op + '_b.json', JSON.stringify(b))
63 throw e
64 }
65}
66
evansiroky63d35e12016-06-16 10:08:15 -070067var union = function(a, b) {
68 var _a = geoJsonReader.read(JSON.stringify(a)),
69 _b = geoJsonReader.read(JSON.stringify(b))
70
71 var result = _a.union(_b)
72
evansiroky35f64342016-06-16 22:17:04 -070073 return geoJsonWriter.write(result)
evansiroky63d35e12016-06-16 10:08:15 -070074
75}
76
evansiroky4be1c7a2016-06-16 18:23:34 -070077// copied and modified from turf-intersect
78var intersection = function(a, b) {
79 var _a = geoJsonReader.read(JSON.stringify(a)),
80 _b = geoJsonReader.read(JSON.stringify(b))
81
82 var result = _a.intersection(_b)
83
evansiroky35f64342016-06-16 22:17:04 -070084 return geoJsonWriter.write(result)
evansiroky4be1c7a2016-06-16 18:23:34 -070085}
86
87var diff = function(a, b) {
88 var _a = geoJsonReader.read(JSON.stringify(a)),
89 _b = geoJsonReader.read(JSON.stringify(b))
90
91 var result = _a.difference(_b)
92
evansiroky35f64342016-06-16 22:17:04 -070093 return geoJsonWriter.write(result)
evansiroky4be1c7a2016-06-16 18:23:34 -070094}
95
evansiroky50216c62016-06-16 17:41:47 -070096var fetchIfNeeded = function(file, superCallback, fetchFn) {
97 fs.stat(file, function(err) {
98 if(!err) { return superCallback() }
99 fetchFn()
100 })
101}
102
evansiroky63d35e12016-06-16 10:08:15 -0700103var downloadOsmBoundary = function(boundaryId, boundaryCallback) {
104 var cfg = osmBoundarySources[boundaryId],
105 query = '[out:json][timeout:60];',
106 boundaryFilename = './downloads/' + cfg.type,
107 debug = 'getting data for '
108
109 if(cfg.type === 'ISO3166-1') {
110 query += '(relation["boundary"="administrative"]' +
111 '["admin_level"="2"]' +
112 '["ISO3166-1"="' + cfg.code + '"]);' +
113 'out body;>;out meta qt;'
114 boundaryFilename += '_' + cfg.code
115 debug += 'country: ' + cfg.code
evansiroky6f9d8f72016-06-21 16:27:54 -0700116 } else if(cfg.type === 'ISO3166-2') {
117 query += '(relation["boundary"="administrative"]' +
118 '["admin_level"="4"]' +
119 '["ISO3166-2"="' + cfg.code + '"]);' +
120 'out body;>;out meta qt;'
121 boundaryFilename += '_' + cfg.code
122 debug += 'state/province: ' + cfg.code
evansiroky5d008132016-06-17 08:37:51 -0700123 } else if(cfg.type === 'city') {
124 query += '(relation["boundary"="administrative"]' +
125 '["admin_level"="8"]' +
126 '["name"="' + cfg.name + '"]);' +
127 'out body;>;out meta qt;'
128 boundaryFilename += '_' + cfg.name
129 debug += 'city: ' + cfg.name
evansiroky63d35e12016-06-16 10:08:15 -0700130 }
131
evansiroky4be1c7a2016-06-16 18:23:34 -0700132 boundaryFilename += '.json'
133
evansiroky63d35e12016-06-16 10:08:15 -0700134 console.log(debug)
135
136 async.auto({
evansiroky5d008132016-06-17 08:37:51 -0700137 downloadFromOverpass: function(cb) {
evansiroky50216c62016-06-16 17:41:47 -0700138 console.log('downloading from overpass')
evansiroky4be1c7a2016-06-16 18:23:34 -0700139 fetchIfNeeded(boundaryFilename, boundaryCallback, function() {
evansiroky50216c62016-06-16 17:41:47 -0700140 overpass(query, cb, { flatProperties: true })
evansiroky63d35e12016-06-16 10:08:15 -0700141 })
142 },
evansiroky63d35e12016-06-16 10:08:15 -0700143 validateOverpassResult: ['downloadFromOverpass', function(results, cb) {
144 var data = results.downloadFromOverpass
145 if(!data.features || data.features.length == 0) {
146 err = new Error('Invalid geojson for boundary: ' + boundaryId)
147 return cb(err)
148 }
149 cb()
150 }],
151 saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) {
152 var data = results.downloadFromOverpass,
153 combined
154
155 // union all multi-polygons / polygons into one
156 for (var i = data.features.length - 1; i >= 0; i--) {
157 var curGeom = data.features[i].geometry
158 if(curGeom.type === 'Polygon' || curGeom.type === 'MultiPolygon') {
159 console.log('combining border')
160 if(!combined) {
161 combined = curGeom
162 } else {
163 combined = union(curGeom, combined)
164 }
165 }
166 }
167 fs.writeFile(boundaryFilename, JSON.stringify(combined, null, 2), cb)
168 }]
169 }, boundaryCallback)
170}
evansirokyd401c892016-06-16 00:05:14 -0700171
evansiroky4be1c7a2016-06-16 18:23:34 -0700172var getDataSource = function(source) {
173 if(source.source === 'efele') {
evansiroky35f64342016-06-16 22:17:04 -0700174 return efeleGeoms[efeleLookup[source.id]].geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700175 } else if(source.source === 'overpass') {
176 return require('./downloads/' + source.id + '.json')
evansiroky35f64342016-06-16 22:17:04 -0700177 } else if(source.source === 'manual-polygon') {
178 return polygon(source.data).geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700179 } else {
180 var err = new Error('unknown source: ' + source.source)
181 throw err
182 }
183}
184
185var makeTimezoneBoundary = function(tzid, callback) {
evansiroky35f64342016-06-16 22:17:04 -0700186 console.log('makeTimezoneBoundary for', tzid)
187
evansiroky4be1c7a2016-06-16 18:23:34 -0700188 var ops = zoneCfg[tzid],
189 geom
190
191 async.eachSeries(ops, function(task, cb) {
192 var taskData = getDataSource(task)
evansiroky6f9d8f72016-06-21 16:27:54 -0700193 console.log('-', task.op, task.id)
evansiroky4be1c7a2016-06-16 18:23:34 -0700194 if(task.op === 'init') {
195 geom = taskData
196 } else if(task.op === 'intersect') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700197 geom = debugGeo('intersection', geom, taskData)
evansiroky4be1c7a2016-06-16 18:23:34 -0700198 } else if(task.op === 'difference') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700199 geom = debugGeo('diff', geom, taskData)
evansiroky6e45be62016-06-17 08:46:28 -0700200 } else if(task.op === 'union') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700201 geom = debugGeo('union', geom, taskData)
evansiroky4be1c7a2016-06-16 18:23:34 -0700202 }
evansiroky35f64342016-06-16 22:17:04 -0700203 cb()
evansiroky4be1c7a2016-06-16 18:23:34 -0700204 }, function(err) {
205 if(err) { return callback(err) }
evansiroky35f64342016-06-16 22:17:04 -0700206 fs.writeFile('./dist/' + tzid.replace(/\//g, '__') + '.json', JSON.stringify(geom), callback)
evansiroky4be1c7a2016-06-16 18:23:34 -0700207 })
208}
209
evansirokyd401c892016-06-16 00:05:14 -0700210async.auto({
211 makeDownloadsDir: function(cb) {
212 console.log('creating downloads dir')
evansiroky4be1c7a2016-06-16 18:23:34 -0700213 safeMkdir('./downloads', cb)
214 },
215 makeDistDir: function(cb) {
216 console.log('createing dist dir')
217 safeMkdir('./dist', cb)
evansirokyd401c892016-06-16 00:05:14 -0700218 },
219 getEfeleShapefile: ['makeDownloadsDir', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700220 console.log('download efele.net shapefile')
221 var efeleFilename = './downloads/tz_world_mp.zip'
222 fetchIfNeeded(efeleFilename, cb, function() {
223 var file = fs.createWriteStream(efeleFilename)
224 http.get('http://efele.net/maps/tz/world/tz_world_mp.zip', function(response) {
225 response.pipe(file)
226 file
227 .on('finish', function() {
228 file.close(cb)
229 })
230 .on('error', cb)
231 })
evansirokyd401c892016-06-16 00:05:14 -0700232 })
233 }],
evansirokyd401c892016-06-16 00:05:14 -0700234 getOsmBoundaries: ['makeDownloadsDir', function(results, cb) {
235 console.log('downloading osm boundaries')
evansiroky63d35e12016-06-16 10:08:15 -0700236 async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb)
237 }],
238 extractEfeleNetShapefile: ['getOsmBoundaries', function(results, cb) {
evansiroky63d35e12016-06-16 10:08:15 -0700239 console.log('extracting efele.net shapefile')
240 extractToGeoJson(cb)
evansiroky50216c62016-06-16 17:41:47 -0700241 }],
242 dictifyEfeleNetData: ['extractEfeleNetShapefile', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700243 console.log('dictify efele.net')
evansiroky4be1c7a2016-06-16 18:23:34 -0700244 efeleGeoms = results.extractEfeleNetShapefile.features
245 for (var i = efeleGeoms.length - 1; i >= 0; i--) {
246 var curTz = efeleGeoms[i]
247 efeleLookup[curTz.properties.TZID] = i
evansiroky50216c62016-06-16 17:41:47 -0700248 }
evansiroky4be1c7a2016-06-16 18:23:34 -0700249 cb()
evansiroky50216c62016-06-16 17:41:47 -0700250 }],
evansiroky4be1c7a2016-06-16 18:23:34 -0700251 createZones: ['makeDistDir', 'dictifyEfeleNetData', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700252 console.log('createZones')
evansiroky50216c62016-06-16 17:41:47 -0700253 async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb)
254 }],
255 mergeZones: ['createZones', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700256 cb()
evansirokyd401c892016-06-16 00:05:14 -0700257 }]
evansiroky50216c62016-06-16 17:41:47 -0700258}, function(err, results) {
evansirokyd401c892016-06-16 00:05:14 -0700259 console.log('done')
260 if(err) {
261 console.log('error!', err)
262 return
263 }
evansirokyd401c892016-06-16 00:05:14 -0700264})