blob: 7c848ef372a13ba3affa04da254bfc192fc7c102 [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
92 }
93
evansiroky4be1c7a2016-06-16 18:23:34 -070094 boundaryFilename += '.json'
95
evansiroky63d35e12016-06-16 10:08:15 -070096 console.log(debug)
97
98 async.auto({
evansiroky50216c62016-06-16 17:41:47 -070099 downloadFromOverpass: function(results, cb) {
100 console.log('downloading from overpass')
evansiroky4be1c7a2016-06-16 18:23:34 -0700101 fetchIfNeeded(boundaryFilename, boundaryCallback, function() {
evansiroky50216c62016-06-16 17:41:47 -0700102 overpass(query, cb, { flatProperties: true })
evansiroky63d35e12016-06-16 10:08:15 -0700103 })
104 },
evansiroky63d35e12016-06-16 10:08:15 -0700105 validateOverpassResult: ['downloadFromOverpass', function(results, cb) {
106 var data = results.downloadFromOverpass
107 if(!data.features || data.features.length == 0) {
108 err = new Error('Invalid geojson for boundary: ' + boundaryId)
109 return cb(err)
110 }
111 cb()
112 }],
113 saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) {
114 var data = results.downloadFromOverpass,
115 combined
116
117 // union all multi-polygons / polygons into one
118 for (var i = data.features.length - 1; i >= 0; i--) {
119 var curGeom = data.features[i].geometry
120 if(curGeom.type === 'Polygon' || curGeom.type === 'MultiPolygon') {
121 console.log('combining border')
122 if(!combined) {
123 combined = curGeom
124 } else {
125 combined = union(curGeom, combined)
126 }
127 }
128 }
129 fs.writeFile(boundaryFilename, JSON.stringify(combined, null, 2), cb)
130 }]
131 }, boundaryCallback)
132}
evansirokyd401c892016-06-16 00:05:14 -0700133
evansiroky4be1c7a2016-06-16 18:23:34 -0700134var getDataSource = function(source) {
135 if(source.source === 'efele') {
evansiroky35f64342016-06-16 22:17:04 -0700136 return efeleGeoms[efeleLookup[source.id]].geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700137 } else if(source.source === 'overpass') {
138 return require('./downloads/' + source.id + '.json')
evansiroky35f64342016-06-16 22:17:04 -0700139 } else if(source.source === 'manual-polygon') {
140 return polygon(source.data).geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700141 } else {
142 var err = new Error('unknown source: ' + source.source)
143 throw err
144 }
145}
146
147var makeTimezoneBoundary = function(tzid, callback) {
evansiroky35f64342016-06-16 22:17:04 -0700148 console.log('makeTimezoneBoundary for', tzid)
149
evansiroky4be1c7a2016-06-16 18:23:34 -0700150 var ops = zoneCfg[tzid],
151 geom
152
153 async.eachSeries(ops, function(task, cb) {
154 var taskData = getDataSource(task)
evansiroky35f64342016-06-16 22:17:04 -0700155 //console.log(task.op)
evansiroky4be1c7a2016-06-16 18:23:34 -0700156 if(task.op === 'init') {
157 geom = taskData
158 } else if(task.op === 'intersect') {
159 geom = intersection(geom, taskData)
160 } else if(task.op === 'difference') {
161 geom = diff(geom, taskData)
162 }
evansiroky35f64342016-06-16 22:17:04 -0700163 cb()
evansiroky4be1c7a2016-06-16 18:23:34 -0700164 }, function(err) {
165 if(err) { return callback(err) }
evansiroky35f64342016-06-16 22:17:04 -0700166 fs.writeFile('./dist/' + tzid.replace(/\//g, '__') + '.json', JSON.stringify(geom), callback)
evansiroky4be1c7a2016-06-16 18:23:34 -0700167 })
168}
169
evansirokyd401c892016-06-16 00:05:14 -0700170async.auto({
171 makeDownloadsDir: function(cb) {
172 console.log('creating downloads dir')
evansiroky4be1c7a2016-06-16 18:23:34 -0700173 safeMkdir('./downloads', cb)
174 },
175 makeDistDir: function(cb) {
176 console.log('createing dist dir')
177 safeMkdir('./dist', cb)
evansirokyd401c892016-06-16 00:05:14 -0700178 },
179 getEfeleShapefile: ['makeDownloadsDir', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700180 console.log('download efele.net shapefile')
181 var efeleFilename = './downloads/tz_world_mp.zip'
182 fetchIfNeeded(efeleFilename, cb, function() {
183 var file = fs.createWriteStream(efeleFilename)
184 http.get('http://efele.net/maps/tz/world/tz_world_mp.zip', function(response) {
185 response.pipe(file)
186 file
187 .on('finish', function() {
188 file.close(cb)
189 })
190 .on('error', cb)
191 })
evansirokyd401c892016-06-16 00:05:14 -0700192 })
193 }],
evansirokyd401c892016-06-16 00:05:14 -0700194 getOsmBoundaries: ['makeDownloadsDir', function(results, cb) {
195 console.log('downloading osm boundaries')
evansiroky63d35e12016-06-16 10:08:15 -0700196 async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb)
197 }],
198 extractEfeleNetShapefile: ['getOsmBoundaries', function(results, cb) {
evansiroky63d35e12016-06-16 10:08:15 -0700199 console.log('extracting efele.net shapefile')
200 extractToGeoJson(cb)
evansiroky50216c62016-06-16 17:41:47 -0700201 }],
202 dictifyEfeleNetData: ['extractEfeleNetShapefile', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700203 console.log('dictify efele.net')
evansiroky4be1c7a2016-06-16 18:23:34 -0700204 efeleGeoms = results.extractEfeleNetShapefile.features
205 for (var i = efeleGeoms.length - 1; i >= 0; i--) {
206 var curTz = efeleGeoms[i]
207 efeleLookup[curTz.properties.TZID] = i
evansiroky50216c62016-06-16 17:41:47 -0700208 }
evansiroky4be1c7a2016-06-16 18:23:34 -0700209 cb()
evansiroky50216c62016-06-16 17:41:47 -0700210 }],
evansiroky4be1c7a2016-06-16 18:23:34 -0700211 createZones: ['makeDistDir', 'dictifyEfeleNetData', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700212 console.log('createZones')
evansiroky50216c62016-06-16 17:41:47 -0700213 async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb)
214 }],
215 mergeZones: ['createZones', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700216 cb()
evansirokyd401c892016-06-16 00:05:14 -0700217 }]
evansiroky50216c62016-06-16 17:41:47 -0700218}, function(err, results) {
evansirokyd401c892016-06-16 00:05:14 -0700219 console.log('done')
220 if(err) {
221 console.log('error!', err)
222 return
223 }
evansirokyd401c892016-06-16 00:05:14 -0700224})