Skip to content

Instantly share code, notes, and snippets.

@iSkore
Created January 28, 2022 16:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iSkore/21a329980edb3b9493a279982164b56a to your computer and use it in GitHub Desktop.
Save iSkore/21a329980edb3b9493a279982164b56a to your computer and use it in GitHub Desktop.
A class collection of mathematical equations for calculating Web Mercator tiles
'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