Compare commits
78 Commits
Author | SHA1 | Date |
---|---|---|
AJ ONeal | 7a10f5b562 | |
AJ ONeal | e7ecb48449 | |
AJ ONeal | 441c693bad | |
AJ ONeal | e67bb19828 | |
AJ ONeal | df6d38b5ad | |
AJ ONeal | 3a962c1956 | |
AJ ONeal | 5495dd0a53 | |
Patrick | d28f0a6d3f | |
Patrick | a4e97ba0a9 | |
AJ ONeal | 0cc25036e5 | |
AJ ONeal | a11cd4e920 | |
AJ ONeal | 42a9eaf1fc | |
David Murdoch | 4e631500ea | |
David Murdoch | 8aa84ebf93 | |
Jason Hunter | 9fdcc00739 | |
AJ ONeal | 7180db1be6 | |
AJ ONeal | c6ee1ee508 | |
AJ ONeal | 4249d3bfa6 | |
AJ ONeal | 60d01395ef | |
AJ ONeal | 2e7b12e212 | |
AJ ONeal | 1a9c95e2c6 | |
AJ ONeal | b3f5bbd27f | |
AJ ONeal | f96e16c6bf | |
AJ ONeal | d1f19ef616 | |
AJ ONeal | 191e83fe40 | |
AJ ONeal | 4c4154a288 | |
AJ ONeal | be9112f2ad | |
AJ ONeal | bb664af25b | |
AJ ONeal | 98fa79b6bd | |
AJ ONeal | d5524f7b74 | |
AJ ONeal | afe681240b | |
AJ ONeal | 197e07a605 | |
AJ ONeal | 60186d007f | |
AJ ONeal | b7f7c6df1c | |
AJ ONeal | a1da97514e | |
AJ ONeal | 68274eb677 | |
AJ ONeal | e04b0b0ecd | |
AJ ONeal | 4a7b8819c9 | |
AJ ONeal | c0c80ca0f8 | |
AJ ONeal | c094e1def7 | |
AJ ONeal | d18e4906c5 | |
AJ ONeal | b45bb89117 | |
AJ ONeal | d7aa5c136a | |
AJ ONeal | 0d8eec3d04 | |
AJ ONeal | 113e375824 | |
AJ ONeal | e461dcfd5c | |
AJ ONeal | 51b549de38 | |
AJ ONeal | c275019aec | |
AJ ONeal | 2796829356 | |
AJ ONeal | 6252e4e9e9 | |
AJ ONeal | 65d94c9141 | |
AJ ONeal | d344d81611 | |
AJ ONeal | 54bd875e3e | |
AJ ONeal | e3619d963a | |
AJ ONeal | c65239853a | |
AJ ONeal | 103d955300 | |
AJ ONeal | 7ca690db14 | |
AJ ONeal | f4768f7a60 | |
AJ ONeal | a60945a4b9 | |
AJ ONeal | 0d828b28b0 | |
AJ ONeal | f911efcdab | |
AJ ONeal | b5c41f04e4 | |
AJ ONeal | 51d45cd809 | |
AJ ONeal | 5179a6b31d | |
AJ ONeal | a73556c7a3 | |
AJ ONeal | 7014dd3632 | |
AJ ONeal | 35ceafae3d | |
AJ ONeal | 74149b89cb | |
AJ ONeal | 544fc8a48d | |
AJ ONeal | 54fccf662e | |
AJ ONeal | ce2e39a090 | |
AJ ONeal | 8c17bc9e4f | |
AJ ONeal | 61b3ea3f37 | |
AJ ONeal | eb224e01aa | |
AJ ONeal | 8c15c5a1d7 | |
AJ ONeal | e3a7a4519b | |
AJ ONeal | 5a589ea596 | |
AJ ONeal | 020b5ab34e |
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.*.*sw*
|
|
@ -0,0 +1,2 @@
|
|||
Jon Atkins <github@jonatkins.com> (http://www.jonatkins.com/)
|
||||
AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com)
|
|
@ -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
211
README.md
|
@ -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
|
||||
|
||||
<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
|
||||
---------------
|
||||
|
||||
```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');
|
||||
```
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -22,13 +22,55 @@
|
|||
// - 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)
|
||||
|
||||
(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) {
|
||||
var d2r = L.LatLng.DEG_TO_RAD;
|
||||
if (isNaN(lat) || isNaN(lng)) {
|
||||
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 theta = latLng.lng*d2r;
|
||||
|
@ -38,13 +80,13 @@ var LatLngToXYZ = function(latLng) {
|
|||
return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)];
|
||||
};
|
||||
|
||||
var XYZToLatLng = function(xyz) {
|
||||
var r2d = L.LatLng.RAD_TO_DEG;
|
||||
S2.XYZToLatLng = function(xyz) {
|
||||
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 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) {
|
||||
|
@ -76,28 +118,28 @@ var faceXYZToUV = function(face,xyz) {
|
|||
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 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];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
var XYZToFaceUV = function(xyz) {
|
||||
S2.XYZToFaceUV = function(xyz) {
|
||||
var face = largestAbsComponent(xyz);
|
||||
|
||||
if (xyz[face] < 0) {
|
||||
face += 3;
|
||||
}
|
||||
|
||||
uv = faceXYZToUV (face,xyz);
|
||||
var uv = faceXYZToUV (face,xyz);
|
||||
|
||||
return [face, uv];
|
||||
};
|
||||
|
||||
var FaceUVToXYZ = function(face,uv) {
|
||||
S2.FaceUVToXYZ = function(face,uv) {
|
||||
var u = uv[0];
|
||||
var v = uv[1];
|
||||
|
||||
|
@ -112,35 +154,32 @@ var FaceUVToXYZ = function(face,uv) {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
var STToUV = function(st) {
|
||||
var singleSTtoUV = function(st) {
|
||||
if (st >= 0.5) {
|
||||
return (1/3.0) * (4*st*st - 1);
|
||||
} else {
|
||||
return (1/3.0) * (1 - (4*(1-st)*(1-st)));
|
||||
}
|
||||
var singleSTtoUV = function(st) {
|
||||
if (st >= 0.5) {
|
||||
return (1/3.0) * (4*st*st - 1);
|
||||
} else {
|
||||
return (1/3.0) * (1 - (4*(1-st)*(1-st)));
|
||||
}
|
||||
};
|
||||
|
||||
S2.STToUV = function(st) {
|
||||
return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
|
||||
};
|
||||
|
||||
|
||||
|
||||
var UVToST = function(uv) {
|
||||
var singleUVtoST = function(uv) {
|
||||
if (uv >= 0) {
|
||||
return 0.5 * Math.sqrt (1 + 3*uv);
|
||||
} else {
|
||||
return 1 - 0.5 * Math.sqrt (1 - 3*uv);
|
||||
}
|
||||
var singleUVtoST = function(uv) {
|
||||
if (uv >= 0) {
|
||||
return 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])];
|
||||
};
|
||||
|
||||
|
||||
var STToIJ = function(st,order) {
|
||||
S2.STToIJ = function(st,order) {
|
||||
var maxSize = (1<<order);
|
||||
|
||||
var singleSTtoIJ = function(st) {
|
||||
|
@ -152,29 +191,56 @@ var STToIJ = function(st,order) {
|
|||
};
|
||||
|
||||
|
||||
var IJToST = function(ij,order,offsets) {
|
||||
S2.IJToST = function(ij,order,offsets) {
|
||||
var maxSize = (1<<order);
|
||||
|
||||
return [
|
||||
(ij[0]+offsets[0])/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
|
||||
// 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
|
||||
// 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
|
||||
var pointToHilbertQuadList = function(x,y,order) {
|
||||
var pointToHilbertQuadList = function(x,y,order,face) {
|
||||
var hilbertMap = {
|
||||
'a': [ [0,'d'], [1,'a'], [3,'b'], [2,'a'] ],
|
||||
'b': [ [2,'b'], [1,'b'], [3,'a'], [0,'c'] ],
|
||||
'c': [ [2,'c'], [3,'d'], [1,'c'], [0,'b'] ],
|
||||
'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 = [];
|
||||
|
||||
for (var i=order-1; i>=0; i--) {
|
||||
|
@ -194,28 +260,86 @@ var pointToHilbertQuadList = function(x,y,order) {
|
|||
return positions;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// S2Cell class
|
||||
|
||||
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
|
||||
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 st = UVToST(faceuv[1]);
|
||||
|
||||
var ij = STToIJ(st,level);
|
||||
var ij = S2.STToIJ(st,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) {
|
||||
var cell = new S2.S2Cell();
|
||||
cell.face = face;
|
||||
|
@ -231,11 +355,11 @@ S2.S2Cell.prototype.toString = function() {
|
|||
};
|
||||
|
||||
S2.S2Cell.prototype.getLatLng = function() {
|
||||
var st = IJToST(this.ij,this.level, [0.5,0.5]);
|
||||
var uv = STToUV(st);
|
||||
var xyz = FaceUVToXYZ(this.face, uv);
|
||||
var st = S2.IJToST(this.ij,this.level, [0.5,0.5]);
|
||||
var uv = S2.STToUV(st);
|
||||
var xyz = S2.FaceUVToXYZ(this.face, uv);
|
||||
|
||||
return XYZToLatLng(xyz);
|
||||
return S2.XYZToLatLng(xyz);
|
||||
};
|
||||
|
||||
S2.S2Cell.prototype.getCornerLatLngs = function() {
|
||||
|
@ -248,22 +372,32 @@ S2.S2Cell.prototype.getCornerLatLngs = function() {
|
|||
];
|
||||
|
||||
for (var i=0; i<4; i++) {
|
||||
var st = IJToST(this.ij, this.level, offsets[i]);
|
||||
var uv = STToUV(st);
|
||||
var xyz = FaceUVToXYZ(this.face, uv);
|
||||
var st = S2.IJToST(this.ij, this.level, offsets[i]);
|
||||
var uv = S2.STToUV(st);
|
||||
var xyz = S2.FaceUVToXYZ(this.face, uv);
|
||||
|
||||
result.push ( XYZToLatLng(xyz) );
|
||||
result.push ( S2.XYZToLatLng(xyz) );
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
S2.S2Cell.prototype.getFaceAndQuads = function() {
|
||||
var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level);
|
||||
S2.S2Cell.prototype.getFaceAndQuads = function () {
|
||||
var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
|
||||
|
||||
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() {
|
||||
|
||||
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
|
||||
// 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 uv = STToUV(st);
|
||||
var xyz = FaceUVToXYZ(face,uv);
|
||||
var faceuv = XYZToFaceUV(xyz);
|
||||
var st = S2.IJToST(ij,level,[0.5,0.5]);
|
||||
var uv = S2.STToUV(st);
|
||||
var xyz = S2.FaceUVToXYZ(face,uv);
|
||||
var faceuv = S2.XYZToFaceUV(xyz);
|
||||
face = faceuv[0];
|
||||
uv = faceuv[1];
|
||||
st = UVToST(uv);
|
||||
ij = STToIJ(st,level);
|
||||
st = S2.UVToST(uv);
|
||||
ij = S2.STToIJ(st,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);
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
|
@ -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) + ")");
|
||||
});
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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);
|
||||
}
|
|
@ -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);
|
|
@ -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');
|
|
@ -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');
|
|
@ -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);
|
||||
}
|
||||
*/
|
|
@ -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);
|
||||
});
|
|
@ -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());
|
||||
});
|
|
@ -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);
|
Loading…
Reference in New Issue