Created
February 3, 2020 21:30
-
-
Save jatorre/1ee18a1b50229e2e72f61a05a31275cb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CREATE TEMP FUNCTION bbox(x NUMERIC, y NUMERIC,zoom NUMERIC ) | |
RETURNS ARRAY<FLOAT64> | |
LANGUAGE js AS """ | |
var SphericalMercator = (function(){ | |
// Closures including constants and other precalculated values. | |
var cache = {}, | |
EPSLN = 1.0e-10, | |
D2R = Math.PI / 180, | |
R2D = 180 / Math.PI, | |
// 900913 properties. | |
A = 6378137.0, | |
MAXEXTENT = 20037508.342789244; | |
function isFloat(n){ | |
return Number(n) === n && n % 1 !== 0; | |
} | |
// SphericalMercator constructor: precaches calculations | |
// for fast tile lookups. | |
function SphericalMercator(options) { | |
options = options || {}; | |
this.size = options.size || 256; | |
if (!cache[this.size]) { | |
var size = this.size; | |
var c = cache[this.size] = {}; | |
c.Bc = []; | |
c.Cc = []; | |
c.zc = []; | |
c.Ac = []; | |
for (var d = 0; d < 30; d++) { | |
c.Bc.push(size / 360); | |
c.Cc.push(size / (2 * Math.PI)); | |
c.zc.push(size / 2); | |
c.Ac.push(size); | |
size *= 2; | |
} | |
} | |
this.Bc = cache[this.size].Bc; | |
this.Cc = cache[this.size].Cc; | |
this.zc = cache[this.size].zc; | |
this.Ac = cache[this.size].Ac; | |
}; | |
// Convert lon lat to screen pixel value | |
// | |
// - `ll` {Array} `[lon, lat]` array of geographic coordinates. | |
// - `zoom` {Number} zoom level. | |
SphericalMercator.prototype.px = function(ll, zoom) { | |
if (isFloat(zoom)) { | |
var size = this.size * Math.pow(2, zoom); | |
var d = size / 2; | |
var bc = (size / 360); | |
var cc = (size / (2 * Math.PI)); | |
var ac = size; | |
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999); | |
var x = d + ll[0] * bc; | |
var y = d + 0.5 * Math.log((1 + f) / (1 - f)) * -cc; | |
(x > ac) && (x = ac); | |
(y > ac) && (y = ac); | |
//(x < 0) && (x = 0); | |
//(y < 0) && (y = 0); | |
return [x, y]; | |
} else { | |
var d = this.zc[zoom]; | |
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999); | |
var x = Math.round(d + ll[0] * this.Bc[zoom]); | |
var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom])); | |
(x > this.Ac[zoom]) && (x = this.Ac[zoom]); | |
(y > this.Ac[zoom]) && (y = this.Ac[zoom]); | |
//(x < 0) && (x = 0); | |
//(y < 0) && (y = 0); | |
return [x, y]; | |
} | |
}; | |
// Convert screen pixel value to lon lat | |
// | |
// - `px` {Array} `[x, y]` array of geographic coordinates. | |
// - `zoom` {Number} zoom level. | |
SphericalMercator.prototype.ll = function(px, zoom) { | |
if (isFloat(zoom)) { | |
var size = this.size * Math.pow(2, zoom); | |
var bc = (size / 360); | |
var cc = (size / (2 * Math.PI)); | |
var zc = size / 2; | |
var g = (px[1] - zc) / -cc; | |
var lon = (px[0] - zc) / bc; | |
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); | |
return [lon, lat]; | |
} else { | |
var g = (px[1] - this.zc[zoom]) / (-this.Cc[zoom]); | |
var lon = (px[0] - this.zc[zoom]) / this.Bc[zoom]; | |
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); | |
return [lon, lat]; | |
} | |
}; | |
// Convert tile xyz value to bbox of the form `[w, s, e, n]` | |
// | |
// - `x` {Number} x (longitude) number. | |
// - `y` {Number} y (latitude) number. | |
// - `zoom` {Number} zoom. | |
// - `tms_style` {Boolean} whether to compute using tms-style. | |
// - `srs` {String} projection for resulting bbox (WGS84|900913). | |
// - `return` {Array} bbox array of values in form `[w, s, e, n]`. | |
SphericalMercator.prototype.bbox = function(x, y, zoom, tms_style, srs) { | |
// Convert xyz into bbox with srs WGS84 | |
if (tms_style) { | |
y = (Math.pow(2, zoom) - 1) - y; | |
} | |
// Use +y to make sure it's a number to avoid inadvertent concatenation. | |
var ll = [x * this.size, (+y + 1) * this.size]; // lower left | |
// Use +x to make sure it's a number to avoid inadvertent concatenation. | |
var ur = [(+x + 1) * this.size, y * this.size]; // upper right | |
var bbox = this.ll(ll, zoom).concat(this.ll(ur, zoom)); | |
// If web mercator requested reproject to 900913. | |
if (srs === '900913') { | |
return this.convert(bbox, '900913'); | |
} else { | |
return bbox; | |
} | |
}; | |
// Convert bbox to xyx bounds | |
// | |
// - `bbox` {Number} bbox in the form `[w, s, e, n]`. | |
// - `zoom` {Number} zoom. | |
// - `tms_style` {Boolean} whether to compute using tms-style. | |
// - `srs` {String} projection of input bbox (WGS84|900913). | |
// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties. | |
SphericalMercator.prototype.xyz = function(bbox, zoom, tms_style, srs) { | |
// If web mercator provided reproject to WGS84. | |
if (srs === '900913') { | |
bbox = this.convert(bbox, 'WGS84'); | |
} | |
var ll = [bbox[0], bbox[1]]; // lower left | |
var ur = [bbox[2], bbox[3]]; // upper right | |
var px_ll = this.px(ll, zoom); | |
var px_ur = this.px(ur, zoom); | |
// Y = 0 for XYZ is the top hence minY uses px_ur[1]. | |
var x = [ Math.floor(px_ll[0] / this.size), Math.floor((px_ur[0] - 1) / this.size) ]; | |
var y = [ Math.floor(px_ur[1] / this.size), Math.floor((px_ll[1] - 1) / this.size) ]; | |
var bounds = { | |
minX: Math.min.apply(Math, x) < 0 ? 0 : Math.min.apply(Math, x), | |
minY: Math.min.apply(Math, y) < 0 ? 0 : Math.min.apply(Math, y), | |
maxX: Math.max.apply(Math, x), | |
maxY: Math.max.apply(Math, y) | |
}; | |
if (tms_style) { | |
var tms = { | |
minY: (Math.pow(2, zoom) - 1) - bounds.maxY, | |
maxY: (Math.pow(2, zoom) - 1) - bounds.minY | |
}; | |
bounds.minY = tms.minY; | |
bounds.maxY = tms.maxY; | |
} | |
return bounds; | |
}; | |
return SphericalMercator; | |
})(); | |
var merc = new SphericalMercator({ | |
size: 256 | |
}); | |
return merc.bbox(x,y,zoom) | |
"""; | |
CREATE TEMP FUNCTION xyz(bbox ARRAY<FLOAT64>, zoom NUMERIC) | |
RETURNS STRUCT<minX NUMERIC,minY NUMERIC,maxX NUMERIC,maxY NUMERIC> | |
LANGUAGE js AS """ | |
var SphericalMercator = (function(){ | |
// Closures including constants and other precalculated values. | |
var cache = {}, | |
EPSLN = 1.0e-10, | |
D2R = Math.PI / 180, | |
R2D = 180 / Math.PI, | |
// 900913 properties. | |
A = 6378137.0, | |
MAXEXTENT = 20037508.342789244; | |
function isFloat(n){ | |
return Number(n) === n && n % 1 !== 0; | |
} | |
// SphericalMercator constructor: precaches calculations | |
// for fast tile lookups. | |
function SphericalMercator(options) { | |
options = options || {}; | |
this.size = options.size || 256; | |
if (!cache[this.size]) { | |
var size = this.size; | |
var c = cache[this.size] = {}; | |
c.Bc = []; | |
c.Cc = []; | |
c.zc = []; | |
c.Ac = []; | |
for (var d = 0; d < 30; d++) { | |
c.Bc.push(size / 360); | |
c.Cc.push(size / (2 * Math.PI)); | |
c.zc.push(size / 2); | |
c.Ac.push(size); | |
size *= 2; | |
} | |
} | |
this.Bc = cache[this.size].Bc; | |
this.Cc = cache[this.size].Cc; | |
this.zc = cache[this.size].zc; | |
this.Ac = cache[this.size].Ac; | |
}; | |
// Convert lon lat to screen pixel value | |
// | |
// - `ll` {Array} `[lon, lat]` array of geographic coordinates. | |
// - `zoom` {Number} zoom level. | |
SphericalMercator.prototype.px = function(ll, zoom) { | |
if (isFloat(zoom)) { | |
var size = this.size * Math.pow(2, zoom); | |
var d = size / 2; | |
var bc = (size / 360); | |
var cc = (size / (2 * Math.PI)); | |
var ac = size; | |
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999); | |
var x = d + ll[0] * bc; | |
var y = d + 0.5 * Math.log((1 + f) / (1 - f)) * -cc; | |
(x > ac) && (x = ac); | |
(y > ac) && (y = ac); | |
//(x < 0) && (x = 0); | |
//(y < 0) && (y = 0); | |
return [x, y]; | |
} else { | |
var d = this.zc[zoom]; | |
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999); | |
var x = Math.round(d + ll[0] * this.Bc[zoom]); | |
var y = Math.round(d + 0.5 * Math.log((1 + f) / (1 - f)) * (-this.Cc[zoom])); | |
(x > this.Ac[zoom]) && (x = this.Ac[zoom]); | |
(y > this.Ac[zoom]) && (y = this.Ac[zoom]); | |
//(x < 0) && (x = 0); | |
//(y < 0) && (y = 0); | |
return [x, y]; | |
} | |
}; | |
// Convert screen pixel value to lon lat | |
// | |
// - `px` {Array} `[x, y]` array of geographic coordinates. | |
// - `zoom` {Number} zoom level. | |
SphericalMercator.prototype.ll = function(px, zoom) { | |
if (isFloat(zoom)) { | |
var size = this.size * Math.pow(2, zoom); | |
var bc = (size / 360); | |
var cc = (size / (2 * Math.PI)); | |
var zc = size / 2; | |
var g = (px[1] - zc) / -cc; | |
var lon = (px[0] - zc) / bc; | |
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); | |
return [lon, lat]; | |
} else { | |
var g = (px[1] - this.zc[zoom]) / (-this.Cc[zoom]); | |
var lon = (px[0] - this.zc[zoom]) / this.Bc[zoom]; | |
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI); | |
return [lon, lat]; | |
} | |
}; | |
// Convert tile xyz value to bbox of the form `[w, s, e, n]` | |
// | |
// - `x` {Number} x (longitude) number. | |
// - `y` {Number} y (latitude) number. | |
// - `zoom` {Number} zoom. | |
// - `tms_style` {Boolean} whether to compute using tms-style. | |
// - `srs` {String} projection for resulting bbox (WGS84|900913). | |
// - `return` {Array} bbox array of values in form `[w, s, e, n]`. | |
SphericalMercator.prototype.bbox = function(x, y, zoom, tms_style, srs) { | |
// Convert xyz into bbox with srs WGS84 | |
if (tms_style) { | |
y = (Math.pow(2, zoom) - 1) - y; | |
} | |
// Use +y to make sure it's a number to avoid inadvertent concatenation. | |
var ll = [x * this.size, (+y + 1) * this.size]; // lower left | |
// Use +x to make sure it's a number to avoid inadvertent concatenation. | |
var ur = [(+x + 1) * this.size, y * this.size]; // upper right | |
var bbox = this.ll(ll, zoom).concat(this.ll(ur, zoom)); | |
// If web mercator requested reproject to 900913. | |
if (srs === '900913') { | |
return this.convert(bbox, '900913'); | |
} else { | |
return bbox; | |
} | |
}; | |
// Convert bbox to xyx bounds | |
// | |
// - `bbox` {Number} bbox in the form `[w, s, e, n]`. | |
// - `zoom` {Number} zoom. | |
// - `tms_style` {Boolean} whether to compute using tms-style. | |
// - `srs` {String} projection of input bbox (WGS84|900913). | |
// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties. | |
SphericalMercator.prototype.xyz = function(bbox, zoom, tms_style, srs) { | |
// If web mercator provided reproject to WGS84. | |
if (srs === '900913') { | |
bbox = this.convert(bbox, 'WGS84'); | |
} | |
var ll = [bbox[0], bbox[1]]; // lower left | |
var ur = [bbox[2], bbox[3]]; // upper right | |
var px_ll = this.px(ll, zoom); | |
var px_ur = this.px(ur, zoom); | |
// Y = 0 for XYZ is the top hence minY uses px_ur[1]. | |
var x = [ Math.floor(px_ll[0] / this.size), Math.floor((px_ur[0] - 1) / this.size) ]; | |
var y = [ Math.floor(px_ur[1] / this.size), Math.floor((px_ll[1] - 1) / this.size) ]; | |
var bounds = { | |
minX: Math.min.apply(Math, x) < 0 ? 0 : Math.min.apply(Math, x), | |
minY: Math.min.apply(Math, y) < 0 ? 0 : Math.min.apply(Math, y), | |
maxX: Math.max.apply(Math, x), | |
maxY: Math.max.apply(Math, y) | |
}; | |
if (tms_style) { | |
var tms = { | |
minY: (Math.pow(2, zoom) - 1) - bounds.maxY, | |
maxY: (Math.pow(2, zoom) - 1) - bounds.minY | |
}; | |
bounds.minY = tms.minY; | |
bounds.maxY = tms.maxY; | |
} | |
return bounds; | |
}; | |
return SphericalMercator; | |
})(); | |
var merc = new SphericalMercator({ | |
size: 256 | |
}); | |
return merc.xyz(bbox,zoom) | |
"""; | |
--Area in Brooklyn not very big (4 tiles at zoom 14. | |
--GENERTE a SERIES OF TILES FOR THE BBOX (minLon,minLat,maxLon,maxLat) at zoom 14 | |
WITH | |
tiles_on_aoi_limits AS (SELECT xyz([-73.955397,40.724201,-73.924341,40.743828],14) as xyzbounds), | |
xes AS (SELECT x FROM UNNEST(GENERATE_ARRAY((SELECT xyzbounds.minX FROM tiles_on_aoi_limits), (SELECT xyzbounds.maxX FROM tiles_on_aoi_limits))) AS x), | |
tiles AS (SELECT x,y,14 as zoom FROM xes,UNNEST(GENERATE_ARRAY((SELECT xyzbounds.minY FROM tiles_on_aoi_limits), (SELECT xyzbounds.maxY FROM tiles_on_aoi_limits))) AS y), | |
--Calculate the bbox for each tile | |
tilebbox AS (SELECT bbox(x,y,zoom) as bbox,x,y,zoom FROM tiles) | |
--Get an array of grometries for all the features intersecting a tile | |
SELECT x,y,zoom,ARRAY_AGG(geom) as geoms FROM `carto-do-public-data.usa_carto.geography_usa_blockgroup_2015`, tilebbox WHERE | |
ST_INTERSECTSBOX(geom,bbox[OFFSET(0)],bbox[OFFSET(1)],bbox[OFFSET(2)],bbox[OFFSET(3)]) | |
GROUP by x,y,zoom |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment