@deck.gl/geo-layers
Version:
deck.gl layers supporting geospatial use cases and GIS formats
216 lines (201 loc) • 6.24 kB
JavaScript
import {getOSMTileIndices} from './tile-2d-traversal';
const TILE_SIZE = 512;
const DEFAULT_EXTENT = [-Infinity, -Infinity, Infinity, Infinity];
export const urlType = {
type: 'url',
value: null,
validate: (value, propType) =>
(propType.optional && value === null) ||
typeof value === 'string' ||
(Array.isArray(value) && value.every(url => typeof url === 'string')),
equals: (value1, value2) => {
if (value1 === value2) {
return true;
}
if (!Array.isArray(value1) || !Array.isArray(value2)) {
return false;
}
const len = value1.length;
if (len !== value2.length) {
return false;
}
for (let i = 0; i < len; i++) {
if (value1[i] !== value2[i]) {
return false;
}
}
return true;
}
};
function transformBox(bbox, modelMatrix) {
const transformedCoords = [
// top-left
modelMatrix.transformAsPoint([bbox[0], bbox[1]]),
// top-right
modelMatrix.transformAsPoint([bbox[2], bbox[1]]),
// bottom-left
modelMatrix.transformAsPoint([bbox[0], bbox[3]]),
// bottom-right
modelMatrix.transformAsPoint([bbox[2], bbox[3]])
];
const transformedBox = [
// Minimum x coord
Math.min(...transformedCoords.map(i => i[0])),
// Minimum y coord
Math.min(...transformedCoords.map(i => i[1])),
// Max x coord
Math.max(...transformedCoords.map(i => i[0])),
// Max y coord
Math.max(...transformedCoords.map(i => i[1]))
];
return transformedBox;
}
export function getURLFromTemplate(template, properties) {
if (!template || !template.length) {
return null;
}
if (Array.isArray(template)) {
const index = Math.abs(properties.x + properties.y) % template.length;
template = template[index];
}
const {x, y, z} = properties;
return template
.replace(/\{x\}/g, x)
.replace(/\{y\}/g, y)
.replace(/\{z\}/g, z)
.replace(/\{-y\}/g, Math.pow(2, z) - y - 1);
}
/**
* gets the bounding box of a viewport
*/
function getBoundingBox(viewport, zRange, extent) {
let bounds;
if (zRange && zRange.length === 2) {
const [minZ, maxZ] = zRange;
const bounds0 = viewport.getBounds({z: minZ});
const bounds1 = viewport.getBounds({z: maxZ});
bounds = [
Math.min(bounds0[0], bounds1[0]),
Math.min(bounds0[1], bounds1[1]),
Math.max(bounds0[2], bounds1[2]),
Math.max(bounds0[3], bounds1[3])
];
} else {
bounds = viewport.getBounds();
}
if (!viewport.isGeospatial) {
return [
// Top corner should not be more then bottom corner in either direction
Math.max(Math.min(bounds[0], extent[2]), extent[0]),
Math.max(Math.min(bounds[1], extent[3]), extent[1]),
// Bottom corner should not be less then top corner in either direction
Math.min(Math.max(bounds[2], extent[0]), extent[2]),
Math.min(Math.max(bounds[3], extent[1]), extent[3])
];
}
return [
Math.max(bounds[0], extent[0]),
Math.max(bounds[1], extent[1]),
Math.min(bounds[2], extent[2]),
Math.min(bounds[3], extent[3])
];
}
function getIndexingCoords(bbox, scale, modelMatrixInverse) {
if (modelMatrixInverse) {
const transformedTileIndex = transformBox(bbox, modelMatrixInverse).map(
i => (i * scale) / TILE_SIZE
);
return transformedTileIndex;
}
return bbox.map(i => (i * scale) / TILE_SIZE);
}
function getScale(z, tileSize) {
return (Math.pow(2, z) * TILE_SIZE) / tileSize;
}
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_2
export function osmTile2lngLat(x, y, z) {
const scale = getScale(z, TILE_SIZE);
const lng = (x / scale) * 360 - 180;
const n = Math.PI - (2 * Math.PI * y) / scale;
const lat = (180 / Math.PI) * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
return [lng, lat];
}
function tile2XY(x, y, z, tileSize) {
const scale = getScale(z, tileSize);
return [(x / scale) * TILE_SIZE, (y / scale) * TILE_SIZE];
}
export function tileToBoundingBox(viewport, x, y, z, tileSize = TILE_SIZE) {
if (viewport.isGeospatial) {
const [west, north] = osmTile2lngLat(x, y, z);
const [east, south] = osmTile2lngLat(x + 1, y + 1, z);
return {west, north, east, south};
}
const [left, top] = tile2XY(x, y, z, tileSize);
const [right, bottom] = tile2XY(x + 1, y + 1, z, tileSize);
return {left, top, right, bottom};
}
function getIdentityTileIndices(viewport, z, tileSize, extent, modelMatrixInverse) {
const bbox = getBoundingBox(viewport, null, extent);
const scale = getScale(z, tileSize);
const [minX, minY, maxX, maxY] = getIndexingCoords(bbox, scale, modelMatrixInverse);
const indices = [];
/*
| TILE | TILE | TILE |
|(minX) |(maxX)
*/
for (let x = Math.floor(minX); x < maxX; x++) {
for (let y = Math.floor(minY); y < maxY; y++) {
indices.push({x, y, z});
}
}
return indices;
}
/**
* Returns all tile indices in the current viewport. If the current zoom level is smaller
* than minZoom, return an empty array. If the current zoom level is greater than maxZoom,
* return tiles that are on maxZoom.
*/
// eslint-disable-next-line complexity
export function getTileIndices({
viewport,
maxZoom,
minZoom,
zRange,
extent,
tileSize = TILE_SIZE,
modelMatrix,
modelMatrixInverse,
zoomOffset = 0
}) {
let z = viewport.isGeospatial
? Math.round(viewport.zoom + Math.log2(TILE_SIZE / tileSize)) + zoomOffset
: Math.ceil(viewport.zoom) + zoomOffset;
if (Number.isFinite(minZoom) && z < minZoom) {
if (!extent) {
return [];
}
z = minZoom;
}
if (Number.isFinite(maxZoom) && z > maxZoom) {
z = maxZoom;
}
let transformedExtent = extent;
if (modelMatrix && modelMatrixInverse && extent && !viewport.isGeospatial) {
transformedExtent = transformBox(extent, modelMatrix);
}
return viewport.isGeospatial
? getOSMTileIndices(viewport, z, zRange, extent)
: getIdentityTileIndices(
viewport,
z,
tileSize,
transformedExtent || DEFAULT_EXTENT,
modelMatrixInverse
);
}
/**
* Returns true if s is a valid URL template
*/
export function isURLTemplate(s) {
return /(?=.*{z})(?=.*{x})(?=.*({y}|{-y}))/.test(s);
}