Compare commits
	
		
			56 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 7a10f5b562 | ||
|  | e7ecb48449 | ||
|  | 441c693bad | ||
|  | e67bb19828 | ||
|  | df6d38b5ad | ||
|  | 3a962c1956 | ||
|  | 5495dd0a53 | ||
|  | d28f0a6d3f | ||
|  | a4e97ba0a9 | ||
|  | 0cc25036e5 | ||
|  | a11cd4e920 | ||
|  | 42a9eaf1fc | ||
|  | 4e631500ea | ||
|  | 8aa84ebf93 | ||
|  | 9fdcc00739 | ||
|  | 7180db1be6 | ||
|  | c6ee1ee508 | ||
|  | 4249d3bfa6 | ||
|  | 60d01395ef | ||
|  | 2e7b12e212 | ||
|  | 1a9c95e2c6 | ||
|  | b3f5bbd27f | ||
|  | f96e16c6bf | ||
|  | d1f19ef616 | ||
|  | 191e83fe40 | ||
|  | 4c4154a288 | ||
|  | be9112f2ad | ||
|  | bb664af25b | ||
|  | 98fa79b6bd | ||
|  | d5524f7b74 | ||
|  | afe681240b | ||
|  | 197e07a605 | ||
|  | 60186d007f | ||
|  | b7f7c6df1c | ||
|  | a1da97514e | ||
|  | 68274eb677 | ||
|  | e04b0b0ecd | ||
|  | 4a7b8819c9 | ||
|  | c0c80ca0f8 | ||
|  | c094e1def7 | ||
|  | d18e4906c5 | ||
|  | b45bb89117 | ||
|  | d7aa5c136a | ||
|  | 0d8eec3d04 | ||
|  | 113e375824 | ||
|  | e461dcfd5c | ||
|  | 51b549de38 | ||
|  | c275019aec | ||
|  | 2796829356 | ||
|  | 6252e4e9e9 | ||
|  | 65d94c9141 | ||
|  | d344d81611 | ||
|  | 54bd875e3e | ||
|  | e3619d963a | ||
|  | c65239853a | ||
|  | 103d955300 | 
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @ -1,7 +1,7 @@ | ||||
| ISC License (ISC) | ||||
| 
 | ||||
| Copyright (c) 2012-2016, Jon Atkins <github@jonatkins.com> | ||||
| Copyright (c) 2016, AJ ONeal <coolaj86@gmail.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. | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										184
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										184
									
								
								README.md
									
									
									
									
									
								
							| @ -1,31 +1,186 @@ | ||||
| s2-geometry (JavaScript/ES5.1) | ||||
| ====================== | ||||
| 
 | ||||
| A pure JavaScript/ES5.1 port of Google/Niantic's S2 Geometry library (used by **Ingress**, **Pokemon GO**) | ||||
| | 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 | ||||
| 'use strict'; | ||||
| 
 | ||||
| var S2 = require('s2-geometry').S2; | ||||
| 
 | ||||
| var lat = 40.2574448; | ||||
| var lng = -111.7089464; | ||||
| var level = 15; | ||||
| var latlng = { lat: 40.2574448, lng: -111.7089464 }; | ||||
| var cell = S2.S2Cell.FromLatLng(latlng, level); | ||||
| 
 | ||||
| cell.getNeighbors();  // [ cellLeft, cellDown, cellRight, cellUp ] | ||||
| 
 | ||||
| cell.getLatLng();     // { lat: 40.2574448, lng: -111.7089464 } | ||||
| 
 | ||||
| // | ||||
| // 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); | ||||
| ``` | ||||
| 
 | ||||
| convert Cell Id to Quadkey | ||||
| 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' | ||||
| @ -34,7 +189,7 @@ var face = parts[0];                  // 4 | ||||
| var position = parts[1];              // '032212303102210'; | ||||
| var level = '032212303102210'.length; // 15 | ||||
| 
 | ||||
| var cellId = S2.fromFacePosLevel(face, position, level); | ||||
| var cellId = S2.facePosLevelToId(face, position, level); | ||||
| 
 | ||||
| console.log(cellId); | ||||
| ``` | ||||
| @ -43,12 +198,21 @@ Convert from hilbert quadtree id to s2 cell id: | ||||
| 
 | ||||
| Example '9749618446378729472' becomes '4/032212303102210' | ||||
| 
 | ||||
| ``` | ||||
| ```javascript | ||||
| 'use strict'; | ||||
| 
 | ||||
| var cellId = '9749618446378729472'; | ||||
| 
 | ||||
| var hilbertQuadkey = S2.toHilbertQuadkey(cellId); | ||||
| 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'); | ||||
| ``` | ||||
|  | ||||
| @ -34,5 +34,6 @@ | ||||
|     "bower_components", | ||||
|     "test", | ||||
|     "tests" | ||||
|   ] | ||||
|   ], | ||||
|   "version": "1.2.9" | ||||
| } | ||||
|  | ||||
							
								
								
									
										10
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								package.json
									
									
									
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "s2-geometry", | ||||
|   "version": "1.1.1", | ||||
|   "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": { | ||||
| @ -8,7 +8,7 @@ | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/coolaj86/s2-geometry-javascript.git" | ||||
|     "url": "git+https://git.coolaj86.com/coolaj86/s2-geometry.js.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "s2", | ||||
| @ -28,11 +28,11 @@ | ||||
|     "lng" | ||||
|   ], | ||||
|   "author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)", | ||||
|   "license": "ISC", | ||||
|   "license": "(MIT or Apache-2 or ISC)", | ||||
|   "bugs": { | ||||
|     "url": "https://github.com/coolaj86/s2-geometry-javascript/issues" | ||||
|     "url": "https://git.coolaj86.com/coolaj86/s2-geometry.js/issues" | ||||
|   }, | ||||
|   "homepage": "https://github.com/coolaj86/s2-geometry-javascript#readme", | ||||
|   "homepage": "https://git.coolaj86.com/coolaj86/s2-geometry.js#readme", | ||||
|   "dependencies": { | ||||
|     "long": "^3.2.0" | ||||
|   } | ||||
|  | ||||
| @ -22,12 +22,32 @@ | ||||
| // - 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(exports) { | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| 
 | ||||
| var S2 = exports.S2 = {}; | ||||
| var S2 = exports.S2 = { L: {} }; | ||||
| 
 | ||||
| var LatLngToXYZ = function(latLng) { | ||||
| S2.L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) { | ||||
|   var lat = parseFloat(rawLat, 10); | ||||
|   var lng = parseFloat(rawLng, 10); | ||||
| 
 | ||||
|   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; | ||||
| @ -48,8 +68,19 @@ var LatLngToXYZ = function(latLng) { | ||||
|   , rad * sinLat | ||||
|   ]; | ||||
| }; | ||||
| */ | ||||
| S2.LatLngToXYZ = function(latLng) { | ||||
|   var d2r = S2.L.LatLng.DEG_TO_RAD; | ||||
| 
 | ||||
| var XYZToLatLng = function(xyz) { | ||||
|   var phi = latLng.lat*d2r; | ||||
|   var theta = latLng.lng*d2r; | ||||
| 
 | ||||
|   var cosphi = Math.cos(phi); | ||||
| 
 | ||||
|   return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)]; | ||||
| }; | ||||
| 
 | ||||
| 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])); | ||||
| @ -96,7 +127,7 @@ var faceXYZToUV = function(face,xyz) { | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| var XYZToFaceUV = function(xyz) { | ||||
| S2.XYZToFaceUV = function(xyz) { | ||||
|   var face = largestAbsComponent(xyz); | ||||
| 
 | ||||
|   if (xyz[face] < 0) { | ||||
| @ -108,7 +139,7 @@ var XYZToFaceUV = function(xyz) { | ||||
|   return [face, uv]; | ||||
| }; | ||||
| 
 | ||||
| var FaceUVToXYZ = function(face,uv) { | ||||
| S2.FaceUVToXYZ = function(face,uv) { | ||||
|   var u = uv[0]; | ||||
|   var v = uv[1]; | ||||
| 
 | ||||
| @ -131,7 +162,7 @@ var singleSTtoUV = function(st) { | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| var STToUV = function(st) { | ||||
| S2.STToUV = function(st) { | ||||
|   return [singleSTtoUV(st[0]), singleSTtoUV(st[1])]; | ||||
| }; | ||||
| 
 | ||||
| @ -143,12 +174,12 @@ var singleUVtoST = function(uv) { | ||||
|     return 1 - 0.5 * Math.sqrt (1 - 3*uv); | ||||
|   } | ||||
| }; | ||||
| var UVToST = function(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) { | ||||
| @ -160,7 +191,7 @@ var STToIJ = function(st,order) { | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| var IJToST = function(ij,order,offsets) { | ||||
| S2.IJToST = function(ij,order,offsets) { | ||||
|   var maxSize = (1<<order); | ||||
| 
 | ||||
|   return [ | ||||
| @ -169,12 +200,36 @@ var IJToST = function(ij,order,offsets) { | ||||
|   ]; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 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'] ], | ||||
| @ -182,7 +237,10 @@ var pointToHilbertQuadList = function(x,y,order) { | ||||
|     '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--) { | ||||
| @ -202,24 +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); | ||||
| }; | ||||
| 
 | ||||
| /* | ||||
| 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; | ||||
| @ -235,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() { | ||||
| @ -252,27 +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); | ||||
|   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); | ||||
|   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) { | ||||
| @ -285,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); | ||||
|     } | ||||
|   }; | ||||
| @ -317,10 +442,13 @@ S2.S2Cell.prototype.getNeighbors = function() { | ||||
| //
 | ||||
| S2.FACE_BITS = 3; | ||||
| S2.MAX_LEVEL = 30; | ||||
| S2.POS_BITS = (2 * S2.MAX_LEVEL) + 1; | ||||
| S2.POS_BITS = (2 * S2.MAX_LEVEL) + 1; // 61 (60 bits of data, 1 bit lsb marker)
 | ||||
| 
 | ||||
| S2.fromFacePosLevel = function (faceN, posS, levelN) { | ||||
| 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; | ||||
| @ -329,30 +457,56 @@ S2.fromFacePosLevel = function (faceN, posS, levelN) { | ||||
|     posS = posS.substr(0, levelN); | ||||
|   } | ||||
| 
 | ||||
|   var posB = Long.fromString(posS, true, 4).toString(2); | ||||
|   // 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; | ||||
|   } | ||||
|   var bin = Long.fromString(faceN.toString(10), true, 10).toString(2); | ||||
|   bin += 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(); | ||||
|   return Long.fromString(bin, true, 2).toString(10); | ||||
| }; | ||||
| 
 | ||||
| S2.toHilbertQuadkey = function (idS) { | ||||
| 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); | ||||
|   var lsbIndex = bin.lastIndexOf('1'); | ||||
| 
 | ||||
|   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)
 | ||||
|   var faceB = bin.substr(0, 3); | ||||
|   // 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(4, lsbIndex); | ||||
|   var posB = bin.substring(3, lsbIndex); | ||||
|   var levelN = posB.length / 2; | ||||
| 
 | ||||
|   var faceS = Long.fromString(faceB, true, 2).toString(10); | ||||
| @ -365,37 +519,76 @@ S2.toHilbertQuadkey = function (idS) { | ||||
|   return faceS + '/' + posS; | ||||
| }; | ||||
| 
 | ||||
| })('undefined' !== typeof window ? window : module.exports); | ||||
| S2.keyToLatLng = S2.S2Cell.keyToLatLng = function (key) { | ||||
|   var cell2 = S2.S2Cell.FromHilbertQuadKey(key); | ||||
|   return cell2.getLatLng(); | ||||
| }; | ||||
| 
 | ||||
| (function (exports) { | ||||
| 'use strict'; | ||||
| S2.idToLatLng = S2.S2Cell.idToLatLng = function (id) { | ||||
|   var key = S2.idToKey(id); | ||||
|   return S2.keyToLatLng(key); | ||||
| }; | ||||
| 
 | ||||
|   // Adapted from Leafletjs https://searchcode.com/codesearch/view/42525008/
 | ||||
| 
 | ||||
|   var L = {}; | ||||
|   var S2 = exports.S2; | ||||
| 
 | ||||
|   if (!exports.L) { | ||||
|     exports.L = L; | ||||
| 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)"); | ||||
|   } | ||||
|   S2.L = L; | ||||
|   // 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(); | ||||
| }; | ||||
| 
 | ||||
|   L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) { | ||||
|     var lat = parseFloat(rawLat, 10); | ||||
|     var lng = parseFloat(rawLng, 10); | ||||
| S2.stepKey = function (key, num) { | ||||
|   var Long = exports.dcodeIO && exports.dcodeIO.Long || require('long'); | ||||
|   var parts = key.split('/'); | ||||
| 
 | ||||
|     if (isNaN(lat) || isNaN(lng)) { | ||||
|       throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')'); | ||||
|   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")); | ||||
|   } | ||||
| 
 | ||||
|     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
 | ||||
|   while (otherS.length < level) { | ||||
|     otherS = '0' + otherS; | ||||
|   } | ||||
| 
 | ||||
|     return { lat: lat, lng: lng }; | ||||
|   }; | ||||
|   return faceS + '/' + otherS; | ||||
| }; | ||||
| 
 | ||||
|   L.LatLng.DEG_TO_RAD = Math.PI / 180; | ||||
|   L.LatLng.RAD_TO_DEG = 180 / Math.PI; | ||||
| })('undefined' !== typeof window ? window : module.exports); | ||||
| 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
									
								
							
							
						
						
									
										84
									
								
								tests/conversions.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										79
									
								
								tests/debug.js
									
									
									
									
									
										Normal 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) + ")"); | ||||
| }); | ||||
							
								
								
									
										32
									
								
								tests/exhaustive.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								tests/exhaustive.js
									
									
									
									
									
										Normal 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'); | ||||
							
								
								
									
										2053
									
								
								tests/generated-locations.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2053
									
								
								tests/generated-locations.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										36
									
								
								tests/hilbert-to-cell.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								tests/hilbert-to-cell.js
									
									
									
									
									
										Normal 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); | ||||
| } | ||||
							
								
								
									
										33
									
								
								tests/js-vs-go.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/js-vs-go.js
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										48
									
								
								tests/key-id-to-latlng.js
									
									
									
									
									
										Normal 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'); | ||||
							
								
								
									
										72
									
								
								tests/prev-next.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								tests/prev-next.js
									
									
									
									
									
										Normal 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); | ||||
| }); | ||||
| @ -1,15 +1,23 @@ | ||||
| var S2 = require('s2geometry-node'); | ||||
| 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; | ||||
| //var lat = 40.2262363;
 | ||||
| //var lng = -111.6630927;
 | ||||
| 
 | ||||
| var s2latlng = new S2.S2LatLng(lat, lng); | ||||
| var cellId = new S2.S2CellId(s2latlng).parent(15); | ||||
| // 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 = []; | ||||
| @ -39,6 +47,6 @@ walk.forEach(function (parts) { | ||||
|   var i = parts[0]; | ||||
|   var cellId = parts[1]; | ||||
| 
 | ||||
|   cell = new S2.S2Cell(cellId); | ||||
|   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
									
								
							
							
						
						
									
										51
									
								
								tests/simple.js
									
									
									
									
									
										Normal 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); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user