Compare commits

..

78 Commits

Author SHA1 Message Date
AJ ONeal 7a10f5b562 rebrand 2018-04-10 09:57:02 -06:00
AJ ONeal e7ecb48449 auto-update banner 2016-12-30 02:41:07 -07:00
AJ ONeal 441c693bad auto-update ad 2016-12-30 02:23:27 -07:00
AJ ONeal e67bb19828 Update README.md 2016-11-25 10:39:15 -07:00
AJ ONeal df6d38b5ad v1.2.9 2016-08-17 23:35:26 -06:00
AJ ONeal 3a962c1956 further cleanup and testing for #16 2016-08-17 23:35:20 -06:00
AJ ONeal 5495dd0a53 Merge pull request #16 from lopper/master
support converting from HilbertQuadkey to s2Cell.
2016-08-17 23:02:59 -06:00
Patrick d28f0a6d3f add support to convert from HilbertQuadkey to s2Cell. 2016-08-16 22:08:21 -07:00
Patrick a4e97ba0a9 fix issue with FromLatLng so that it supports latLng cooridinates that involve 0. 2016-08-16 22:00:34 -07:00
AJ ONeal 0cc25036e5 v1.2.8 2016-08-16 13:19:30 -06:00
AJ ONeal a11cd4e920 merge #14 and #15 2016-08-16 13:18:33 -06:00
AJ ONeal 42a9eaf1fc Merge pull request #15 from breadboardllc/module-exporting-fixes
Fix export so it works with `module.exports` & `window`
2016-08-16 13:10:27 -06:00
David Murdoch 4e631500ea Don't overwrite L if it exists in export scope
This makes the module.exports fix more inline with previous behavior.
2016-08-16 14:56:14 -04:00
David Murdoch 8aa84ebf93 Fix export so it works w/ `module.exports` & `window`
Fixes Issue #13.

This also fixes a similar issue where this library is used via browserify/webpack in the browser (or other environments where `window` is defined). Previously, `var S2 = require("s1-geometry").S2;`, would result in `window.S2` being defined but `S2` remaining undefined. This changes the behavior to prefer exporting via `module.exports`, if available, and falling back to `window` (or whatever `this` is in the module's imported scope --  which is usually `window`). Additionally, this keeps the library from polluting the global scope unnecessarily.
2016-08-16 14:52:15 -04:00
Jason Hunter 9fdcc00739 flip module.exports and window check for this lib to work on nw.js and electron per Issue #13 2016-08-14 22:39:50 -04:00
AJ ONeal 7180db1be6 explicit br 2016-08-03 18:00:49 -06:00
AJ ONeal c6ee1ee508 update formatting 2016-08-03 17:59:48 -06:00
AJ ONeal 4249d3bfa6 add orientation 2016-08-03 17:54:58 -06:00
AJ ONeal 60d01395ef add orientation 2016-08-03 17:53:38 -06:00
AJ ONeal 2e7b12e212 add more of api 2016-08-03 17:48:24 -06:00
AJ ONeal 1a9c95e2c6 bump 2016-08-03 17:44:03 -06:00
AJ ONeal b3f5bbd27f Merge branch 'master' of github.com:Daplie/s2-geometry.js 2016-08-03 17:43:49 -06:00
AJ ONeal f96e16c6bf update docs and aliases 2016-08-03 17:43:35 -06:00
AJ ONeal d1f19ef616 Update README.md 2016-08-03 16:56:56 -06:00
AJ ONeal 191e83fe40 Update README.md 2016-08-03 16:56:09 -06:00
AJ ONeal 4c4154a288 update table 2016-08-03 16:54:02 -06:00
AJ ONeal be9112f2ad add table with faces 2016-08-03 16:48:49 -06:00
AJ ONeal bb664af25b special test case for @vekexasia #1 2016-08-03 10:27:24 -06:00
AJ ONeal 98fa79b6bd special test case for @Skeec for #11 2016-08-03 09:24:49 -06:00
AJ ONeal d5524f7b74 test for @DVassilev on #8 and minor updates 2016-08-03 09:05:23 -06:00
AJ ONeal afe681240b test against golang/geo/s2 2016-08-03 01:20:48 -06:00
AJ ONeal 197e07a605 bump 2016-08-03 00:33:32 -06:00
AJ ONeal 60186d007f fix #8 #6 2016-08-03 00:33:20 -06:00
AJ ONeal b7f7c6df1c fix #9 2016-08-03 00:23:22 -06:00
AJ ONeal a1da97514e bump 2016-08-03 00:04:51 -06:00
AJ ONeal 68274eb677 use correct starting square for face #6 #8 #9 2016-08-03 00:04:26 -06:00
AJ ONeal e04b0b0ecd move to Daplie 2016-07-30 11:06:23 -04:00
AJ ONeal 4a7b8819c9 bump 2016-07-30 00:51:34 -04:00
AJ ONeal c0c80ca0f8 show xyz value also 2016-07-29 13:50:20 -04:00
AJ ONeal c094e1def7 add step-by-step value debugging 2016-07-29 13:49:32 -04:00
AJ ONeal d18e4906c5 export some internals for debugging 2016-07-29 13:48:41 -04:00
AJ ONeal b45bb89117 bump 2016-07-29 10:37:52 -04:00
AJ ONeal d7aa5c136a thoroughly test partial fix of #6 2016-07-29 10:37:43 -04:00
AJ ONeal 0d8eec3d04 add expected location values from s2geometry-node 2016-07-29 09:53:03 -04:00
AJ ONeal 113e375824 #6 fix padding issue converting from Hilbert Quadkey to CellId 2016-07-29 09:31:39 -04:00
AJ ONeal e461dcfd5c #6 fix padding issue converting from CellId to Hilbert Quadkey 2016-07-29 09:07:52 -04:00
AJ ONeal 51b549de38 Merge branch 'master' of github.com:coolaj86/s2-geometry-javascript 2016-07-28 09:04:59 -04:00
AJ ONeal c275019aec magic 3 -> S2.FACE_BITS 2016-07-28 09:04:22 -04:00
AJ ONeal 2796829356 add 'used by' section 2016-07-28 05:23:39 -04:00
AJ ONeal 6252e4e9e9 syntax, comments with output 2016-07-28 05:21:24 -04:00
AJ ONeal 65d94c9141 add javascript styling 2016-07-28 05:17:53 -04:00
AJ ONeal d344d81611 bump 2016-07-28 05:06:41 -04:00
AJ ONeal 54bd875e3e bugfix #3 pad face binary string 2016-07-28 05:06:23 -04:00
AJ ONeal e3619d963a bump 2016-07-28 02:25:54 -04:00
AJ ONeal c65239853a update docs and comments 2016-07-28 02:25:17 -04:00
AJ ONeal 103d955300 add next(), prev(), and other helpers 2016-07-28 02:13:05 -04:00
AJ ONeal 7ca690db14 bump 2016-07-28 00:38:01 -04:00
AJ ONeal f4768f7a60 refactor and test for getting latlng 2016-07-28 00:37:43 -04:00
AJ ONeal a60945a4b9 Merge branch 'master' of github.com:coolaj86/s2-geometry-javascript 2016-07-27 20:47:25 -04:00
AJ ONeal 0d828b28b0 add .gitignore 2016-07-27 20:46:04 -04:00
AJ ONeal f911efcdab import dcodeIO.Long in browser 2016-07-27 20:45:56 -04:00
AJ ONeal b5c41f04e4 add id conversion 2016-07-27 20:41:04 -04:00
AJ ONeal 51d45cd809 chimney 2016-07-27 08:04:16 -04:00
AJ ONeal 5179a6b31d chimney 2016-07-27 07:56:55 -04:00
AJ ONeal a73556c7a3 how walk should progress 2016-07-26 02:36:42 -04:00
AJ ONeal 7014dd3632 how walk should progress 2016-07-26 02:31:39 -04:00
AJ ONeal 35ceafae3d cellid.js -> latlng2cell.js 2016-07-26 01:48:00 -04:00
AJ ONeal 74149b89cb v1.0.0 2016-07-26 00:44:38 -04:00
AJ ONeal 544fc8a48d v1.0.0 2016-07-26 00:42:53 -04:00
AJ ONeal 54fccf662e Merge pull request #2 from coolaj86/patch-1
Mention what you need for Pokemon GO clients
2016-07-25 22:59:39 -04:00
AJ ONeal ce2e39a090 Merge pull request #1 from coolaj86/patch-2
Make the code work!
2016-07-25 22:58:46 -04:00
AJ ONeal 8c17bc9e4f explicit radix 2016-07-25 22:56:23 -04:00
AJ ONeal 61b3ea3f37 don't redefine L, don't undefine DEG_TO_RAD
Moved the consts to below function definition.
2016-07-25 22:50:45 -04:00
AJ ONeal eb224e01aa typo fix `uv` -> `var uv` 2016-07-25 22:06:43 -04:00
AJ ONeal 8c15c5a1d7 typo fix windows -> window 2016-07-25 22:05:28 -04:00
AJ ONeal e3a7a4519b add relevant L parts 2016-07-25 21:53:02 -04:00
AJ ONeal 5a589ea596 Update README.md 2016-07-25 21:40:26 -04:00
AJ ONeal 020b5ab34e Add "what you need to know for Pokemon GO clients" 2016-07-25 21:36:32 -04:00
21 changed files with 3281 additions and 68 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/
.*.*sw*

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
Jon Atkins <github@jonatkins.com> (http://www.jonatkins.com/)
AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com)

8
LICENSE Normal file
View File

@ -0,0 +1,8 @@
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.

211
README.md
View File

@ -1,19 +1,218 @@
s2-geometry-javascript s2-geometry (JavaScript/ES5.1)
====================== ======================
Porting Google's S2 Geometry Library to Javascript | Sponsored by [ppl](https://ppl.family)
A pure JavaScript/ES5.1 port of Google/Niantic's S2 Geometry library (as used by **Ingress**, **Pokemon GO**)
Currently contains basic support for S2Cell Currently contains basic support for S2Cell
<table>
<tr>
<td></td>
<td>
Face 2
<br>
Orientation A
<a href="http://i.imgur.com/SODO4bT.jpg" target="_face2"><img src="http://i.imgur.com/SODO4bTt.jpg" title="Face 2" alt="Face 2"></a>
<br>
The North Pole<br>(and Canada / Europe)
</td>
<td></td>
</tr>
<tr>
<td>
Face 0
<br>
Orientation A
<a href="http://i.imgur.com/dLI5Zd1.jpg" target="_face0"><img src="http://i.imgur.com/dLI5Zd1t.jpg" title="Face 0" alt="Face 0"></a>
<br>
Africa
</td>
<td>
Face 1
<br>
Orientation D
<a href="http://i.imgur.com/duTLDTV.jpg" target="_face1"><img src="http://i.imgur.com/duTLDTVt.jpg" title="Face 1" alt="Face 1"></a>
<br>
Asia
</td>
<td>
Face 3
<br>
Orientation D
<a href="http://i.imgur.com/6Ho35Tc.jpg" target="_face3"><img src="http://i.imgur.com/6Ho35Tct.jpg" title="Face 3" alt="Face 3"></a>
<br>
Nothing<br>(and Australia)
</td>
<td>
Face 4
<br>
Orientation A
<a href="http://i.imgur.com/3IBAfqj.jpg" target="_face4"><img src="http://i.imgur.com/3IBAfqjt.jpg" title="Face 4" alt="Face 4"></a>
<br>
The Americas<br>(and Provo, UT)
</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>
Face 5
<br>
Orientation D
<a href="http://i.imgur.com/HZCBvgy.jpg" target="_face5"><img src="http://i.imgur.com/HZCBvgyt.jpg" title="Face 5" alt="Face 5"></a>
<br>
Antarctica
</td>
</tr>
</table>
Where is this being used?
---------------------
* [pokemap-webapp](https://github.com/Daplie/pokemap-webapp)
* [node-pokemap](https://github.com/Daplie/node-pokemap)
* [Pokemon-GO-node-api](https://github.com/Daplie/Pokemon-GO-node-api)
Simple Examples Simple Examples
--------------- ---------------
```javascript ```javascript
var cell = S2.S2Cell.FromLatLng({ lat: 40.2574448, lng: -111.7089464 }, 15); 'use strict';
cell.getNeighbors(); // [ cellLeft, cellDown, cellRight, cellUp ] var S2 = require('s2-geometry').S2;
cell.getLatLng(); // { lat: 40.2574448, lng: -111.7089464 } var lat = 40.2574448;
var lng = -111.7089464;
var level = 15;
//
// Convert from Lat / Lng
//
var key = S2.latLngToKey(lat, lng, level);
// '4/032212303102210'
//
// Convert between Hilbert Curve Quadtree Key and S2 Cell Id
//
var id = S2.keyToId(key);
// '9749618446378729472'
var key = S2.idToKey(id);
// '9749618446378729472'
//
// Convert between Quadkey and Id
//
var latlng = S2.keyToLatLng(key);
var latlng = S2.idToLatLng(id);
//
// Neighbors
//
var neighbors = S2.latLngToNeighborKeys(lat, lng, level);
// [ keyLeft, keyDown, keyRight, keyUp ]
//
// Previous, Next, and Step
//
var nextKey = S2.nextKey(key);
var prevKey = S2.prevKey(key);
var backTenKeys = S2.stepKey(key, -10);
``` ```
More details and examples to come later. Previous and Next
-----------------
You can get the previous and next S2CellId from any given Key:
1. Convert from Lat/Lng to Key (Face and Hilbert Curve Quadtree)
2. Get the Previous or Next Key
3. Convert the Key to an Id (uint64 string)
```javascript
var key = S2.latLngToKey(40.2574448, -111.7089464, 15); // '4/032212303102210'
var id = S2.keyToId(key); // '9749618446378729472'
var nextKey = S2.nextKey(key);
var nextId = S2.keyToId(nextKey);
var prevKey = S2.prevKey(key);
var prevId = S2.keyToId(prevKey);
var backTenKeys = S2.stepKey(key, -10);
// See it
console.log(prevKey); // '4/032212303102203'
console.log(key); // '4/032212303102210'
console.log(nextKey); // '4/032212303102211'
console.log(nextId);
```
convert Cell Id to Hilbert Curve Quad Tree
------------------
Convert from base 10 (decimal) `S2 Cell Id` to base 4 `quadkey` (aka hilbert curve quadtree id)
Example '4/032212303102210' becomes '9749618446378729472'
```javascript
'use strict';
var quadkey = '4/032212303102210'
var parts = quadkey.split('/');
var face = parts[0]; // 4
var position = parts[1]; // '032212303102210';
var level = '032212303102210'.length; // 15
var cellId = S2.facePosLevelToId(face, position, level);
console.log(cellId);
```
Convert from hilbert quadtree id to s2 cell id:
Example '9749618446378729472' becomes '4/032212303102210'
```javascript
'use strict';
var cellId = '9749618446378729472';
var hilbertQuadkey = S2.idToKey(cellId);
console.log(hilbertQuadkey);
```
Convert Key and Id to LatLng
---------------------
```javascript
var latlng = S2.keyToLatLng('4/032212303102210');
var latlng = S2.idToLatLng('9749618446378729472');
```

39
bower.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "s2-geometry",
"description": "A pure JavaScript/ES5.1 port of Google/Niantic's S2 Geometry library (used by Ingress, Pokemon GO)",
"main": "src/s2geometry.js",
"authors": [
"AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)"
],
"license": "ISC",
"keywords": [
"s2",
"geometry",
"s2geometry",
"Niantic",
"Ingress",
"Pokemon",
"GO",
"PokemonGO",
"cellid",
"s2cell",
"s2cellid",
"latitude",
"longitude",
"lat",
"lng"
],
"homepage": "https://github.com/coolaj86/s2-geometry-javascript",
"moduleType": [
"globals",
"node"
],
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"version": "1.2.9"
}

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "s2-geometry",
"version": "1.2.10",
"description": "A pure JavaScript/ES5.1 port of Google/Niantic's S2 Geometry library (used by Ingress, Pokemon GO)",
"main": "src/s2geometry.js",
"scripts": {
"test": "node tests/latlng2cell.js"
},
"repository": {
"type": "git",
"url": "git+https://git.coolaj86.com/coolaj86/s2-geometry.js.git"
},
"keywords": [
"s2",
"geometry",
"s2geometry",
"Niantic",
"Ingress",
"Pokemon",
"GO",
"PokemonGO",
"cellid",
"s2cell",
"s2cellid",
"latitude",
"longitude",
"lat",
"lng"
],
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
"license": "(MIT or Apache-2 or ISC)",
"bugs": {
"url": "https://git.coolaj86.com/coolaj86/s2-geometry.js/issues"
},
"homepage": "https://git.coolaj86.com/coolaj86/s2-geometry.js#readme",
"dependencies": {
"long": "^3.2.0"
}
}

View File

@ -22,13 +22,55 @@
// - i,j: they always use 30 bits, adjusting as needed. we use 0 to (1<<level)-1 instead // - i,j: they always use 30 bits, adjusting as needed. we use 0 to (1<<level)-1 instead
// (so GetSizeIJ for a cell is always 1) // (so GetSizeIJ for a cell is always 1)
(function() { (function (exports) {
'use strict';
window.S2 = {}; var S2 = exports.S2 = { L: {} };
S2.L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
var lat = parseFloat(rawLat, 10);
var lng = parseFloat(rawLng, 10);
var LatLngToXYZ = function(latLng) { if (isNaN(lat) || isNaN(lng)) {
var d2r = L.LatLng.DEG_TO_RAD; throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
}
if (noWrap !== true) {
lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
}
return { lat: lat, lng: lng };
};
S2.L.LatLng.DEG_TO_RAD = Math.PI / 180;
S2.L.LatLng.RAD_TO_DEG = 180 / Math.PI;
/*
S2.LatLngToXYZ = function(latLng) {
// http://stackoverflow.com/questions/8981943/lat-long-to-x-y-z-position-in-js-not-working
var lat = latLng.lat;
var lon = latLng.lng;
var DEG_TO_RAD = Math.PI / 180.0;
var phi = lat * DEG_TO_RAD;
var theta = lon * DEG_TO_RAD;
var cosLat = Math.cos(phi);
var sinLat = Math.sin(phi);
var cosLon = Math.cos(theta);
var sinLon = Math.sin(theta);
var rad = 500.0;
return [
rad * cosLat * cosLon
, rad * cosLat * sinLon
, rad * sinLat
];
};
*/
S2.LatLngToXYZ = function(latLng) {
var d2r = S2.L.LatLng.DEG_TO_RAD;
var phi = latLng.lat*d2r; var phi = latLng.lat*d2r;
var theta = latLng.lng*d2r; var theta = latLng.lng*d2r;
@ -38,13 +80,13 @@ var LatLngToXYZ = function(latLng) {
return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)]; return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)];
}; };
var XYZToLatLng = function(xyz) { S2.XYZToLatLng = function(xyz) {
var r2d = L.LatLng.RAD_TO_DEG; var r2d = S2.L.LatLng.RAD_TO_DEG;
var lat = Math.atan2(xyz[2], Math.sqrt(xyz[0]*xyz[0]+xyz[1]*xyz[1])); var lat = Math.atan2(xyz[2], Math.sqrt(xyz[0]*xyz[0]+xyz[1]*xyz[1]));
var lng = Math.atan2(xyz[1], xyz[0]); var lng = Math.atan2(xyz[1], xyz[0]);
return L.latLng(lat*r2d, lng*r2d); return S2.L.LatLng(lat*r2d, lng*r2d);
}; };
var largestAbsComponent = function(xyz) { var largestAbsComponent = function(xyz) {
@ -76,28 +118,28 @@ var faceXYZToUV = function(face,xyz) {
case 3: u = xyz[2]/xyz[0]; v = xyz[1]/xyz[0]; break; case 3: u = xyz[2]/xyz[0]; v = xyz[1]/xyz[0]; break;
case 4: u = xyz[2]/xyz[1]; v = -xyz[0]/xyz[1]; break; case 4: u = xyz[2]/xyz[1]; v = -xyz[0]/xyz[1]; break;
case 5: u = -xyz[1]/xyz[2]; v = -xyz[0]/xyz[2]; break; case 5: u = -xyz[1]/xyz[2]; v = -xyz[0]/xyz[2]; break;
default: throw {error: 'Invalid face'}; break; default: throw {error: 'Invalid face'};
} }
return [u,v]; return [u,v];
} };
var XYZToFaceUV = function(xyz) { S2.XYZToFaceUV = function(xyz) {
var face = largestAbsComponent(xyz); var face = largestAbsComponent(xyz);
if (xyz[face] < 0) { if (xyz[face] < 0) {
face += 3; face += 3;
} }
uv = faceXYZToUV (face,xyz); var uv = faceXYZToUV (face,xyz);
return [face, uv]; return [face, uv];
}; };
var FaceUVToXYZ = function(face,uv) { S2.FaceUVToXYZ = function(face,uv) {
var u = uv[0]; var u = uv[0];
var v = uv[1]; var v = uv[1];
@ -112,35 +154,32 @@ var FaceUVToXYZ = function(face,uv) {
} }
}; };
var singleSTtoUV = function(st) {
var STToUV = function(st) { if (st >= 0.5) {
var singleSTtoUV = function(st) { return (1/3.0) * (4*st*st - 1);
if (st >= 0.5) { } else {
return (1/3.0) * (4*st*st - 1); return (1/3.0) * (1 - (4*(1-st)*(1-st)));
} else {
return (1/3.0) * (1 - (4*(1-st)*(1-st)));
}
} }
};
S2.STToUV = function(st) {
return [singleSTtoUV(st[0]), singleSTtoUV(st[1])]; return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
}; };
var singleUVtoST = function(uv) {
var UVToST = function(uv) { if (uv >= 0) {
var singleUVtoST = function(uv) { return 0.5 * Math.sqrt (1 + 3*uv);
if (uv >= 0) { } else {
return 0.5 * Math.sqrt (1 + 3*uv); return 1 - 0.5 * Math.sqrt (1 - 3*uv);
} else {
return 1 - 0.5 * Math.sqrt (1 - 3*uv);
}
} }
};
S2.UVToST = function(uv) {
return [singleUVtoST(uv[0]), singleUVtoST(uv[1])]; return [singleUVtoST(uv[0]), singleUVtoST(uv[1])];
}; };
var STToIJ = function(st,order) { S2.STToIJ = function(st,order) {
var maxSize = (1<<order); var maxSize = (1<<order);
var singleSTtoIJ = function(st) { var singleSTtoIJ = function(st) {
@ -152,21 +191,45 @@ var STToIJ = function(st,order) {
}; };
var IJToST = function(ij,order,offsets) { S2.IJToST = function(ij,order,offsets) {
var maxSize = (1<<order); var maxSize = (1<<order);
return [ return [
(ij[0]+offsets[0])/maxSize, (ij[0]+offsets[0])/maxSize,
(ij[1]+offsets[1])/maxSize (ij[1]+offsets[1])/maxSize
]; ];
};
var rotateAndFlipQuadrant = function(n, point, rx, ry)
{
var newX, newY;
if(ry == 0)
{
if(rx == 1){
point.x = n - 1 - point.x;
point.y = n - 1 - point.y
}
var x = point.x;
point.x = point.y
point.y = x;
}
} }
// hilbert space-filling curve // hilbert space-filling curve
// based on http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves // based on http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
// note: rather then calculating the final integer hilbert position, we just return the list of quads // note: rather then calculating the final integer hilbert position, we just return the list of quads
// this ensures no precision issues whth large orders (S3 cell IDs use up to 30), and is more // this ensures no precision issues whth large orders (S3 cell IDs use up to 30), and is more
// convenient for pulling out the individual bits as needed later // convenient for pulling out the individual bits as needed later
var pointToHilbertQuadList = function(x,y,order) { var pointToHilbertQuadList = function(x,y,order,face) {
var hilbertMap = { var hilbertMap = {
'a': [ [0,'d'], [1,'a'], [3,'b'], [2,'a'] ], 'a': [ [0,'d'], [1,'a'], [3,'b'], [2,'a'] ],
'b': [ [2,'b'], [1,'b'], [3,'a'], [0,'c'] ], 'b': [ [2,'b'], [1,'b'], [3,'a'], [0,'c'] ],
@ -174,7 +237,10 @@ var pointToHilbertQuadList = function(x,y,order) {
'd': [ [0,'a'], [3,'c'], [1,'d'], [2,'d'] ] 'd': [ [0,'a'], [3,'c'], [1,'d'], [2,'d'] ]
}; };
var currentSquare='a'; if ('number' !== typeof face) {
console.warn(new Error("called pointToHilbertQuadList without face value, defaulting to '0'").stack);
}
var currentSquare = (face % 2) ? 'd' : 'a';
var positions = []; var positions = [];
for (var i=order-1; i>=0; i--) { for (var i=order-1; i>=0; i--) {
@ -194,28 +260,86 @@ var pointToHilbertQuadList = function(x,y,order) {
return positions; return positions;
}; };
// S2Cell class // S2Cell class
S2.S2Cell = function(){}; S2.S2Cell = function(){};
S2.S2Cell.FromHilbertQuadKey = function(hilbertQuadkey) {
var parts = hilbertQuadkey.split('/');
var face = parseInt(parts[0]);
var position = parts[1];
var maxLevel = position.length;
var point = {
x : 0,
y: 0
};
var i;
var level;
var bit;
var rx, ry;
var val;
for(i = maxLevel - 1; i >= 0; i--) {
level = maxLevel - i;
bit = position[i];
rx = 0;
ry = 0;
if (bit === '1') {
ry = 1;
}
else if (bit === '2') {
rx = 1;
ry = 1;
}
else if (bit === '3') {
rx = 1;
}
val = Math.pow(2, level - 1);
rotateAndFlipQuadrant(val, point, rx, ry);
point.x += val * rx;
point.y += val * ry;
}
if (face % 2 === 1) {
var t = point.x;
point.x = point.y;
point.y = t;
}
return S2.S2Cell.FromFaceIJ(parseInt(face), [point.x, point.y], level);
};
//static method to construct //static method to construct
S2.S2Cell.FromLatLng = function(latLng,level) { S2.S2Cell.FromLatLng = function(latLng, level) {
if ((!latLng.lat && latLng.lat !== 0) || (!latLng.lng && latLng.lng !== 0)) {
throw new Error("Pass { lat: lat, lng: lng } to S2.S2Cell.FromLatLng");
}
var xyz = S2.LatLngToXYZ(latLng);
var xyz = LatLngToXYZ(latLng); var faceuv = S2.XYZToFaceUV(xyz);
var st = S2.UVToST(faceuv[1]);
var faceuv = XYZToFaceUV(xyz); var ij = S2.STToIJ(st,level);
var st = UVToST(faceuv[1]);
var ij = STToIJ(st,level);
return S2.S2Cell.FromFaceIJ (faceuv[0], ij, level); return S2.S2Cell.FromFaceIJ (faceuv[0], ij, level);
return result;
}; };
/*
S2.faceIjLevelToXyz = function (face, ij, level) {
var st = S2.IJToST(ij, level, [0.5, 0.5]);
var uv = S2.STToUV(st);
var xyz = S2.FaceUVToXYZ(face, uv);
return S2.XYZToLatLng(xyz);
return xyz;
};
*/
S2.S2Cell.FromFaceIJ = function(face,ij,level) { S2.S2Cell.FromFaceIJ = function(face,ij,level) {
var cell = new S2.S2Cell(); var cell = new S2.S2Cell();
cell.face = face; cell.face = face;
@ -231,11 +355,11 @@ S2.S2Cell.prototype.toString = function() {
}; };
S2.S2Cell.prototype.getLatLng = function() { S2.S2Cell.prototype.getLatLng = function() {
var st = IJToST(this.ij,this.level, [0.5,0.5]); var st = S2.IJToST(this.ij,this.level, [0.5,0.5]);
var uv = STToUV(st); var uv = S2.STToUV(st);
var xyz = FaceUVToXYZ(this.face, uv); var xyz = S2.FaceUVToXYZ(this.face, uv);
return XYZToLatLng(xyz); return S2.XYZToLatLng(xyz);
}; };
S2.S2Cell.prototype.getCornerLatLngs = function() { S2.S2Cell.prototype.getCornerLatLngs = function() {
@ -248,22 +372,32 @@ S2.S2Cell.prototype.getCornerLatLngs = function() {
]; ];
for (var i=0; i<4; i++) { for (var i=0; i<4; i++) {
var st = IJToST(this.ij, this.level, offsets[i]); var st = S2.IJToST(this.ij, this.level, offsets[i]);
var uv = STToUV(st); var uv = S2.STToUV(st);
var xyz = FaceUVToXYZ(this.face, uv); var xyz = S2.FaceUVToXYZ(this.face, uv);
result.push ( XYZToLatLng(xyz) ); result.push ( S2.XYZToLatLng(xyz) );
} }
return result; return result;
}; };
S2.S2Cell.prototype.getFaceAndQuads = function() { S2.S2Cell.prototype.getFaceAndQuads = function () {
var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level); var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
return [this.face,quads]; return [this.face,quads];
}; };
S2.S2Cell.prototype.toHilbertQuadkey = function () {
var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
return this.face.toString(10) + '/' + quads.join('');
};
S2.latLngToNeighborKeys = S2.S2Cell.latLngToNeighborKeys = function (lat, lng, level) {
return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).getNeighbors().map(function (cell) {
return cell.toHilbertQuadkey();
});
};
S2.S2Cell.prototype.getNeighbors = function() { S2.S2Cell.prototype.getNeighbors = function() {
var fromFaceIJWrap = function(face,ij,level) { var fromFaceIJWrap = function(face,ij,level) {
@ -276,14 +410,14 @@ S2.S2Cell.prototype.getNeighbors = function() {
// with the assumption that they're only a little past the borders we can just take the points as // with the assumption that they're only a little past the borders we can just take the points as
// just beyond the cube face, project to XYZ, then re-create FaceUV from the XYZ vector // just beyond the cube face, project to XYZ, then re-create FaceUV from the XYZ vector
var st = IJToST(ij,level,[0.5,0.5]); var st = S2.IJToST(ij,level,[0.5,0.5]);
var uv = STToUV(st); var uv = S2.STToUV(st);
var xyz = FaceUVToXYZ(face,uv); var xyz = S2.FaceUVToXYZ(face,uv);
var faceuv = XYZToFaceUV(xyz); var faceuv = S2.XYZToFaceUV(xyz);
face = faceuv[0]; face = faceuv[0];
uv = faceuv[1]; uv = faceuv[1];
st = UVToST(uv); st = S2.UVToST(uv);
ij = STToIJ(st,level); ij = S2.STToIJ(st,level);
return S2.S2Cell.FromFaceIJ (face, ij, level); return S2.S2Cell.FromFaceIJ (face, ij, level);
} }
}; };
@ -303,5 +437,158 @@ S2.S2Cell.prototype.getNeighbors = function() {
}; };
//
// Functional Style
//
S2.FACE_BITS = 3;
S2.MAX_LEVEL = 30;
S2.POS_BITS = (2 * S2.MAX_LEVEL) + 1; // 61 (60 bits of data, 1 bit lsb marker)
})(); S2.facePosLevelToId = S2.S2Cell.facePosLevelToId = S2.fromFacePosLevel = function (faceN, posS, levelN) {
var Long = exports.dcodeIO && exports.dcodeIO.Long || require('long');
var faceB;
var posB;
var bin;
if (!levelN) {
levelN = posS.length;
}
if (posS.length > levelN) {
posS = posS.substr(0, levelN);
}
// 3-bit face value
faceB = Long.fromString(faceN.toString(10), true, 10).toString(2);
while (faceB.length < S2.FACE_BITS) {
faceB = '0' + faceB;
}
// 60-bit position value
posB = Long.fromString(posS, true, 4).toString(2);
while (posB.length < (2 * levelN)) {
posB = '0' + posB;
}
bin = faceB + posB;
// 1-bit lsb marker
bin += '1';
// n-bit padding to 64-bits
while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
bin += '0';
}
return Long.fromString(bin, true, 2).toString(10);
};
S2.keyToId = S2.S2Cell.keyToId
= S2.toId = S2.toCellId = S2.fromKey
= function (key) {
var parts = key.split('/');
return S2.fromFacePosLevel(parts[0], parts[1], parts[1].length);
};
S2.idToKey = S2.S2Cell.idToKey
= S2.S2Cell.toKey = S2.toKey
= S2.fromId = S2.fromCellId
= S2.S2Cell.toHilbertQuadkey = S2.toHilbertQuadkey
= function (idS) {
var Long = exports.dcodeIO && exports.dcodeIO.Long || require('long');
var bin = Long.fromString(idS, true, 10).toString(2);
while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
bin = '0' + bin;
}
// MUST come AFTER binstr has been left-padded with '0's
var lsbIndex = bin.lastIndexOf('1');
// substr(start, len)
// substring(start, end) // includes start, does not include end
var faceB = bin.substring(0, 3);
// posB will always be a multiple of 2 (or it's invalid)
var posB = bin.substring(3, lsbIndex);
var levelN = posB.length / 2;
var faceS = Long.fromString(faceB, true, 2).toString(10);
var posS = Long.fromString(posB, true, 2).toString(4);
while (posS.length < levelN) {
posS = '0' + posS;
}
return faceS + '/' + posS;
};
S2.keyToLatLng = S2.S2Cell.keyToLatLng = function (key) {
var cell2 = S2.S2Cell.FromHilbertQuadKey(key);
return cell2.getLatLng();
};
S2.idToLatLng = S2.S2Cell.idToLatLng = function (id) {
var key = S2.idToKey(id);
return S2.keyToLatLng(key);
};
S2.S2Cell.latLngToKey = S2.latLngToKey
= S2.latLngToQuadkey = function (lat, lng, level) {
if (isNaN(level) || level < 1 || level > 30) {
throw new Error("'level' is not a number between 1 and 30 (but it should be)");
}
// TODO
//
// S2.idToLatLng(id)
// S2.keyToLatLng(key)
// S2.nextFace(key) // prevent wrapping on nextKey
// S2.prevFace(key) // prevent wrapping on prevKey
//
// .toKeyArray(id) // face,quadtree
// .toKey(id) // hilbert
// .toPoint(id) // ij
// .toId(key) // uint64 (as string)
// .toLong(key) // long.js
// .toLatLng(id) // object? or array?, or string (with comma)?
//
// maybe S2.HQ.x, S2.GPS.x, S2.CI.x?
return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).toHilbertQuadkey();
};
S2.stepKey = function (key, num) {
var Long = exports.dcodeIO && exports.dcodeIO.Long || require('long');
var parts = key.split('/');
var faceS = parts[0];
var posS = parts[1];
var level = parts[1].length;
var posL = Long.fromString(posS, true, 4);
// TODO handle wrapping (0 === pos + 1)
// (only on the 12 edges of the globe)
var otherL;
if (num > 0) {
otherL = posL.add(Math.abs(num));
}
else if (num < 0) {
otherL = posL.subtract(Math.abs(num));
}
var otherS = otherL.toString(4);
if ('0' === otherS) {
console.warning(new Error("face/position wrapping is not yet supported"));
}
while (otherS.length < level) {
otherS = '0' + otherS;
}
return faceS + '/' + otherS;
};
S2.S2Cell.prevKey = S2.prevKey = function (key) {
return S2.stepKey(key, -1);
};
S2.S2Cell.nextKey = S2.nextKey = function (key) {
return S2.stepKey(key, 1);
};
})('undefined' !== typeof module ? module.exports : window);

84
tests/conversions.js Normal file
View File

@ -0,0 +1,84 @@
'use strict';
var Long = require('long');
var s2node = require('s2geometry-node');
var S2 = require('../src/s2geometry.js').S2;
var tests = [
{ 'name': 'Provo, UT'
, 'lat': 40.2574448
, 'lng': -111.7089464
, 'key': ''
, 'id': ''
}
, { 'name': 'Startup Building'
, 'lat': 40.2262363
, 'lng': -111.6630927
, 'key': ''
, 'id': ''
}
, { 'name': "Kyderman's"
, 'lat': 51.352085106718384 // 51.352085106718384
, 'lng': -2.9877930879592896 // -2.9877930879592896
, 'key': ''
, 'id': ''
}
, { 'name': "Toeler's"
, 'lat': -43.525166 // -43.5261282
, 'lng': 172.655096 // 172.6561085
, 'key': ''
, 'id': ''
}
/*
, { 'name': ""
, 'lat': 0
, 'lng': 0
, 'key': ''
, 'id': ''
}
*/
];
// get known-expected values
tests.forEach(function (loc) {
var level = 15;
var s2nLatLng = new s2node.S2LatLng(loc.lat, loc.lng);
var s2nId = new s2node.S2CellId(s2nLatLng).parent(level);
var s2nCell = new s2node.S2Cell(s2nId);
loc.face = s2nCell.face();
loc.id = s2nId.id();
loc.key = s2nId.toString();
loc.lat = s2nId.toLatLng().toString().split(',')[0];
loc.lng = s2nId.toLatLng().toString().split(',')[1];
loc.level = s2nId.level(); // always 15
loc.point = s2nId.toPoint()// .toArray();
//console.log(JSON.stringify(loc, null, ' '));
var key = S2.latLngToQuadkey(loc.lat, loc.lng, level);
var id = S2.toId(key);
var key2 = S2.toKey(id);
var id2 = S2.toId(key2);
if (loc.key !== key || loc.id !== id) {
console.error("Error testing " + loc.name + " @ " + loc.lat + ',' + loc.lng);
console.error("Calculated/Expected:");
console.error(id, ':', loc.id);
console.error(key, " : ", loc.key);
console.error(loc.point.x(), loc.point.y(), loc.point.z());
console.error(Long.fromString(id, true, 10).toString(2));
console.error(Long.fromString(loc.id, true, 10).toString(2));
throw new Error('Test Failed');
}
if (loc.key !== key2 || loc.id !== id2) {
console.error("Error testing " + loc.name + " @ " + loc.lat + ',' + loc.lng);
console.error("Secondary Key / ID conversion failed: Calculated/Expected:");
console.error(id2, ':', loc.id);
console.error(key2, " : ", loc.key);
throw new Error('Test Failed');
}
});

79
tests/debug.js Normal file
View File

@ -0,0 +1,79 @@
'use strict';
//var oS2 = require('./s2geometry.old.js').S2;
var jS2 = require('../src/s2geometry.js').S2;
var nS2 = require('s2geometry-node');
var lat = -43.525166;
var lng = 172.655096;
//var id = '8678661352471920640';
var tests = [
[ -13.846153846153854, -41.53846153846155 ] // face 0
, [ -13.846153846153854, 96.92307692307692 ] // face 1
, [ 41.53846153846153, -124.61538461538463 ] // face 2
, [ -152.30769230769232, 41.53846153846153 ] // face 3
, [ -152.30769230769232, 69.23076923076923 ] // face 4
, [ -124.61538461538463, -69.23076923076924 ] // face 5
];
var tests = [
[ -3.9832738, 12.6825449, 'The Congo (Africa)' ]
, [ 19.0827696, 72.7407752, 'Mumbai, India (Asia)' ]
, [ 68.5207533, -74.9563282, 'Greenland (North Pole)' ]
, [ -1.573289, -158.957890, 'The Pacific Ocean (nowhere)' ]
, [ 40.2573137, -111.7091177, 'Provo, UT, USA (Americas)' ]
, [ -46.362972,-73.7431064, 'Antarctica (South Pole)' ]
];
var bugReports = [
// https://github.com/jonatkins/s2-geometry-javascript/issues/12
[ -6.120097, 106.846511, '@Skeec' ]
// https://github.com/coolaj86/s2-geometry-javascript/issues/8#issuecomment-237204759
, [ -33.87347601637759, 151.1954084522501 ]
// https://github.com/Daplie/s2-geometry.js/issues/1
, [ 45.74528835847731, 12.5516625, '@vekexasia' ]
];
tests.concat(bugReports).forEach(function (pair, i) {
var lat = pair[0];
var lng = pair[1];
var comment = pair[2] && ('(' + pair[2] + ')') || '';
console.log('');
console.log('');
if (i < 6) {
console.log('FACE', i);
}
console.log('Lat/Lng', '=', lat + ',' + lng, comment);
//
// Lat / Lng to XYZ
//
var nS2LatLng = new nS2.S2LatLng(lat, lng).toPoint();
//var nXyz = [ nS2LatLng.x(), nS2LatLng.y(), nS2LatLng.z() ];
//var jXyz = jS2.LatLngToXYZ({ lat: lat, lng: lng });
/*
console.log('');
console.log('XYZ');
console.log('=', nXyz);
console.log('j', jXyz);
*/
var nCell = new nS2.S2CellId(nS2LatLng).parent(15);
var jCell = jS2.S2Cell.FromLatLng({ lat: lat, lng: lng }, 15);
/*
console.log('');
console.log('F,IJ,L');
console.log('j', jCell.toString());
*/
var nKey = nCell.toString();
var jQuad = jCell.getFaceAndQuads();
var jKey = jQuad[0] + '/' + jQuad[1].join('');
console.log('Quadkey');
console.log('=', nKey);
console.log('j', jKey, "(" + jS2.toId(jKey) + ")");
});

21
tests/example.provo.txt Normal file
View File

@ -0,0 +1,21 @@
-10 4 '9749618424903892992' '4/032212303102122' '40.253289,-111.712279' 15
-9 4 '9749618427051376640' '4/032212303102123' '40.253874,-111.709298' 15
-8 4 '9749618429198860288' '4/032212303102130' '40.254460,-111.706317' 15
-7 4 '9749618431346343936' '4/032212303102131' '40.252022,-111.706317' 15
-6 4 '9749618433493827584' '4/032212303102132' '40.252607,-111.703336' 15
-5 4 '9749618435641311232' '4/032212303102133' '40.255045,-111.703336' 15
-4 4 '9749618437788794880' '4/032212303102200' '40.257483,-111.703336' 15
-3 4 '9749618439936278528' '4/032212303102201' '40.259920,-111.703336' 15
-2 4 '9749618442083762176' '4/032212303102202' '40.259335,-111.706317' 15
-1 4 '9749618444231245824' '4/032212303102203' '40.256897,-111.706317' 15
0 4 '9749618446378729472' '4/032212303102210' '40.256312,-111.709298' 15
1 4 '9749618448526213120' '4/032212303102211' '40.255727,-111.712279' 15
2 4 '9749618450673696768' '4/032212303102212' '40.258165,-111.712279' 15
3 4 '9749618452821180416' '4/032212303102213' '40.258750,-111.709298' 15
4 4 '9749618454968664064' '4/032212303102220' '40.261188,-111.709298' 15
5 4 '9749618457116147712' '4/032212303102221' '40.260602,-111.712279' 15
6 4 '9749618459263631360' '4/032212303102222' '40.263040,-111.712279' 15
7 4 '9749618461411115008' '4/032212303102223' '40.263625,-111.709298' 15
8 4 '9749618463558598656' '4/032212303102230' '40.264210,-111.706317' 15
9 4 '9749618465706082304' '4/032212303102231' '40.261773,-111.706317' 15
10 4 '9749618467853565952' '4/032212303102232' '40.262358,-111.703336' 15

21
tests/example.startup.txt Normal file
View File

@ -0,0 +1,21 @@
-10 4 '9749615149991329792' '4/032212302322211' '40.235133,-111.655639' 15
-9 4 '9749615152138813440' '4/032212302322212' '40.234549,-111.658620' 15
-8 4 '9749615154286297088' '4/032212302322213' '40.232110,-111.658620' 15
-7 4 '9749615156433780736' '4/032212302322220' '40.231526,-111.661601' 15
-6 4 '9749615158581264384' '4/032212302322221' '40.233965,-111.661601' 15
-5 4 '9749615160728748032' '4/032212302322222' '40.233381,-111.664582' 15
-4 4 '9749615162876231680' '4/032212302322223' '40.230942,-111.664582' 15
-3 4 '9749615165023715328' '4/032212302322230' '40.228503,-111.664582' 15
-2 4 '9749615167171198976' '4/032212302322231' '40.229087,-111.661601' 15
-1 4 '9749615169318682624' '4/032212302322232' '40.226647,-111.661601' 15
0 4 '9749615171466166272' '4/032212302322233' '40.226063,-111.664582' 15
1 4 '9749615173613649920' '4/032212302322300' '40.223624,-111.664582' 15
2 4 '9749615175761133568' '4/032212302322301' '40.221184,-111.664582' 15
3 4 '9749615177908617216' '4/032212302322302' '40.221768,-111.661601' 15
4 4 '9749615180056100864' '4/032212302322303' '40.224208,-111.661601' 15
5 4 '9749615182203584512' '4/032212302322310' '40.224791,-111.658620' 15
6 4 '9749615184351068160' '4/032212302322311' '40.225375,-111.655639' 15
7 4 '9749615186498551808' '4/032212302322312' '40.222935,-111.655639' 15
8 4 '9749615188646035456' '4/032212302322313' '40.222352,-111.658620' 15
9 4 '9749615190793519104' '4/032212302322320' '40.219912,-111.658620' 15
10 4 '9749615192941002752' '4/032212302322321' '40.220496,-111.655639' 15

32
tests/exhaustive.js Normal file
View File

@ -0,0 +1,32 @@
'use strict';
var jS2 = require('../src/s2geometry.js').S2;
var nS2 = require('s2geometry-node');
var x, y;
function checkReal(lat, lng) {
var nS2LatLng = new nS2.S2LatLng(lat, lng).toPoint();
var nCell = new nS2.S2CellId(nS2LatLng).parent(15);
var jCell = jS2.S2Cell.FromLatLng({ lat: lat, lng: lng }, 15);
var nKey = nCell.toString();
var jQuad = jCell.getFaceAndQuads();
var jKey = jQuad[0] + '/' + jQuad[1].join('');
if (nKey !== jKey) {
console.log('');
console.log('Quadkey');
console.log('=', nKey);
console.log('j', jKey);
throw new Error("values didn't match expected");
}
}
console.log('Exhaustive check of about 518,400 random lat,lng coordinates of the earth (about every 0.5°)');
console.log('(this will take several seconds)');
for (x = -180; x <= 180; x += (0 + Math.random())) {
for (y = -180; y <= 180; y += (0 + Math.random())) {
checkReal(x, y);
}
}
console.log('PASS');

File diff suppressed because it is too large Load Diff

36
tests/hilbert-to-cell.js Normal file
View File

@ -0,0 +1,36 @@
'use strict';
var S2 = require('../src/s2geometry.js').S2;
for(var level = 1; level <= 20; level++) {
var success = 0;
var total = 0;
for (var x = -180.0; x < 180; x += 0.5) {
for (var y = -180.0; y < 180; y += 0.5) {
var latlng = { lat: x, lng: y };
var cell = S2.S2Cell.FromLatLng(latlng, level);
var quadKey = cell.toHilbertQuadkey();
var cell2 = S2.S2Cell.FromHilbertQuadKey(quadKey);
if(cell.face != cell2.face ||
cell.ij[0] != cell2.ij[0] ||
cell.ij[1] != cell2.ij[1] ||
cell.level != cell2.level)
{
/*console.log({
cell: cell,
cell2: cell2})*/
}
else
{
success++;
}
total++;
// check equal
}
}
console.log("level:" + level + "\t total:" + total + "\t success:" + success);
}

9
tests/ids.js Normal file
View File

@ -0,0 +1,9 @@
'use strict';
var S2 = require('../src/s2geometry').S2;
var cellId = S2.fromFacePosLevel(4, '032212303102210');
var hilbertQuadkey = S2.toHilbertQuadkey('9749618446378729472');
console.log(cellId, '9749618446378729472' === cellId);
console.log(hilbertQuadkey, '4/032212303102210' === hilbertQuadkey);

33
tests/js-vs-go.js Normal file
View File

@ -0,0 +1,33 @@
'use strict';
var tests = require('./generated-locations.json');
var jS2 = require('../src/s2geometry.js').S2;
function checkReal(loc) {
var jCell = jS2.S2Cell.FromLatLng({ lat: loc.lat, lng: loc.lng }, 15);
var jQuad = jCell.getFaceAndQuads();
var jKey = jQuad[0] + '/' + jQuad[1].join('');
if (loc.quadkey !== jKey) {
loc.badFace = jCell.face;
loc.badI = jCell.ij[0];
loc.badJ = jCell.ij[1];
loc.badQuad = jKey;
loc.badId = jS2.toId(jKey);
console.log(JSON.stringify(loc, null, ' ') + ',');
console.log('');
console.log('Lat/Lng:', loc.lat, loc.lng);
console.log('');
console.log('I, J:');
console.log('=', loc.i, loc.j);
console.log('j', jCell.ij.join(', '));
console.log('');
console.log('Quadkey');
console.log('=', loc.quadkey);
console.log('j', jKey);
throw new Error("values didn't match expected");
}
}
tests.forEach(checkReal);
console.log('PASS');

48
tests/key-id-to-latlng.js Normal file
View File

@ -0,0 +1,48 @@
'use strict';
var S2 = require('../src/s2geometry.js').S2;
var x, y;
var count = 0;
function refCheck() {
var refKey = '4/032212303102210';
var latlng = {
'lat': 40.2574448
, 'lng': -111.7089464
};
var key = S2.latLngToKey(latlng.lat, latlng.lng, 15);
if (key !== refKey) {
throw new Error("reference doesn't match");
}
var latlng1 = S2.keyToLatLng('4/032212303102210');
var key1 = S2.latLngToKey(latlng1.lat, latlng1.lng, 15);
if (key1 !== refKey) {
throw new Error("reference 1 doesn't match");
}
var latlng2 = S2.idToLatLng('9749618446378729472');
var key2 = S2.latLngToKey(latlng2.lat, latlng2.lng, 15);
if (key2 !== refKey) {
throw new Error("reference 2 doesn't match");
}
}
function checkReal(lat, lng) {
var key = S2.latLngToKey(lat, lng, 15);
var latlng = S2.keyToLatLng(key);
var key2 = S2.latLngToKey(latlng.lat, latlng.lng, 15);
if (key !== key2) {
throw new Error("keys do not match", latlng, key, key2);
}
}
for (x = -180; x <= 180; x += (0 + Math.random())) {
for (y = -180; y <= 180; y += (0 + Math.random())) {
count += 1;
checkReal(x, y);
}
}
console.log('PASS:', count, 'random locations without any key mismatches');

46
tests/latlng2cell.js Normal file
View File

@ -0,0 +1,46 @@
'use strict';
var S2 = require('../src/s2geometry.js').S2;
var level = 15;
// Provo, UT (Center St)
// '9749618446378729472' '4/032212303102210' '40.256312,-111.709298' 15
// 4/032212303102210
//var lat = 40.2574448;
//var lng = -111.7089464;
var latlng = { lat: 40.256312, lng: -111.709298 };
var cell = S2.S2Cell.FromLatLng(latlng, level);
console.log(cell.toHilbertQuadkey(), '4/032212303102210' === cell.toHilbertQuadkey());
console.log(cell.getLatLng(), '40.256312,-111.709298' === cell.getLatLng(), '40.256312,-111.709298');
// Startup Building in Provo
// '9749615171466166272' '4/032212302322233' '40.226063,-111.664582' 15
// 4/032212302322233
//var lat = 40.2262363;
//var lng = -111.6630927;
var latlng = { lat: 40.226063, lng: -111.664582 };
var cell = S2.S2Cell.FromLatLng(latlng, level);
console.log(cell.toHilbertQuadkey(), '4/032212302322233' === cell.toHilbertQuadkey());
console.log(cell.getLatLng(), '40.226063,-111.664582' === cell.getLatLng(), '40.226063,-111.664582');
/*
cell.getNeighbors(); // [ cellLeft, cellDown, cellRight, cellUp ]
latlng = cell.getLatLng(); // { lat: 40.2574448, lng: -111.7089464 }
console.log(orig);
console.log(latlng);
if (40 === Math.round(latlng.lat) && -112 === Math.round(latlng.lng)) {
console.log('OK');
process.exit(0);
}
else {
console.log('[ERROR] latitude and longitude were not the expected values:');
console.log(latlng);
process.exit(1);
}
*/

72
tests/prev-next.js Normal file
View File

@ -0,0 +1,72 @@
'use strict';
var S2 = require('../src/s2geometry.js').S2;
function getNeighbors(lat, lng, step) {
//var step = 10;
var level = 15;
var origin = S2.latLngToQuadkey(lat, lng, level);
var walk = [];
// 10 before and 10 after
var next = S2.nextKey(origin);
var prev = S2.prevKey(origin);
var i;
for (i = 0; i < step; i++) {
walk.unshift(S2.toId(prev));
prev = S2.prevKey(prev);
}
walk.push(S2.toId(origin));
for (i = 0; i < step; i++) {
// in range(10):
walk.push(S2.toId(next));
next = S2.nextKey(next);
}
return walk;
}
/*
// Startup Building in Provo
var lat = 40.2262363;
var lng = -111.6630927;
var walk = getNeighbors(lat, lng, 5);
walk.forEach(function (cellId, i) {
var key = S2.fromId(cellId);
var face = parseInt(key.substr(0, 1), 10);
var pos = key.substr(2);
var level = pos.length;
// TODO
// S2.keyToLatLng(key);
// S2.idToLatLng(id);
// ! because converting CellId / HilbertQuadkey to LatLng is not implemented... yet
console.log(-((walk.length - 1) / 2) + i, face, cellId, S2.fromId(cellId), '!', level);
});
*/
// Kyderman's test location
var lat = 51.352085106718384;
var lng = -2.9877930879592896;
var walk = getNeighbors(lat, lng, 5);
walk.forEach(function (cellId, i) {
var key = S2.fromId(cellId);
var face = parseInt(key.substr(0, 1), 10);
var pos = key.substr(2);
var level = pos.length;
// TODO
// S2.keyToLatLng(key);
// S2.idToLatLng(id);
// ! because converting CellId / HilbertQuadkey to LatLng is not implemented... yet
console.log(-((walk.length - 1) / 2) + i, face, cellId, S2.fromId(cellId), '!', level);
});

52
tests/s2-node-hilbert.js Normal file
View File

@ -0,0 +1,52 @@
var s2n = require('s2geometry-node');
// Provo, UT (Center St)
//var lat = 40.2574448;
//var lng = -111.7089464;
// Startup Building in Provo
//var lat = 40.2262363;
//var lng = -111.6630927;
// Kyderman's test location
//var lat = 51.352085106718384;
//var lng = -2.9877930879592896;
// Toeler's test location
var lat = -43.5261282;
var lng = 172.6561085;
var s2nlatlng = new s2n.S2LatLng(lat, lng);
var cellId = new s2n.S2CellId(s2nlatlng).parent(15);
var cell;
var walk = [];
var next = cellId;
var prev = cellId;
var i;
// -10 - -1
for (i = 0; i < 10; i += 1) {
prev = prev.prev();
walk.unshift([ -(i + 1), prev ]);
}
// 0
walk.push([ 0, cellId ]);
// 1 - 10
for (i = 0; i < 10; i += 1) {
next = next.next();
walk.push([ i + 1, next ]);
}
// all results
walk.forEach(function (parts) {
var i = parts[0];
var cellId = parts[1];
cell = new s2n.S2Cell(cellId);
console.log(i, cell.face(), cellId.id(), cellId.toString(), cellId.toLatLng().toString(), cellId.level());
});

51
tests/simple.js Normal file
View File

@ -0,0 +1,51 @@
'use strict';
var S2 = require('../src/s2geometry.js').S2;
var lat = 40.2574448;
var lng = -111.7089464;
var level = 15;
//
// Convert from Lat / Lng
//
var key = S2.latLngToKey(lat, lng, level);
console.log(key);
// '4/032212303102210'
//
// Convert between Hilbert Curve Quadtree Key and S2 Cell Id
//
var id = S2.keyToId(key);
console.log(id);
// '9749618446378729472'
var key = S2.idToKey(id);
console.log(key);
// '9749618446378729472'
//
// Neighbors
//
var neighbors = S2.latLngToNeighborKeys(lat, lng, level);
console.log(neighbors);
// [ keyLeft, keyDown, keyRight, keyUp ]
//
// Previous, Next, and Step
//
var nextKey = S2.nextKey(key);
console.log(nextKey);
var prevKey = S2.prevKey(key);
console.log(prevKey);
var backTenKeys = S2.stepKey(key, -10);
console.log(backTenKeys);