blob: 2c7040e980cf93a089c81b563b1847c1d6386955 [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(),
15 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')))
39 .then(function(geojson) { console.log('extract success'); callback(null, geojson) })
40 .catch(function(e){ console.log('extract err', e); callback(e) })
41}
42
evansiroky4be1c7a2016-06-16 18:23:34 -070043var jstsToGeojson = function(data) {
44 var result = geoJsonWriter.write(data);
45
46 if(result.type === 'GeometryCollection' && result.geometries.length === 0) {
47 return undefined
48 } else {
49 return {
50 type: 'Feature',
51 properties: {},
52 geometry: result
53 }
54 }
55}
56
evansiroky63d35e12016-06-16 10:08:15 -070057var union = function(a, b) {
58 var _a = geoJsonReader.read(JSON.stringify(a)),
59 _b = geoJsonReader.read(JSON.stringify(b))
60
61 var result = _a.union(_b)
62
evansiroky4be1c7a2016-06-16 18:23:34 -070063 return jstsToGeojson(result)
evansiroky63d35e12016-06-16 10:08:15 -070064
65}
66
evansiroky4be1c7a2016-06-16 18:23:34 -070067// copied and modified from turf-intersect
68var intersection = function(a, b) {
69 var _a = geoJsonReader.read(JSON.stringify(a)),
70 _b = geoJsonReader.read(JSON.stringify(b))
71
72 var result = _a.intersection(_b)
73
74 return jstsToGeojson(result)
75}
76
77var diff = function(a, b) {
78 var _a = geoJsonReader.read(JSON.stringify(a)),
79 _b = geoJsonReader.read(JSON.stringify(b))
80
81 var result = _a.difference(_b)
82
83 return jstsToGeojson(result)
84}
85
evansiroky50216c62016-06-16 17:41:47 -070086var fetchIfNeeded = function(file, superCallback, fetchFn) {
87 fs.stat(file, function(err) {
88 if(!err) { return superCallback() }
89 fetchFn()
90 })
91}
92
evansiroky63d35e12016-06-16 10:08:15 -070093var downloadOsmBoundary = function(boundaryId, boundaryCallback) {
94 var cfg = osmBoundarySources[boundaryId],
95 query = '[out:json][timeout:60];',
96 boundaryFilename = './downloads/' + cfg.type,
97 debug = 'getting data for '
98
99 if(cfg.type === 'ISO3166-1') {
100 query += '(relation["boundary"="administrative"]' +
101 '["admin_level"="2"]' +
102 '["ISO3166-1"="' + cfg.code + '"]);' +
103 'out body;>;out meta qt;'
104 boundaryFilename += '_' + cfg.code
105 debug += 'country: ' + cfg.code
106 }
107
evansiroky4be1c7a2016-06-16 18:23:34 -0700108 boundaryFilename += '.json'
109
evansiroky63d35e12016-06-16 10:08:15 -0700110 console.log(debug)
111
112 async.auto({
evansiroky50216c62016-06-16 17:41:47 -0700113 downloadFromOverpass: function(results, cb) {
114 console.log('downloading from overpass')
evansiroky4be1c7a2016-06-16 18:23:34 -0700115 fetchIfNeeded(boundaryFilename, boundaryCallback, function() {
evansiroky50216c62016-06-16 17:41:47 -0700116 overpass(query, cb, { flatProperties: true })
evansiroky63d35e12016-06-16 10:08:15 -0700117 })
118 },
evansiroky63d35e12016-06-16 10:08:15 -0700119 validateOverpassResult: ['downloadFromOverpass', function(results, cb) {
120 var data = results.downloadFromOverpass
121 if(!data.features || data.features.length == 0) {
122 err = new Error('Invalid geojson for boundary: ' + boundaryId)
123 return cb(err)
124 }
125 cb()
126 }],
127 saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) {
128 var data = results.downloadFromOverpass,
129 combined
130
131 // union all multi-polygons / polygons into one
132 for (var i = data.features.length - 1; i >= 0; i--) {
133 var curGeom = data.features[i].geometry
134 if(curGeom.type === 'Polygon' || curGeom.type === 'MultiPolygon') {
135 console.log('combining border')
136 if(!combined) {
137 combined = curGeom
138 } else {
139 combined = union(curGeom, combined)
140 }
141 }
142 }
143 fs.writeFile(boundaryFilename, JSON.stringify(combined, null, 2), cb)
144 }]
145 }, boundaryCallback)
146}
evansirokyd401c892016-06-16 00:05:14 -0700147
evansiroky4be1c7a2016-06-16 18:23:34 -0700148var getDataSource = function(source) {
149 if(source.source === 'efele') {
150 return efeleGeoms[efeleLookup[source.id]]
151 } else if(source.source === 'overpass') {
152 return require('./downloads/' + source.id + '.json')
153 } else if(source.source === 'bbox') {
154 return polygon(source.data)
155 } else {
156 var err = new Error('unknown source: ' + source.source)
157 throw err
158 }
159}
160
161var makeTimezoneBoundary = function(tzid, callback) {
162 var ops = zoneCfg[tzid],
163 geom
164
165 async.eachSeries(ops, function(task, cb) {
166 var taskData = getDataSource(task)
167 if(task.op === 'init') {
168 geom = taskData
169 } else if(task.op === 'intersect') {
170 geom = intersection(geom, taskData)
171 } else if(task.op === 'difference') {
172 geom = diff(geom, taskData)
173 }
174 }, function(err) {
175 if(err) { return callback(err) }
176 fs.writeFile('./dist/' + tzid.replace(/\//g, '__'), JSON.stringify(geom))
177 })
178}
179
evansirokyd401c892016-06-16 00:05:14 -0700180async.auto({
181 makeDownloadsDir: function(cb) {
182 console.log('creating downloads dir')
evansiroky4be1c7a2016-06-16 18:23:34 -0700183 safeMkdir('./downloads', cb)
184 },
185 makeDistDir: function(cb) {
186 console.log('createing dist dir')
187 safeMkdir('./dist', cb)
evansirokyd401c892016-06-16 00:05:14 -0700188 },
189 getEfeleShapefile: ['makeDownloadsDir', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700190 console.log('download efele.net shapefile')
191 var efeleFilename = './downloads/tz_world_mp.zip'
192 fetchIfNeeded(efeleFilename, cb, function() {
193 var file = fs.createWriteStream(efeleFilename)
194 http.get('http://efele.net/maps/tz/world/tz_world_mp.zip', function(response) {
195 response.pipe(file)
196 file
197 .on('finish', function() {
198 file.close(cb)
199 })
200 .on('error', cb)
201 })
evansirokyd401c892016-06-16 00:05:14 -0700202 })
203 }],
evansirokyd401c892016-06-16 00:05:14 -0700204 getOsmBoundaries: ['makeDownloadsDir', function(results, cb) {
205 console.log('downloading osm boundaries')
evansiroky63d35e12016-06-16 10:08:15 -0700206 async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb)
207 }],
208 extractEfeleNetShapefile: ['getOsmBoundaries', function(results, cb) {
evansiroky63d35e12016-06-16 10:08:15 -0700209 console.log('extracting efele.net shapefile')
210 extractToGeoJson(cb)
evansiroky50216c62016-06-16 17:41:47 -0700211 }],
212 dictifyEfeleNetData: ['extractEfeleNetShapefile', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700213 efeleGeoms = results.extractEfeleNetShapefile.features
214 for (var i = efeleGeoms.length - 1; i >= 0; i--) {
215 var curTz = efeleGeoms[i]
216 efeleLookup[curTz.properties.TZID] = i
evansiroky50216c62016-06-16 17:41:47 -0700217 }
evansiroky4be1c7a2016-06-16 18:23:34 -0700218 cb()
evansiroky50216c62016-06-16 17:41:47 -0700219 }],
evansiroky4be1c7a2016-06-16 18:23:34 -0700220 createZones: ['makeDistDir', 'dictifyEfeleNetData', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700221 async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb)
222 }],
223 mergeZones: ['createZones', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700224 cb()
evansirokyd401c892016-06-16 00:05:14 -0700225 }]
evansiroky50216c62016-06-16 17:41:47 -0700226}, function(err, results) {
evansirokyd401c892016-06-16 00:05:14 -0700227 console.log('done')
228 if(err) {
229 console.log('error!', err)
230 return
231 }
evansirokyd401c892016-06-16 00:05:14 -0700232})