From 7891a6ea5ba074d979d893c4efd5fad7b70cf46c Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Sat, 5 Nov 2016 11:50:50 -0700 Subject: [PATCH 1/2] update packages and make js standard compliant --- index.js | 182 +++++++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 89 insertions(+), 95 deletions(-) diff --git a/index.js b/index.js index e85cf04..ada5c81 100644 --- a/index.js +++ b/index.js @@ -1,24 +1,21 @@ -var exec = require('child_process').exec, - fs = require('fs'), - http = require('http') - -var async = require('async'), - jsts = require('jsts'), - multiPolygon = require('turf-multipolygon'), - overpass = require('query-overpass'), - polygon = require('turf-polygon'), - shp = require('shpjs') - - -var osmBoundarySources = require('./osmBoundarySources.json'), - zoneCfg = require('./timezones.json'), - geoJsonReader = new jsts.io.GeoJSONReader(), - geoJsonWriter = new jsts.io.GeoJSONWriter(), - distZones = {} - -var safeMkdir = function(dirname, callback) { - fs.mkdir(dirname, function(err) { - if(err && err.code === 'EEXIST') { +var exec = require('child_process').exec +var fs = require('fs') + +var asynclib = require('async') +var jsts = require('jsts') +var multiPolygon = require('turf-multipolygon') +var overpass = require('query-overpass') +var polygon = require('turf-polygon') + +var osmBoundarySources = require('./osmBoundarySources.json') +var zoneCfg = require('./timezones.json') +var geoJsonReader = new jsts.io.GeoJSONReader() +var geoJsonWriter = new jsts.io.GeoJSONWriter() +var distZones = {} + +var safeMkdir = function (dirname, callback) { + fs.mkdir(dirname, function (err) { + if (err && err.code === 'EEXIST') { callback() } else { callback(err) @@ -26,12 +23,11 @@ var safeMkdir = function(dirname, callback) { }) } -debugGeo = function(op, a, b) { - +var debugGeo = function (op, a, b) { var result try { - switch(op) { + switch (op) { case 'union': result = a.union(b) break @@ -41,11 +37,11 @@ debugGeo = function(op, a, b) { case 'diff': try { result = a.difference(b) - } catch(e) { - if(e.name === 'TopologyException') { + } catch (e) { + if (e.name === 'TopologyException') { console.log('retry with GeometryPrecisionReducer') - var precisionModel = new jsts.geom.PrecisionModel(10000), - precisionReducer = new jsts.precision.GeometryPrecisionReducer(precisionModel) + var precisionModel = new jsts.geom.PrecisionModel(10000) + var precisionReducer = new jsts.precision.GeometryPrecisionReducer(precisionModel) a = precisionReducer.reduce(a) b = precisionReducer.reduce(b) @@ -60,7 +56,7 @@ debugGeo = function(op, a, b) { var err = new Error('invalid op: ' + op) throw err } - } catch(e) { + } catch (e) { console.log('op err') console.log(e) console.log(e.stack) @@ -72,14 +68,14 @@ debugGeo = function(op, a, b) { return result } -var fetchIfNeeded = function(file, superCallback, fetchFn) { - fs.stat(file, function(err) { - if(!err) { return superCallback() } +var fetchIfNeeded = function (file, superCallback, fetchFn) { + fs.stat(file, function (err) { + if (!err) { return superCallback() } fetchFn() }) } -var geoJsonToGeom = function(geoJson) { +var geoJsonToGeom = function (geoJson) { return geoJsonReader.read(JSON.stringify(geoJson)) } @@ -87,55 +83,54 @@ var geomToGeoJson = function (geom) { return geoJsonWriter.write(geom) } -var geomToGeoJsonString = function(geom) { +var geomToGeoJsonString = function (geom) { return JSON.stringify(geoJsonWriter.write(geom)) } -var downloadOsmBoundary = function(boundaryId, boundaryCallback) { - var cfg = osmBoundarySources[boundaryId], - query = '[out:json][timeout:60];(relation', - boundaryFilename = './downloads/' + boundaryId + '.json', - debug = 'getting data for ' + boundaryId, - queryKeys = Object.keys(cfg) +var downloadOsmBoundary = function (boundaryId, boundaryCallback) { + var cfg = osmBoundarySources[boundaryId] + var query = '[out:json][timeout:60];(relation' + var boundaryFilename = './downloads/' + boundaryId + '.json' + var debug = 'getting data for ' + boundaryId + var queryKeys = Object.keys(cfg) for (var i = queryKeys.length - 1; i >= 0; i--) { - var k = queryKeys[i], - v = cfg[k] + var k = queryKeys[i] + var v = cfg[k] query += '["' + k + '"="' + v + '"]' - } query += ');out body;>;out meta qt;' console.log(debug) - async.auto({ - downloadFromOverpass: function(cb) { + asynclib.auto({ + downloadFromOverpass: function (cb) { console.log('downloading from overpass') - fetchIfNeeded(boundaryFilename, boundaryCallback, function() { + fetchIfNeeded(boundaryFilename, boundaryCallback, function () { overpass(query, cb, { flatProperties: true }) }) }, - validateOverpassResult: ['downloadFromOverpass', function(results, cb) { + validateOverpassResult: ['downloadFromOverpass', function (results, cb) { var data = results.downloadFromOverpass - if(!data.features || data.features.length == 0) { - err = new Error('Invalid geojson for boundary: ' + boundaryId) + if (!data.features || data.features.length === 0) { + var err = new Error('Invalid geojson for boundary: ' + boundaryId) return cb(err) } cb() }], - saveSingleMultiPolygon: ['validateOverpassResult', function(results, cb) { - var data = results.downloadFromOverpass, - combined + saveSingleMultiPolygon: ['validateOverpassResult', function (results, cb) { + var data = results.downloadFromOverpass + var combined // union all multi-polygons / polygons into one for (var i = data.features.length - 1; i >= 0; i--) { var curOsmGeom = data.features[i].geometry - if(curOsmGeom.type === 'Polygon' || curOsmGeom.type === 'MultiPolygon') { + if (curOsmGeom.type === 'Polygon' || curOsmGeom.type === 'MultiPolygon') { console.log('combining border') var curGeom = geoJsonToGeom(curOsmGeom) - if(!combined) { + if (!combined) { combined = curGeom } else { combined = debugGeo('union', curGeom, combined) @@ -160,15 +155,15 @@ var getTzDistFilename = function (tzid) { * - `id` if from a file * - `id` if from a file */ -var getDataSource = function(source) { +var getDataSource = function (source) { var geoJson - if(source.source === 'overpass') { + if (source.source === 'overpass') { geoJson = require('./downloads/' + source.id + '.json') - } else if(source.source === 'manual-polygon') { + } else if (source.source === 'manual-polygon') { geoJson = polygon(source.data).geometry - } else if(source.source === 'manual-multipolygon') { + } else if (source.source === 'manual-multipolygon') { geoJson = multiPolygon(source.data).geometry - } else if(source.source === 'dist') { + } else if (source.source === 'dist') { geoJson = require(getTzDistFilename(source.id)) } else { var err = new Error('unknown source: ' + source.source) @@ -177,24 +172,24 @@ var getDataSource = function(source) { return geoJsonToGeom(geoJson) } -var makeTimezoneBoundary = function(tzid, callback) { +var makeTimezoneBoundary = function (tzid, callback) { console.log('makeTimezoneBoundary for', tzid) - var ops = zoneCfg[tzid], - geom + var ops = zoneCfg[tzid] + var geom - async.eachSeries(ops, function(task, cb) { + asynclib.eachSeries(ops, function (task, cb) { var taskData = getDataSource(task) console.log('-', task.op, task.id) - if(task.op === 'init') { + if (task.op === 'init') { geom = taskData - } else if(task.op === 'intersect') { + } else if (task.op === 'intersect') { geom = debugGeo('intersection', geom, taskData) - } else if(task.op === 'difference') { + } else if (task.op === 'difference') { geom = debugGeo('diff', geom, taskData) - } else if(task.op === 'difference-reverse-order') { + } else if (task.op === 'difference-reverse-order') { geom = debugGeo('diff', taskData, geom) - } else if(task.op === 'union') { + } else if (task.op === 'union') { geom = debugGeo('union', geom, taskData) } else { var err = new Error('unknown op: ' + task.op) @@ -202,8 +197,8 @@ var makeTimezoneBoundary = function(tzid, callback) { } cb() }, - function(err) { - if(err) { return callback(err) } + function (err) { + if (err) { return callback(err) } fs.writeFile(getTzDistFilename(tzid), geomToGeoJsonString(geom), callback) @@ -212,8 +207,8 @@ var makeTimezoneBoundary = function(tzid, callback) { var loadDistZonesIntoMemory = function () { console.log('load zones into memory') - var zones = Object.keys(zoneCfg), - tzid + var zones = Object.keys(zoneCfg) + var tzid for (var i = 0; i < zones.length; i++) { tzid = zones[i] @@ -227,9 +222,9 @@ var getDistZoneGeom = function (tzid) { var validateTimezoneBoundaries = function () { console.log('do validation') - var allZonesOk = true, - zones = Object.keys(zoneCfg), - compareTzid, tzid, zoneGeom + var allZonesOk = true + var zones = Object.keys(zoneCfg) + var compareTzid, tzid, zoneGeom for (var i = 0; i < zones.length; i++) { tzid = zones[i] @@ -239,11 +234,11 @@ var validateTimezoneBoundaries = function () { compareTzid = zones[j] var compareZoneGeom = getDistZoneGeom(compareTzid) - if(zoneGeom.intersects(compareZoneGeom)) { - var intersectedGeom = debugGeo('intersection', zoneGeom, compareZoneGeom), - intersectedArea = intersectedGeom.getArea() + if (zoneGeom.intersects(compareZoneGeom)) { + var intersectedGeom = debugGeo('intersection', zoneGeom, compareZoneGeom) + var intersectedArea = intersectedGeom.getArea() - if(intersectedArea > 0.0001) { + if (intersectedArea > 0.0001) { console.log('Validation error: ' + tzid + ' intersects ' + compareTzid + ' area: ' + intersectedArea) allZonesOk = false } @@ -252,17 +247,16 @@ var validateTimezoneBoundaries = function () { } return allZonesOk ? null : 'Zone validation unsuccessful' - } -var combineAndWriteZones = function(callback) { +var combineAndWriteZones = function (callback) { var stream = fs.createWriteStream('./dist/combined.json') var zones = Object.keys(zoneCfg) stream.write('{"type":"FeatureCollection","features":[') for (var i = 0; i < zones.length; i++) { - if(i > 0) { + if (i > 0) { stream.write(',') } var feature = { @@ -275,29 +269,29 @@ var combineAndWriteZones = function(callback) { stream.end(']}', callback) } -async.auto({ - makeDownloadsDir: function(cb) { +asynclib.auto({ + makeDownloadsDir: function (cb) { console.log('creating downloads dir') safeMkdir('./downloads', cb) }, - makeDistDir: function(cb) { + makeDistDir: function (cb) { console.log('createing dist dir') safeMkdir('./dist', cb) }, - getOsmBoundaries: ['makeDownloadsDir', function(results, cb) { + getOsmBoundaries: ['makeDownloadsDir', function (results, cb) { console.log('downloading osm boundaries') - async.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb) + asynclib.eachSeries(Object.keys(osmBoundarySources), downloadOsmBoundary, cb) }], - createZones: ['makeDistDir', 'getOsmBoundaries', function(results, cb) { + createZones: ['makeDistDir', 'getOsmBoundaries', function (results, cb) { console.log('createZones') - async.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb) + asynclib.each(Object.keys(zoneCfg), makeTimezoneBoundary, cb) }], - validateZones: ['createZones', function(results, cb) { + validateZones: ['createZones', function (results, cb) { console.log('validating zones') loadDistZonesIntoMemory() cb(validateTimezoneBoundaries()) }], - mergeZones: ['validateZones', function(results, cb) { + mergeZones: ['validateZones', function (results, cb) { console.log('merge zones') combineAndWriteZones(cb) }], @@ -308,13 +302,13 @@ async.auto({ makeShapefile: ['mergeZones', function (results, cb) { console.log('convert from geojson to shapefile') exec('ogr2ogr -nlt MULTIPOLYGON dist/combined_shapefile.shp dist/combined.json OGRGeoJSON', function (err, stdout, stderr) { - if(err) { return cb(err) } + if (err) { return cb(err) } exec('zip dist/timezones.shapefile.zip dist/combined_shapefile.*', cb) }) }] -}, function(err, results) { +}, function (err, results) { console.log('done') - if(err) { + if (err) { console.log('error!', err) return } diff --git a/package.json b/package.json index c479a85..ea3aee7 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "author": "Evan Siroky", "license": "MIT", "dependencies": { - "async": "^2.0.0-rc.5", + "async": "^2.1.2", "jsts": "^1.2.1", "query-overpass": "^1.1.0", "shpjs": "^3.3.2", From b57a5b9d95975d5d2c4a57b2f2201c6b00ffb38b Mon Sep 17 00:00:00 2001 From: Evan Siroky Date: Mon, 7 Nov 2016 10:22:34 -0800 Subject: [PATCH 2/2] 2016i update * Zone Changes ** Split Cyprus into 2 zones. The existing Asia/Nicosia now ends at the northern boundary of the United Nations Buffer Zone and the new zone Asia/Famagusta contains everything north of the buffer zone. ** Add missing data definitions: Congo-Kinshasa and South Sudan ** Old Crimea boundary no longer exists in OSM, use combination of Crimea + Sevastopol ** Typo of extra space in Harrison County fixed in OSM ** Taishan City now has invalid geometry in OSM, use Xinhui district instead when making boundaries ** Update to latest OSM data * Other changes ** Add download throttling of publicly available Overpass API ** Remove old dist files if they exist so ogr2ogr can work ** Update README to note change in Overpass API querying * Issues ** #4 --- README.md | 2 +- index.js | 25 +++++++++++++++++++- osmBoundarySources.json | 22 ++++++++++++++---- package.json | 1 + timezones.json | 51 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 88 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 54799a6..1b84b7a 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ There are two config files that describe the boundary building process. The `os ### Special note regarding the Overpass API -Although the code currently queries the publicly available overpass API, this is not recommended because you will get throttled really quickly! I was able to get away with this because I worked on extracting pieces of data over a few months. In the future, I hope to make a docker file that downloads a planet dump and starts a local version of the Overpass API to extract the data. +The code does query the publicly available overpass API, but it self-throttles the making of requests to have a minimum of 4 seconds gap between requests. If the Overpass API throttles the download, then the gap will be increased exponentionally. ### Running the project diff --git a/index.js b/index.js index ada5c81..78b2f80 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ var fs = require('fs') var asynclib = require('async') var jsts = require('jsts') +var rimraf = require('rimraf') var multiPolygon = require('turf-multipolygon') var overpass = require('query-overpass') var polygon = require('turf-polygon') @@ -12,6 +13,8 @@ var zoneCfg = require('./timezones.json') var geoJsonReader = new jsts.io.GeoJSONReader() var geoJsonWriter = new jsts.io.GeoJSONWriter() var distZones = {} +var minRequestGap = 4 +var curRequestGap = 4 var safeMkdir = function (dirname, callback) { fs.mkdir(dirname, function (err) { @@ -109,7 +112,25 @@ var downloadOsmBoundary = function (boundaryId, boundaryCallback) { downloadFromOverpass: function (cb) { console.log('downloading from overpass') fetchIfNeeded(boundaryFilename, boundaryCallback, function () { - overpass(query, cb, { flatProperties: true }) + var overpassResponseHandler = function (err, data) { + if (err) { + console.log(err) + console.log('Increasing overpass request gap') + curRequestGap *= 2 + makeQuery() + } else { + console.log('Success, decreasing overpass request gap') + curRequestGap = Math.max(minRequestGap, curRequestGap / 2) + cb(null, data) + } + } + var makeQuery = function () { + console.log('waiting ' + curRequestGap + ' seconds') + setTimeout(function () { + overpass(query, overpassResponseHandler, { flatProperties: true }) + }, curRequestGap * 1000) + } + makeQuery() }) }, validateOverpassResult: ['downloadFromOverpass', function (results, cb) { @@ -301,6 +322,8 @@ asynclib.auto({ }], makeShapefile: ['mergeZones', function (results, cb) { console.log('convert from geojson to shapefile') + rimraf.sync('dist/dist') + rimraf.sync('dist/combined_shapefile.*') exec('ogr2ogr -nlt MULTIPOLYGON dist/combined_shapefile.shp dist/combined.json OGRGeoJSON', function (err, stdout, stderr) { if (err) { return cb(err) } exec('zip dist/timezones.shapefile.zip dist/combined_shapefile.*', cb) diff --git a/osmBoundarySources.json b/osmBoundarySources.json index ba4890d..b9cef8c 100644 --- a/osmBoundarySources.json +++ b/osmBoundarySources.json @@ -251,6 +251,9 @@ "Congo-Brazzaville": { "ISO3166-1": "CG" }, + "Congo-Kinshasa": { + "ISO3166-1": "CD" + }, "Cook Islands": { "ISO3166-1": "CK" }, @@ -277,7 +280,7 @@ "timezone": "America/Creston" }, "Crimea": { - "name:en": "Crimean Federal District" + "name:en": "Republic of Crimea" }, "Croatia": { "ISO3166-1": "HR" @@ -466,7 +469,7 @@ "nist:state_fips": "21" }, "Harrison County, IN": { - "name": "Harrison County", + "name": "Harrison County", "nist:state_fips": "18" }, "Honduras": { @@ -1035,6 +1038,9 @@ "name": "Sequatchie County", "nist:state_fips": "47" }, + "Sevastopol": { + "name:en": "Sevastopol" + }, "Seychelles": { "ISO3166-1": "SC" }, @@ -1081,6 +1087,9 @@ "South Korea": { "ISO3166-1": "KR" }, + "South Sudan": { + "ISO3166-1": "SS" + }, "Spain": { "ISO3166-1": "ES" }, @@ -1125,9 +1134,6 @@ "Tajikistan": { "ISO3166-1": "TJ" }, - "Taishan City": { - "name:en": "Taishan City" - }, "Tanzania": { "name:en": "Tanzania" }, @@ -1217,6 +1223,9 @@ "United Kingdom": { "ISO3166-1": "GB" }, + "United Nations Buffer Zone": { + "name": "United Nations Buffer Zone" + }, "United States of America": { "ISO3166-1": "US" }, @@ -1281,6 +1290,9 @@ "Winnipeg-tz": { "timezone": "America/Winnipeg" }, + "Xinhui District": { + "name:en": "Xinhui District" + }, "Xinxing County": { "name:en": "Xinxing County" }, diff --git a/package.json b/package.json index ea3aee7..afb7e9c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "async": "^2.1.2", "jsts": "^1.2.1", "query-overpass": "^1.1.0", + "rimraf": "^2.5.4", "shpjs": "^3.3.2", "turf-multipolygon": "^1.0.1", "turf-polygon": "^1.0.3" diff --git a/timezones.json b/timezones.json index 7395847..adbd99f 100644 --- a/timezones.json +++ b/timezones.json @@ -2240,7 +2240,7 @@ }, { "op": "intersect", "source": "manual-multipolygon", - "data": [[[[110,44],[106,18],[87,18],[94,45],[110,44]]],[[[113.03,22.12],[113.05,22.097],[113.22,20.9],[113,19],[111.5,21.35],[111.45,21.48],[111.455,21.53],[111.441,21.5457],[111.4455,21.554],[111.4398,21.5638],[111.4391,21.574],[111.4364,21.5797],[111.4305,21.5802],[111.4311892,21.5843946],[111.6,21.6],[111.3,22.1],[110.5,22.3],[110,23],[111.9,23.2],[112.4,22.5],[112.8,22.45],[112.8,22.2],[113.0154615,22.1175344],[113.03,22.12]]]] + "data": [[[[110,44],[106,18],[87,18],[94,45],[110,44]]],[[[113.03,22.12],[113.05,22.097],[113.22,20.9],[113,19],[111.5,21.35],[111.45,21.48],[111.455,21.53],[111.441,21.5457],[111.4455,21.554],[111.4398,21.5638],[111.4391,21.574],[111.4364,21.5797],[111.4305,21.5802],[111.4311892,21.5843946],[111.6,21.6],[111.3,22.1],[110.5,22.3],[110,23],[111.9,23.2],[112.4,22.5],[112.8,22.45],[113,22.4],[113.0154615,22.1175344],[113.03,22.12]]]] }, { "op": "union", "source": "overpass", @@ -2278,9 +2278,9 @@ "source": "overpass", "id": "Kaiping City" }, { - "op": "union", + "op": "difference", "source": "overpass", - "id": "Taishan City" + "id": "Xinhui District" }, { "op": "union", "source": "overpass", @@ -2337,6 +2337,25 @@ "id": "Uzbekistan" } ], + "Asia/Famagusta": [ + { + "op": "init", + "source": "overpass", + "id": "Cyprus" + }, { + "op": "intersect", + "source": "manual-polygon", + "data": [[[32.609482,35.166161],[32.621326,35.176544],[32.6283528,35.1893165],[32.640381,35.229355],[32.659264,35.244498],[32.686043,35.244217],[32.701836,35.227672],[32.6968982,35.1834922],[32.703896,35.179982],[32.703037,35.140125],[32.764664,35.122576],[32.792988,35.118504],[32.811527,35.095614],[32.839851,35.080163],[32.866888,35.081287],[32.86397,35.101232],[32.886114,35.104181],[32.897272,35.105445],[32.906799,35.101162],[32.920532,35.090277],[32.959242,35.1006],[33.012371,35.147144],[33.122749,35.156407],[33.164978,35.189803],[33.211327,35.183349],[33.267288,35.160056],[33.307028,35.166231],[33.315933,35.172844],[33.31869,35.172116],[33.320729,35.172247],[33.321351,35.176167],[33.319076,35.176957],[33.319774,35.180561],[33.323979,35.181227],[33.325567,35.182016],[33.334258,35.181946],[33.335073,35.182508],[33.338056,35.181824],[33.348613,35.183121],[33.348312,35.179456],[33.349857,35.177421],[33.352985,35.178452],[33.353135,35.178618],[33.353725,35.178842],[33.355844,35.178176],[33.355855,35.175878],[33.355002,35.174878],[33.355168,35.17462],[33.356161,35.173813],[33.35689,35.174049],[33.358929,35.174036],[33.360474,35.174891],[33.362303,35.174808],[33.363773,35.174611],[33.365752,35.175264],[33.367361,35.175953],[33.367522,35.17601],[33.367442,35.176382],[33.367769,35.176584],[33.368048,35.177009],[33.368397,35.177119],[33.368493,35.177597],[33.368826,35.177763],[33.36933,35.178114],[33.369963,35.17782],[33.372281,35.179158],[33.372624,35.185506],[33.37198,35.186497],[33.374727,35.187734],[33.374566,35.188567],[33.373922,35.189452],[33.374491,35.190689],[33.388739,35.197799],[33.403759,35.186261],[33.412471,35.174054],[33.408952,35.15967],[33.424401,35.156267],[33.424273,35.139143],[33.459463,35.109026],[33.469849,35.048268],[33.447189,35.022968],[33.444614,35.004128],[33.458347,35.001914],[33.464913,35.004796],[33.485126,35.005886],[33.488731,35.060493],[33.576279,35.042927],[33.593616,35.032808],[33.705025,35.028591],[33.794975,35.050235],[33.821239,35.072577],[33.847847,35.063304],[33.888702,35.088591],[33.907499,35.067448],[33.931961,35.067167],[33.94136,35.070048],[33.960886,35.073525],[33.966765,35.072436],[33.977666,35.064919],[33.990326,35.064744],[33.9988932,35.069797],[33.999531,35.069626],[34.246445,35.178298],[35.134277,35.554574],[34.799194,36.160053],[32.382202,35.572449],[32.542877,35.396887],[32.5987035,35.1791626],[32.597401,35.175001],[32.609482,35.166161]]] + }, { + "op": "difference", + "source": "overpass", + "id": "British Sovereign Base Areas" + }, { + "op": "difference", + "source": "overpass", + "id": "United Nations Buffer Zone" + } + ], "Asia/Gaza": [ { "op": "init", @@ -2632,10 +2651,18 @@ "op": "init", "source": "overpass", "id": "Cyprus" + }, { + "op": "difference", + "source": "manual-polygon", + "data": [[[32.609482,35.166161],[32.621326,35.176544],[32.6283528,35.1893165],[32.640381,35.229355],[32.659264,35.244498],[32.686043,35.244217],[32.701836,35.227672],[32.6968982,35.1834922],[32.703896,35.179982],[32.703037,35.140125],[32.764664,35.122576],[32.792988,35.118504],[32.811527,35.095614],[32.839851,35.080163],[32.866888,35.081287],[32.86397,35.101232],[32.886114,35.104181],[32.897272,35.105445],[32.906799,35.101162],[32.920532,35.090277],[32.959242,35.1006],[33.012371,35.147144],[33.122749,35.156407],[33.164978,35.189803],[33.211327,35.183349],[33.267288,35.160056],[33.307028,35.166231],[33.315933,35.172844],[33.31869,35.172116],[33.320729,35.172247],[33.321351,35.176167],[33.319076,35.176957],[33.319774,35.180561],[33.323979,35.181227],[33.325567,35.182016],[33.334258,35.181946],[33.335073,35.182508],[33.338056,35.181824],[33.348613,35.183121],[33.348312,35.179456],[33.349857,35.177421],[33.352985,35.178452],[33.353135,35.178618],[33.353725,35.178842],[33.355844,35.178176],[33.355855,35.175878],[33.355002,35.174878],[33.355168,35.17462],[33.356161,35.173813],[33.35689,35.174049],[33.358929,35.174036],[33.360474,35.174891],[33.362303,35.174808],[33.363773,35.174611],[33.365752,35.175264],[33.367361,35.175953],[33.367522,35.17601],[33.367442,35.176382],[33.367769,35.176584],[33.368048,35.177009],[33.368397,35.177119],[33.368493,35.177597],[33.368826,35.177763],[33.36933,35.178114],[33.369963,35.17782],[33.372281,35.179158],[33.372624,35.185506],[33.37198,35.186497],[33.374727,35.187734],[33.374566,35.188567],[33.373922,35.189452],[33.374491,35.190689],[33.388739,35.197799],[33.403759,35.186261],[33.412471,35.174054],[33.408952,35.15967],[33.424401,35.156267],[33.424273,35.139143],[33.459463,35.109026],[33.469849,35.048268],[33.447189,35.022968],[33.444614,35.004128],[33.458347,35.001914],[33.464913,35.004796],[33.485126,35.005886],[33.488731,35.060493],[33.576279,35.042927],[33.593616,35.032808],[33.705025,35.028591],[33.794975,35.050235],[33.821239,35.072577],[33.847847,35.063304],[33.888702,35.088591],[33.907499,35.067448],[33.931961,35.067167],[33.94136,35.070048],[33.960886,35.073525],[33.966765,35.072436],[33.977666,35.064919],[33.990326,35.064744],[33.9988932,35.069797],[33.999531,35.069626],[34.246445,35.178298],[35.134277,35.554574],[34.799194,36.160053],[32.382202,35.572449],[32.542877,35.396887],[32.5987035,35.1791626],[32.597401,35.175001],[32.609482,35.166161]]] }, { "op": "union", "source": "overpass", "id": "British Sovereign Base Areas" + }, { + "op": "union", + "source": "overpass", + "id": "United Nations Buffer Zone" } ], "Asia/Novokuznetsk": [ @@ -2768,7 +2795,7 @@ }, { "op": "difference", "source": "manual-polygon", - "data": [[[113.03,22.12],[113.05,22.097],[113.22,20.9],[113,19],[107,19],[110,23],[111.9,23.2],[112.4,22.5],[112.8,22.45],[112.8,22.2],[113.0154615,22.1175344],[113.03,22.12]]] + "data": [[[113.03,22.12],[113.05,22.097],[113.22,20.9],[113,19],[107,19],[110,23],[111.9,23.2],[112.4,22.5],[112.8,22.45],[113,22.4],[113.0154615,22.1175344],[113.03,22.12]]] }, { "op": "difference", "source": "overpass", @@ -2786,9 +2813,9 @@ "source": "overpass", "id": "Kaiping City" }, { - "op": "difference", + "op": "union", "source": "overpass", - "id": "Taishan City" + "id": "Xinhui District" }, { "op": "difference", "source": "overpass", @@ -3452,6 +3479,10 @@ "op": "difference", "source": "overpass", "id": "Crimea" + }, { + "op": "difference", + "source": "overpass", + "id": "Sevastopol" }, { "op": "difference", "source": "overpass", @@ -3565,6 +3596,10 @@ "op": "difference", "source": "overpass", "id": "Crimea" + }, { + "op": "difference", + "source": "overpass", + "id": "Sevastopol" }, { "op": "difference", "source": "overpass", @@ -3655,6 +3690,10 @@ "op": "init", "source": "overpass", "id": "Crimea" + }, { + "op": "union", + "source": "overpass", + "id": "Sevastopol" } ], "Europe/Skopje": [