Skip to content

Commit

Permalink
Merge 1c79bd2 into b1ce664
Browse files Browse the repository at this point in the history
  • Loading branch information
Pessimistress committed Mar 1, 2020
2 parents b1ce664 + 1c79bd2 commit 82f78a3
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 26 deletions.
9 changes: 4 additions & 5 deletions docs/layers/s2-layer.md
Expand Up @@ -74,12 +74,11 @@ new S2Layer({});
To use pre-bundled scripts:

```html
<script src="https://bundle.run/s2-geometry@1.2.10"></script>
<script src="https://unpkg.com/deck.gl@^8.0.0/dist.min.js"></script>
<script src="https://unpkg.com/deck.gl@^8.1.0/dist.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@^8.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^8.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/geo-layers@^8.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/core@^8.1.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^8.1.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/geo-layers@^8.1.0/dist.min.js"></script>
```

```js
Expand Down
6 changes: 6 additions & 0 deletions docs/upgrade-guide.md
@@ -1,5 +1,11 @@
# Upgrade Guide

## Upgrading from deck.gl v8.0 to v8.1

### Breaking Changes

- `s2-geometry` is no longer a dependency of `@deck.gl/geo-layers`.

## Upgrading from deck.gl v7.x to v8.0

### Breaking Changes
Expand Down
3 changes: 1 addition & 2 deletions modules/geo-layers/package.json
Expand Up @@ -37,8 +37,7 @@
"math.gl": "^3.1.3",
"@math.gl/web-mercator": "^3.1.3",
"h3-js": "^3.6.0",
"long": "^3.2.0",
"s2-geometry": "^1.2.10"
"long": "^3.2.0"
},
"peerDependencies": {
"@deck.gl/core": "^8.0.0",
Expand Down
144 changes: 144 additions & 0 deletions modules/geo-layers/src/s2-layer/s2-geometry.js
@@ -0,0 +1,144 @@
/*
Adapted from s2-geometry
ISC License (ISC)
Copyright (c) 2012-2016, Jon Atkins <github@jonatkins.com>
Copyright (c) 2016, AJ ONeal <aj@daplie.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

import Long from 'long';

//
// Functional Style
//
const FACE_BITS = 3;
const MAX_LEVEL = 30;
const POS_BITS = 2 * MAX_LEVEL + 1; // 61 (60 bits of data, 1 bit lsb marker)
const RADIAN_TO_DEGREE = 180 / Math.PI;

export function IJToST(ij, order, offsets) {
const maxSize = 1 << order;

return [(ij[0] + offsets[0]) / maxSize, (ij[1] + offsets[1]) / maxSize];
}

function singleSTtoUV(st) {
if (st >= 0.5) {
return (1 / 3.0) * (4 * st * st - 1);
}
return (1 / 3.0) * (1 - 4 * (1 - st) * (1 - st));
}

export function STToUV(st) {
return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
}

export function FaceUVToXYZ(face, [u, v]) {
switch (face) {
case 0:
return [1, u, v];
case 1:
return [-u, 1, v];
case 2:
return [-u, -v, 1];
case 3:
return [-1, -v, -u];
case 4:
return [v, -1, -u];
case 5:
return [v, u, -1];
default:
throw new Error('Invalid face');
}
}

export function XYZToLngLat([x, y, z]) {
const lat = Math.atan2(z, Math.sqrt(x * x + y * y));
const lng = Math.atan2(y, x);

return [lng * RADIAN_TO_DEGREE, lat * RADIAN_TO_DEGREE];
}

export function toHilbertQuadkey(idS) {
let bin = Long.fromString(idS, true, 10).toString(2);

while (bin.length < FACE_BITS + POS_BITS) {
// eslint-disable-next-line prefer-template
bin = '0' + bin;
}

// MUST come AFTER binstr has been left-padded with '0's
const lsbIndex = bin.lastIndexOf('1');
// substr(start, len)
// substring(start, end) // includes start, does not include end
const faceB = bin.substring(0, 3);
// posB will always be a multiple of 2 (or it's invalid)
const posB = bin.substring(3, lsbIndex);
const levelN = posB.length / 2;

const faceS = Long.fromString(faceB, true, 2).toString(10);
let posS = Long.fromString(posB, true, 2).toString(4);

while (posS.length < levelN) {
// eslint-disable-next-line prefer-template
posS = '0' + posS;
}

return `${faceS}/${posS}`;
}

function rotateAndFlipQuadrant(n, point, rx, ry) {
if (ry === 0) {
if (rx === 1) {
point[0] = n - 1 - point[0];
point[1] = n - 1 - point[1];
}

const x = point[0];
point[0] = point[1];
point[1] = x;
}
}

export function FromHilbertQuadKey(hilbertQuadkey) {
const parts = hilbertQuadkey.split('/');
const face = parseInt(parts[0], 10);
const position = parts[1];
const maxLevel = position.length;
const point = [0, 0];
let level;

for (let i = maxLevel - 1; i >= 0; i--) {
level = maxLevel - i;
const bit = position[i];
let rx = 0;
let ry = 0;
if (bit === '1') {
ry = 1;
} else if (bit === '2') {
rx = 1;
ry = 1;
} else if (bit === '3') {
rx = 1;
}

const val = Math.pow(2, level - 1);
rotateAndFlipQuadrant(val, point, rx, ry);

point[0] += val * rx;
point[1] += val * ry;
}

if (face % 2 === 1) {
const t = point[0];
point[0] = point[1];
point[1] = t;
}

return {face, ij: point, level};
}
28 changes: 13 additions & 15 deletions modules/geo-layers/src/s2-layer/s2-utils.js
@@ -1,6 +1,13 @@
// s2-geometry is a pure JavaScript port of Google/Niantic's S2 Geometry library
// which is perfect since it works in the browser.
import {S2} from 's2-geometry';
import {
toHilbertQuadkey,
FromHilbertQuadKey,
IJToST,
STToUV,
FaceUVToXYZ,
XYZToLngLat
} from './s2-geometry';
import Long from 'long';

/**
Expand All @@ -13,17 +20,8 @@ function getIdFromToken(token) {
return Long.fromString(paddedToken, 16);
}

const RADIAN_TO_DEGREE = 180 / Math.PI;
const MAX_RESOLUTION = 100;

/* Adapted from s2-geometry's S2.XYZToLatLng */
function XYZToLngLat([x, y, z]) {
const lat = Math.atan2(z, Math.sqrt(x * x + y * y));
const lng = Math.atan2(y, x);

return [lng * RADIAN_TO_DEGREE, lat * RADIAN_TO_DEGREE];
}

/* Adapted from s2-geometry's S2Cell.getCornerLatLngs */
function getGeoBounds({face, ij, level}) {
const offsets = [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]];
Expand All @@ -48,9 +46,9 @@ function getGeoBounds({face, ij, level}) {
offset[1] += stepJ;
// Cell can be represented by coordinates IJ, ST, UV, XYZ
// http://s2geometry.io/devguide/s2cell_hierarchy#coordinate-systems
const st = S2.IJToST(ij, level, offset);
const uv = S2.STToUV(st);
const xyz = S2.FaceUVToXYZ(face, uv);
const st = IJToST(ij, level, offset);
const uv = STToUV(st);
const xyz = FaceUVToXYZ(face, uv);
const lngLat = XYZToLngLat(xyz);

result[ptIndex++] = lngLat[0];
Expand All @@ -73,7 +71,7 @@ export function getS2QuadKey(token) {
token = getIdFromToken(token);
}
// is Long id
return S2.S2Cell.toHilbertQuadkey(token.toString());
return toHilbertQuadkey(token.toString());
}

/**
Expand All @@ -85,7 +83,7 @@ export function getS2QuadKey(token) {
*/
export function getS2Polygon(token) {
const key = getS2QuadKey(token);
const s2cell = S2.S2Cell.FromHilbertQuadKey(key);
const s2cell = FromHilbertQuadKey(key);

return getGeoBounds(s2cell);
}
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -68,7 +68,8 @@
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-map-gl": "^5.1.0",
"reify": "^0.18.1"
"reify": "^0.18.1",
"s2-geometry": "^1.2.10"
},
"pre-commit": [
"test-fast",
Expand Down
3 changes: 1 addition & 2 deletions scripts/bundle.config.js
Expand Up @@ -17,8 +17,7 @@ const PACKAGE_INFO = require(resolve(PACKAGE_ROOT, 'package.json'));
function getExternals(packageInfo) {
let externals = {
// Hard coded externals
'h3-js': 'h3',
's2-geometry': 's2Geometry'
'h3-js': 'h3'
};
const {peerDependencies = {}} = packageInfo;

Expand Down
11 changes: 10 additions & 1 deletion test/modules/geo-layers/s2-layer.spec.js
Expand Up @@ -76,7 +76,16 @@ test('S2Layer#getS2QuadKey', t => {
});

test('S2Layer#getS2Polygon', t => {
const TEST_TOKENS = ['80858004', '1c', new Long(0, -2138636288, false)];
const TEST_TOKENS = [
'80858004', // face 4
'1c', // face 0
'2c', // face 1
'5b', // face 2
'6b', // face 3
'ab', // face 5
'4/001003',
new Long(0, -2138636288, false)
];

for (const token of TEST_TOKENS) {
const polygon = getS2Polygon(token);
Expand Down

0 comments on commit 82f78a3

Please sign in to comment.