blob: b7871cccb31f35aa30750b321a29137e33f4215e [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'),
Evan Siroky8e30a2e2016-08-06 19:55:35 -07006 multiPolygon = require('turf-multipolygon'),
evansirokyd401c892016-06-16 00:05:14 -07007 overpass = require('query-overpass'),
evansiroky4be1c7a2016-06-16 18:23:34 -07008 polygon = require('turf-polygon'),
evansirokyd401c892016-06-16 00:05:14 -07009 shp = require('shpjs')
10
11
evansiroky63d35e12016-06-16 10:08:15 -070012var osmBoundarySources = require('./osmBoundarySources.json'),
evansiroky50216c62016-06-16 17:41:47 -070013 zoneCfg = require('./timezones.json'),
evansiroky63d35e12016-06-16 10:08:15 -070014 geoJsonReader = new jsts.io.GeoJSONReader(),
evansiroky4be1c7a2016-06-16 18:23:34 -070015 geoJsonWriter = new jsts.io.GeoJSONWriter(),
evansiroky35f64342016-06-16 22:17:04 -070016 efeleGeoms, efeleLookup = {}
evansiroky63d35e12016-06-16 10:08:15 -070017
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) {
evansirokybecb56e2016-07-06 12:42:35 -070044
45 var result
46
evansiroky6f9d8f72016-06-21 16:27:54 -070047 try {
48 switch(op) {
49 case 'union':
evansirokybecb56e2016-07-06 12:42:35 -070050 result = a.union(b)
evansiroky6f9d8f72016-06-21 16:27:54 -070051 break
52 case 'intersection':
evansirokybecb56e2016-07-06 12:42:35 -070053 result = a.intersection(b)
evansiroky6f9d8f72016-06-21 16:27:54 -070054 break
55 case 'diff':
evansirokybecb56e2016-07-06 12:42:35 -070056 try {
57 result = a.difference(b)
58 } catch(e) {
59 if(e.name === 'TopologyException') {
60 console.log('retry with GeometryPrecisionReducer')
Evan Siroky783532d2016-07-07 16:44:01 -070061 var precisionModel = new jsts.geom.PrecisionModel(10000),
evansirokybecb56e2016-07-06 12:42:35 -070062 precisionReducer = new jsts.precision.GeometryPrecisionReducer(precisionModel)
63
64 a = precisionReducer.reduce(a)
65 b = precisionReducer.reduce(b)
66
67 result = a.difference(b)
68 } else {
69 throw e
70 }
71 }
evansiroky6f9d8f72016-06-21 16:27:54 -070072 break
73 default:
74 var err = new Error('invalid op: ' + op)
75 throw err
76 }
77 } catch(e) {
78 console.log('op err')
evansirokybecb56e2016-07-06 12:42:35 -070079 console.log(e)
80 console.log(e.stack)
81 fs.writeFileSync('debug_' + op + '_a.json', JSON.stringify(geoJsonWriter.write(a)))
82 fs.writeFileSync('debug_' + op + '_b.json', JSON.stringify(geoJsonWriter.write(b)))
evansiroky6f9d8f72016-06-21 16:27:54 -070083 throw e
84 }
evansiroky6f9d8f72016-06-21 16:27:54 -070085
evansirokybecb56e2016-07-06 12:42:35 -070086 return result
evansiroky4be1c7a2016-06-16 18:23:34 -070087}
88
evansiroky50216c62016-06-16 17:41:47 -070089var fetchIfNeeded = function(file, superCallback, fetchFn) {
90 fs.stat(file, function(err) {
91 if(!err) { return superCallback() }
92 fetchFn()
93 })
94}
95
Evan Siroky5669adc2016-07-07 17:25:31 -070096var geoJsonToGeom = function(geoJson) {
97 return geoJsonReader.read(JSON.stringify(geoJson))
98}
99
100var geomToGeoJsonString = function(geom) {
101 return JSON.stringify(geoJsonWriter.write(geom))
102}
103
evansiroky63d35e12016-06-16 10:08:15 -0700104var downloadOsmBoundary = function(boundaryId, boundaryCallback) {
105 var cfg = osmBoundarySources[boundaryId],
Evan Siroky5669adc2016-07-07 17:25:31 -0700106 query = '[out:json][timeout:60];(relation',
107 boundaryFilename = './downloads/' + boundaryId + '.json',
108 debug = 'getting data for ' + boundaryId,
109 queryKeys = Object.keys(cfg)
evansiroky63d35e12016-06-16 10:08:15 -0700110
Evan Siroky5669adc2016-07-07 17:25:31 -0700111 for (var i = queryKeys.length - 1; i >= 0; i--) {
112 var k = queryKeys[i],
113 v = cfg[k]
114
115 query += '["' + k + '"="' + v + '"]'
116
evansiroky63d35e12016-06-16 10:08:15 -0700117 }
118
Evan Siroky5669adc2016-07-07 17:25:31 -0700119 query += ');out body;>;out meta qt;'
evansiroky4be1c7a2016-06-16 18:23:34 -0700120
evansiroky63d35e12016-06-16 10:08:15 -0700121 console.log(debug)
122
123 async.auto({
evansiroky5d008132016-06-17 08:37:51 -0700124 downloadFromOverpass: function(cb) {
evansiroky50216c62016-06-16 17:41:47 -0700125 console.log('downloading from overpass')
evansiroky4be1c7a2016-06-16 18:23:34 -0700126 fetchIfNeeded(boundaryFilename, boundaryCallback, function() {
evansiroky50216c62016-06-16 17:41:47 -0700127 overpass(query, cb, { flatProperties: true })
evansiroky63d35e12016-06-16 10:08:15 -0700128 })
129 },
evansiroky63d35e12016-06-16 10:08:15 -0700130 validateOverpassResult: ['downloadFromOverpass', function(results, cb) {
131 var data = results.downloadFromOverpass
132 if(!data.features || data.features.length == 0) {
133 err = new Error('Invalid geojson for boundary: ' + boundaryId)
134 return cb(err)
135 }
136 cb()
137 }],
138 saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) {
139 var data = results.downloadFromOverpass,
140 combined
141
142 // union all multi-polygons / polygons into one
143 for (var i = data.features.length - 1; i >= 0; i--) {
Evan Siroky5669adc2016-07-07 17:25:31 -0700144 var curOsmGeom = data.features[i].geometry
145 if(curOsmGeom.type === 'Polygon' || curOsmGeom.type === 'MultiPolygon') {
evansiroky63d35e12016-06-16 10:08:15 -0700146 console.log('combining border')
Evan Siroky5669adc2016-07-07 17:25:31 -0700147 var curGeom = geoJsonToGeom(curOsmGeom)
evansiroky63d35e12016-06-16 10:08:15 -0700148 if(!combined) {
149 combined = curGeom
150 } else {
Evan Siroky5669adc2016-07-07 17:25:31 -0700151 combined = debugGeo('union', curGeom, combined)
evansiroky63d35e12016-06-16 10:08:15 -0700152 }
153 }
154 }
Evan Siroky5669adc2016-07-07 17:25:31 -0700155 fs.writeFile(boundaryFilename, geomToGeoJsonString(combined), cb)
evansiroky63d35e12016-06-16 10:08:15 -0700156 }]
157 }, boundaryCallback)
158}
evansirokyd401c892016-06-16 00:05:14 -0700159
evansiroky4be1c7a2016-06-16 18:23:34 -0700160var getDataSource = function(source) {
evansirokybecb56e2016-07-06 12:42:35 -0700161 var geoJson
evansiroky4be1c7a2016-06-16 18:23:34 -0700162 if(source.source === 'efele') {
evansirokybecb56e2016-07-06 12:42:35 -0700163 geoJson = efeleGeoms[efeleLookup[source.id]].geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700164 } else if(source.source === 'overpass') {
evansirokybecb56e2016-07-06 12:42:35 -0700165 geoJson = require('./downloads/' + source.id + '.json')
evansiroky35f64342016-06-16 22:17:04 -0700166 } else if(source.source === 'manual-polygon') {
evansirokybecb56e2016-07-06 12:42:35 -0700167 geoJson = polygon(source.data).geometry
Evan Siroky8e30a2e2016-08-06 19:55:35 -0700168 } else if(source.source === 'manual-multipolygon') {
169 geoJson = multiPolygon(source.data).geometry
evansiroky4be1c7a2016-06-16 18:23:34 -0700170 } else {
171 var err = new Error('unknown source: ' + source.source)
172 throw err
173 }
Evan Siroky5669adc2016-07-07 17:25:31 -0700174 return geoJsonToGeom(geoJson)
evansiroky4be1c7a2016-06-16 18:23:34 -0700175}
176
177var makeTimezoneBoundary = function(tzid, callback) {
evansiroky35f64342016-06-16 22:17:04 -0700178 console.log('makeTimezoneBoundary for', tzid)
179
evansiroky4be1c7a2016-06-16 18:23:34 -0700180 var ops = zoneCfg[tzid],
181 geom
182
183 async.eachSeries(ops, function(task, cb) {
184 var taskData = getDataSource(task)
evansiroky6f9d8f72016-06-21 16:27:54 -0700185 console.log('-', task.op, task.id)
evansiroky4be1c7a2016-06-16 18:23:34 -0700186 if(task.op === 'init') {
187 geom = taskData
188 } else if(task.op === 'intersect') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700189 geom = debugGeo('intersection', geom, taskData)
evansiroky4be1c7a2016-06-16 18:23:34 -0700190 } else if(task.op === 'difference') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700191 geom = debugGeo('diff', geom, taskData)
Evan Siroky8ccaf0b2016-09-03 11:36:13 -0700192 } else if(task.op === 'difference-reverse-order') {
193 geom = debugGeo('diff', taskData, geom)
evansiroky6e45be62016-06-17 08:46:28 -0700194 } else if(task.op === 'union') {
evansiroky6f9d8f72016-06-21 16:27:54 -0700195 geom = debugGeo('union', geom, taskData)
Evan Siroky8ccaf0b2016-09-03 11:36:13 -0700196 } else {
197 var err = new Error('unknown op: ' + task.op)
198 return cb(err)
evansiroky4be1c7a2016-06-16 18:23:34 -0700199 }
evansiroky35f64342016-06-16 22:17:04 -0700200 cb()
evansiroky4be1c7a2016-06-16 18:23:34 -0700201 }, function(err) {
202 if(err) { return callback(err) }
evansirokybecb56e2016-07-06 12:42:35 -0700203 fs.writeFile('./dist/' + tzid.replace(/\//g, '__') + '.json',
Evan Siroky5669adc2016-07-07 17:25:31 -0700204 geomToGeoJsonString(geom),
evansirokybecb56e2016-07-06 12:42:35 -0700205 callback)
evansiroky4be1c7a2016-06-16 18:23:34 -0700206 })
207}
208
evansirokyd401c892016-06-16 00:05:14 -0700209async.auto({
210 makeDownloadsDir: function(cb) {
211 console.log('creating downloads dir')
evansiroky4be1c7a2016-06-16 18:23:34 -0700212 safeMkdir('./downloads', cb)
213 },
214 makeDistDir: function(cb) {
215 console.log('createing dist dir')
216 safeMkdir('./dist', cb)
evansirokyd401c892016-06-16 00:05:14 -0700217 },
218 getEfeleShapefile: ['makeDownloadsDir', function(results, cb) {
evansiroky50216c62016-06-16 17:41:47 -0700219 console.log('download efele.net shapefile')
220 var efeleFilename = './downloads/tz_world_mp.zip'
221 fetchIfNeeded(efeleFilename, cb, function() {
222 var file = fs.createWriteStream(efeleFilename)
223 http.get('http://efele.net/maps/tz/world/tz_world_mp.zip', function(response) {
224 response.pipe(file)
225 file
226 .on('finish', function() {
227 file.close(cb)
228 })
229 .on('error', cb)
230 })
evansirokyd401c892016-06-16 00:05:14 -0700231 })
232 }],
evansirokyd401c892016-06-16 00:05:14 -0700233 getOsmBoundaries: ['makeDownloadsDir', function(results, cb) {
234 console.log('downloading osm boundaries')
evansiroky63d35e12016-06-16 10:08:15 -0700235 async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb)
236 }],
237 extractEfeleNetShapefile: ['getOsmBoundaries', function(results, cb) {
evansiroky63d35e12016-06-16 10:08:15 -0700238 console.log('extracting efele.net shapefile')
239 extractToGeoJson(cb)
evansiroky50216c62016-06-16 17:41:47 -0700240 }],
241 dictifyEfeleNetData: ['extractEfeleNetShapefile', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700242 console.log('dictify efele.net')
evansiroky4be1c7a2016-06-16 18:23:34 -0700243 efeleGeoms = results.extractEfeleNetShapefile.features
244 for (var i = efeleGeoms.length - 1; i >= 0; i--) {
245 var curTz = efeleGeoms[i]
246 efeleLookup[curTz.properties.TZID] = i
evansiroky50216c62016-06-16 17:41:47 -0700247 }
evansiroky4be1c7a2016-06-16 18:23:34 -0700248 cb()
evansiroky50216c62016-06-16 17:41:47 -0700249 }],
evansiroky4be1c7a2016-06-16 18:23:34 -0700250 createZones: ['makeDistDir', 'dictifyEfeleNetData', function(results, cb) {
evansiroky35f64342016-06-16 22:17:04 -0700251 console.log('createZones')
evansiroky50216c62016-06-16 17:41:47 -0700252 async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb)
253 }],
254 mergeZones: ['createZones', function(results, cb) {
evansiroky4be1c7a2016-06-16 18:23:34 -0700255 cb()
evansirokyd401c892016-06-16 00:05:14 -0700256 }]
evansiroky50216c62016-06-16 17:41:47 -0700257}, function(err, results) {
evansirokyd401c892016-06-16 00:05:14 -0700258 console.log('done')
259 if(err) {
260 console.log('error!', err)
261 return
262 }
evansirokyd401c892016-06-16 00:05:14 -0700263})