import _ from 'lodash' ; import { DECIMAL_DEGREES_PRECISION } from '../../../common/constants' ; import { clampToLatBounds } from '../../../common/elasticsearch_util' ; import { MapExtent } from '../../../common/descriptor_types' ; const ZOOM_TILE_KEY_INDEX = 0 ; const X_TILE_KEY_INDEX = 1 ; const Y_TILE_KEY_INDEX = 2 ; function getTileCount ( zoom ) { return Math . pow ( 2 , zoom ) ; } export interface ESBounds { top_left : { lon : number ; lat : number ; } ; bottom_right : { lon : number ; lat : number ; } ; } export function parseTileKey ( tileKey ) { const tileKeyParts = tileKey . split ( '/' ) ; if ( tileKeyParts . length !== 3 ) { throw new Error ( `Invalid tile key, expecting "zoom/x/y" format but got ${ tileKey }` ) ; } const zoom = parseInt ( tileKeyParts [ ZOOM_TILE_KEY_INDEX ] , 10 ) ; const x = parseInt ( tileKeyParts [ X_TILE_KEY_INDEX ] , 10 ) ; const y = parseInt ( tileKeyParts [ Y_TILE_KEY_INDEX ] , 10 ) ; const tileCount = getTileCount ( zoom ) ; if ( x >= tileCount ) { throw new Error ( `Tile key is malformed, expected x to be less than ${ tileCount }, you provided ${ x }` ) ; } if ( y >= tileCount ) { throw new Error ( `Tile key is malformed, expected y to be less than ${ tileCount }, you provided ${ y }` ) ; } return { x , y , zoom , tileCount } ; } function sinh ( x ) { return ( Math . exp ( x ) - Math . exp ( - x ) ) / 2 ; } function precisionRounding ( v , minPrecision , binSize ) { let precision = Math . ceil ( Math . abs ( Math . log10 ( binSize ) ) ) + 1 ; precision = Math . max ( precision , minPrecision ) ; return _ . round ( v , precision ) ; } export function tile2long ( x , z ) { const tileCount = getTileCount ( z ) ; return tileToLongitude ( x , tileCount ) ; } export function tile2lat ( y , z ) { const tileCount = getTileCount ( z ) ; return tileToLatitude ( y , tileCount ) ; } export function tileToLatitude ( y , tileCount ) { const radians = Math . atan ( sinh ( Math . PI - ( 2 * Math . PI * y ) / tileCount ) ) ; const lat = ( 180 / Math . PI ) * radians ; return precisionRounding ( lat , DECIMAL_DEGREES_PRECISION , 180 / tileCount ) ; } export function tileToLongitude ( x , tileCount ) { const lon = ( x / tileCount ) * 360 - 180 ; return precisionRounding ( lon , DECIMAL_DEGREES_PRECISION , 360 / tileCount ) ; } export function getTileBoundingBox ( tileKey ) { const { x , y , tileCount } = parseTileKey ( tileKey ) ; return { top : tileToLatitude ( y , tileCount ) , bottom : tileToLatitude ( y + 1 , tileCount ) , left : tileToLongitude ( x , tileCount ) , right : tileToLongitude ( x + 1 , tileCount ) , } ; } function sec ( value ) { return 1 / Math . cos ( value ) ; } function latitudeToTile ( lat , tileCount ) { const radians = ( clampToLatBounds ( lat ) * Math . PI ) / 180 ; const y = ( ( 1 - Math . log ( Math . tan ( radians ) + sec ( radians ) ) / Math . PI ) / 2 ) * tileCount ; return Math . floor ( y ) ; } function longitudeToTile ( lon , tileCount ) { const x = ( ( lon + 180 ) / 360 ) * tileCount ; return Math . floor ( x ) ; } export function expandToTileBoundaries ( extent , zoom ) { const tileCount = getTileCount ( zoom ) ; const upperLeftX = longitudeToTile ( extent . minLon , tileCount ) ; const upperLeftY = latitudeToTile ( Math . min ( extent . maxLat , 90 ) , tileCount ) ; const lowerRightX = longitudeToTile ( extent . maxLon , tileCount ) ; const lowerRightY = latitudeToTile ( Math . max ( extent . minLat , - 90 ) , tileCount ) ; return { minLon : tileToLongitude ( upperLeftX , tileCount ) , minLat : tileToLatitude ( lowerRightY + 1 , tileCount ) , maxLon : tileToLongitude ( lowerRightX + 1 , tileCount ) , maxLat : tileToLatitude ( upperLeftY , tileCount ) , } ; }