Created
January 28, 2022 16:36
-
-
Save iSkore/21a329980edb3b9493a279982164b56a to your computer and use it in GitHub Desktop.
A class collection of mathematical equations for calculating Web Mercator tiles
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* Web Mercator Math | |
* @class | |
* @description | |
* Web Mercator Math | |
* A class collection of mathematical equations for calculating Web Mercator tiles | |
* | |
* 0:0:0 | |
* -> 1:0:0 | |
* -> 1:0:1 | |
* -> 1:1:0 | |
* -> 1:1:1 | |
* -> 2:2:2 | |
* -> 2:3:2 | |
* -> 2:2:3 | |
* -> 2:3:3 | |
*/ | |
export default class WebMercatorMath extends Map | |
{ | |
/** | |
* coordsInsideTile | |
* @description | |
* determines which tiles are inside a tile | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {array<array<number, number, number>>} - children tiles | |
*/ | |
static coordsInsideTile( z, x, y ) | |
{ | |
const r = []; | |
for ( let y1 = 0; y1 <= 1; y1++ ) { | |
for ( let x1 = 0; x1 <= 1; x1++ ) { | |
r.push( [ z + 1, ( x << 1 ) + x1, ( y << 1 ) + y1 ] ); | |
} | |
} | |
return r; | |
} | |
/** | |
* getParent | |
* @description | |
* get the parent tile of XYZ tile coordinates | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {array<number>} - tile coordinates of parent | |
*/ | |
static getParent( z, x, y ) | |
{ | |
if ( z === 0 ) { | |
return [ 0, 0, 0 ]; | |
} | |
return [ z - 1, ( x >> 1 ), ( y >> 1 ) ]; | |
} | |
/** | |
* getParents | |
* @description | |
* get all parent tile coordinates from a single tile coordinate | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {array<array<number>>} - returns all parents tile coordinates | |
*/ | |
static getParents( z, x, y ) | |
{ | |
if ( z === 0 ) { | |
return [ [ 0, 0, 0 ] ]; | |
} | |
const r = []; | |
for ( ; z > 0; z--, x >>= 1, y >>= 1 ) { | |
r.push( WebMercatorMath.getParent( z, x, y ) ); | |
} | |
return r; | |
} | |
/** | |
* stringify | |
* @description | |
* stringify three numbers into z:x:y readable format | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {string} - z:x:y readable format of coordinates | |
*/ | |
static stringify( z, x, y ) | |
{ | |
return `${ z }:${ x }:${ y }`; | |
} | |
/** | |
* parse | |
* @description | |
* parse a z:x:y string into coordinate array | |
* @param {string} n - z:x:y formatted string | |
* @return {number[]} - [ z, x, y ] array formatted coordinates | |
*/ | |
static parse( n ) | |
{ | |
const key = n.match( /(?<z>\d+):(?<x>\d+):(?<y>\d+)/ ); | |
return [ +key.groups.z, +key.groups.x, +key.groups.y ]; | |
} | |
/** | |
* validTileCoords | |
* @description | |
* ensures valid Spherical Mercator coordinates | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {boolean} - if valid XYZ coordinate is within bounds | |
*/ | |
static validTileCoords( z, x, y ) | |
{ | |
if ( !z ) { | |
return !x && !y; | |
} | |
const max = ( 1 << z ) - 1; | |
return z >= 0 && x >= 0 && x <= max && y >= 0 && y <= max; | |
} | |
/** | |
* hasKey | |
* @description | |
* determine if this Map has a key | |
* @param {string} key - coords key expected to be formatted `Z:X:Y` | |
* @return {boolean} - if this Map has the key | |
*/ | |
hasKey( key ) | |
{ | |
return super.has( key ); | |
} | |
/** | |
* has | |
* @description | |
* determine with a tile or any of it's parents has been requested | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {boolean} - if a tile or it's parent(s) has been requested | |
*/ | |
has( z, x, y ) | |
{ | |
if ( !WebMercatorMath.validTileCoords( z, x, y ) ) { | |
return false; | |
} | |
if ( this.hasKey( WebMercatorMath.stringify( z, x, y ) ) ) { | |
return true; | |
} | |
if ( z === 0 && x === 0 && y === 0 ) { | |
return false; | |
} | |
return this.has( ...WebMercatorMath.getParent( z, x, y ) ); | |
} | |
/** | |
* get | |
* @description | |
* get a coordinate index key from Map | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @return {boolean|T} - false or value stored | |
*/ | |
get( z, x, y ) | |
{ | |
if ( !WebMercatorMath.validTileCoords( z, x, y ) ) { | |
return false; | |
} | |
return super.get( WebMercatorMath.stringify( z, x, y ) ); | |
} | |
/** | |
* set | |
* @description | |
* store XYZ coordinate index key-value | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @param {*} v - value to set | |
* @return {boolean} - store result | |
*/ | |
set( z, x, y, v ) | |
{ | |
if ( !WebMercatorMath.validTileCoords( z, x, y ) ) { | |
return false; | |
} | |
// TODO:: rethink storing coordinates as a string | |
// binary sets in a SharedArrayBuffer would be significantly more efficient | |
// - UInt32Array because it has 4 byte blocks | |
// - access with Atomics( SharedArrayBuffer ) | |
// - or with some form of binary accumulation: | |
// - 2:3:3 = 47 = 0b101111 | |
// i <<= 32 - Math.clz32( z ); | |
// i |= z; | |
// i <<= 32 - Math.clz32( x ); | |
// i |= x; | |
// i <<= 32 - Math.clz32( y ); | |
// i |= y; | |
return super.set( WebMercatorMath.stringify( z, x, y ), v ); | |
} | |
/** | |
* deleteKey | |
* @description | |
* wrapper for `delete` to parse a key and spread the coordinate index | |
* @param {string} key - coords key expected to be formatted `Z:X:Y` | |
* @param {boolean} [override=false] - force delete the key | |
* @return {boolean} - returns WebMercatorMath.delete | |
*/ | |
deleteKey( key, override = false ) | |
{ | |
return this.delete( ...WebMercatorMath.parse( key ), override ); | |
} | |
/** | |
* delete | |
* @description | |
* delete a coordinate key index | |
* if the tile is the highest stored level, it is not allowed to be deleted | |
* @param {number} z - zoom | |
* @param {number} x - x offset | |
* @param {number} y - y offset | |
* @param {boolean} [override=false] - force delete the key | |
* @return {boolean} - deletion result | |
*/ | |
delete( z, x, y, override = false ) | |
{ | |
if ( !WebMercatorMath.validTileCoords( z, x, y ) ) { | |
return false; | |
} | |
if ( override ) { | |
return super.delete( WebMercatorMath.stringify( z, x, y ) ); | |
} | |
if ( z === 0 && x === 0 && y === 0 ) { | |
return false; | |
} | |
// if the parent exists, allow the child to be destroyed | |
if ( this.has( ...WebMercatorMath.getParent( z, x, y ) ) ) { | |
return super.delete( WebMercatorMath.stringify( z, x, y ) ); | |
} | |
return false; | |
} | |
/** | |
* static get Symbol.species | |
* @description | |
* specification for derived objects | |
* @return {MapConstructor} - object construction type (Map.constructor() signature) | |
*/ | |
static get [ Symbol.species ]() | |
{ | |
return Map; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment