UNPKG

@allmaps/render

Version:

Render functions for WebGL and image buffers

351 lines (350 loc) 11.5 kB
import { sizeToResolution, squaredDistance } from "@allmaps/stdlib"; function getTileZoomLevelForScale(tileZoomLevels, resourceToCanvasScale, scaleFactorCorrection, log2ScaleFactorCorrection) { let smallestLog2ScaleFactorDiff = Number.POSITIVE_INFINITY; let bestTileZoomLevel = tileZoomLevels.at(-1); for (const tileZoomLevel of tileZoomLevels) { const log2ScaleFactorDiff = Math.abs( Math.log2(tileZoomLevel.scaleFactor) - (Math.log2(resourceToCanvasScale + scaleFactorCorrection) + log2ScaleFactorCorrection) ); if (log2ScaleFactorDiff < smallestLog2ScaleFactorDiff) { smallestLog2ScaleFactorDiff = log2ScaleFactorDiff; bestTileZoomLevel = tileZoomLevel; } } return bestTileZoomLevel; } function computeTilesCoveringRingAtTileZoomLevel(resourceRing, tileZoomLevel, imageSize) { const scaledResourceRing = scaleResourcePoints(resourceRing, tileZoomLevel); const tilesByColumn = ringToTilesByColumn(scaledResourceRing); const tiles = tilesByColumnToTiles(tilesByColumn, tileZoomLevel, imageSize); return tiles; } function scaleResourcePoints(resourcePoints, tileZoomLevel) { return resourcePoints.map((point) => [ point[0] / tileZoomLevel.originalWidth, point[1] / tileZoomLevel.originalHeight ]); } function ringToTilesByColumn(ring) { const tilesByColumn = {}; for (let i = 0; i < ring.length; i++) { const line = [ring[i], ring[(i + 1) % ring.length]]; const pixels = lineToPixels(line); pixels.forEach(([x, y]) => { if (!tilesByColumn[x]) { tilesByColumn[x] = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]; } if (y < tilesByColumn[x][0]) { tilesByColumn[x][0] = y; } if (y > tilesByColumn[x][1]) { tilesByColumn[x][1] = y; } }); } return tilesByColumn; } function lineToPixels([startPoint, endPoint]) { let startPixelX = Math.floor(startPoint[0]); let startPixelY = Math.floor(startPoint[1]); const endPixelX = Math.floor(endPoint[0]); const endPixelY = Math.floor(endPoint[1]); const points = [[startPixelX, startPixelY]]; if (startPixelX === endPixelX && startPixelY === endPixelY) { return points; } const stepX = Math.sign(endPoint[0] - startPoint[0]); const stepY = Math.sign(endPoint[1] - startPoint[1]); const restX = Math.abs(startPoint[0] - startPixelX - Math.max(0, stepX)); const restY = Math.abs(startPoint[1] - startPixelY - Math.max(0, stepY)); const distanceX = Math.abs(startPoint[0] - endPoint[0]); const distanceY = Math.abs(startPoint[1] - endPoint[1]); let restPerStepX = restX / distanceX; let restPerStepY = restY / distanceY; const onePerStepX = 1 / distanceX; const onePerStepY = 1 / distanceY; while (!(startPixelX === endPixelX && startPixelY === endPixelY)) { if (distanceY === 0) { startPixelX = startPixelX + stepX; } else if (distanceX === 0) { startPixelY = startPixelY + stepY; } else if (restPerStepX < restPerStepY) { restPerStepX = restPerStepX + onePerStepX; startPixelX = startPixelX + stepX; } else { restPerStepY = restPerStepY + onePerStepY; startPixelY = startPixelY + stepY; } points.push([startPixelX, startPixelY]); } return points; } function tilesByColumnToTiles(tilesByColumn, tileZoomLevel, imageSize) { const tiles = []; for (const xKey in tilesByColumn) { const x = parseInt(xKey); if (x < 0 || x >= tileZoomLevel.columns) { break; } const fromY = Math.max(tilesByColumn[x][0], 0); const toY = Math.min(tilesByColumn[x][1], tileZoomLevel.rows - 1); for (let y = fromY; y <= toY; y++) { tiles.push({ column: x, row: y, tileZoomLevel, imageSize }); } } return tiles; } function getTilesCoveringTileAtScaleFactor(tile, image, scaleFactor, validTile) { let columnStart = Math.floor( tile.column * tile.tileZoomLevel.scaleFactor / scaleFactor ); columnStart = columnStart >= 0 ? columnStart : 0; const columnEnd = Math.ceil( (tile.column + 1) * tile.tileZoomLevel.scaleFactor / scaleFactor ); let rowStart = Math.floor( tile.row * tile.tileZoomLevel.scaleFactor / scaleFactor ); rowStart = rowStart >= 0 ? rowStart : 0; const rowEnd = Math.ceil( (tile.row + 1) * tile.tileZoomLevel.scaleFactor / scaleFactor ); return getTilesAtScaleFactor( scaleFactor, image, columnStart, columnEnd, rowStart, rowEnd, validTile ); } function getTilesAtScaleFactor(scaleFactor, image, columnStart, columnEnd, rowStart, rowEnd, validTile = (_tile) => true) { const tileZoomLevel = image.tileZoomLevels.find( (tileZoomLevel2) => tileZoomLevel2.scaleFactor === scaleFactor ); const imageSize = [image.width, image.height]; if (!tileZoomLevel) { return []; } columnStart = columnStart ? columnStart : 0; columnEnd = columnEnd ? columnEnd : tileZoomLevel.columns; rowStart = rowStart ? rowStart : 0; rowEnd = rowEnd ? rowEnd : tileZoomLevel.rows; const tiles = []; for (let column = columnStart; column < columnEnd; column++) { for (let row = rowStart; row < rowEnd; row++) { const tile = { column, row, tileZoomLevel, imageSize }; if (validTile(tile)) { tiles.push(tile); } } } return tiles; } function squaredDistanceTileToPoint(tile, point) { return squaredDistance(tileCenter(tile), point); } function tileCenter(tile) { const bbox = computeBboxTile(tile); return [(bbox[2] - bbox[0]) / 2 + bbox[0], (bbox[3] - bbox[1]) / 2 + bbox[1]]; } function tileToTileOriginPoint(tile) { return [ tile.column * tile.tileZoomLevel.originalWidth, tile.row * tile.tileZoomLevel.originalHeight ]; } function clipTilePointToTile(tilePoint, tile) { const tileSize = [tile.tileZoomLevel.width, tile.tileZoomLevel.height]; return tilePoint.map((coordinate, index) => { coordinate = Math.max(coordinate, 0); coordinate = Math.min(coordinate, tileSize[index] - 1); return coordinate; }); } function resourcePointInTile(resourcePoint, tile) { const resourceTileOrigin = tileToTileOriginPoint(tile); return resourcePoint[0] >= resourceTileOrigin[0] && resourcePoint[0] <= resourceTileOrigin[0] + tile.tileZoomLevel.originalWidth && resourcePoint[1] >= resourceTileOrigin[1] && resourcePoint[1] <= resourceTileOrigin[1] + tile.tileZoomLevel.originalHeight; } function computeBboxTile(tile) { const resourceTileOriginPoint = tileToTileOriginPoint(tile); const resourceTileMaxX = Math.min( resourceTileOriginPoint[0] + tile.tileZoomLevel.originalWidth, tile.imageSize[0] ); const resourceTileMaxY = Math.min( resourceTileOriginPoint[1] + tile.tileZoomLevel.originalHeight, tile.imageSize[1] ); return [ resourceTileOriginPoint[0], resourceTileOriginPoint[1], resourceTileMaxX, resourceTileMaxY ]; } function getTileSize(tile) { return [tile.tileZoomLevel.width, tile.tileZoomLevel.height]; } function getTileResolution(tile) { return sizeToResolution(getTileSize(tile)); } function getTileZoomLevelResolution(tileZoomLevel) { return tileZoomLevel.rows * tileZoomLevel.width * tileZoomLevel.columns * tileZoomLevel.height; } function getTilesAtOtherScaleFactors(tile, image, scaleFactor, maxLowerLog2ScaleFactorDiff, maxHigherLog2ScaleFactorDiff, validTile) { const tilesAtOtherScaleFactors = []; const tilesAtLowerScaleFactor = recursivelyGetTilesAtLowerScaleFactor( tile, image, scaleFactor, maxLowerLog2ScaleFactorDiff, validTile ); for (const tileAtLowerScaleFactor of tilesAtLowerScaleFactor) { if (tileAtLowerScaleFactor) { tilesAtOtherScaleFactors.push(tileAtLowerScaleFactor); } } if (tilesAtOtherScaleFactors.length === 0) { const maxScaleFactor = Math.max( ...image.tileZoomLevels.map((tileZoomLevel) => tileZoomLevel.scaleFactor) ); const tileAtHigherScaleFactor = recursivelyGetTilesAtHigherScaleFactor( tile, image, scaleFactor, maxHigherLog2ScaleFactorDiff, maxScaleFactor, validTile ); if (tileAtHigherScaleFactor) { tilesAtOtherScaleFactors.push(tileAtHigherScaleFactor); } } return tilesAtOtherScaleFactors; } function recursivelyGetTilesAtHigherScaleFactor(tile, image, scaleFactor, log2ScaleFactorDiff, maxScaleFactor, validTile) { const higherScaleFactor = 2 ** (Math.log2(scaleFactor) + 1); if (higherScaleFactor > maxScaleFactor || log2ScaleFactorDiff <= 0) { return void 0; } const tileAtHigherScaleFactor = getTileAtHigherScaleFactor( tile, image, higherScaleFactor, validTile ); if (tileAtHigherScaleFactor !== void 0) { return tileAtHigherScaleFactor; } else { return recursivelyGetTilesAtHigherScaleFactor( tile, image, higherScaleFactor, log2ScaleFactorDiff--, maxScaleFactor, validTile ); } } function recursivelyGetTilesAtLowerScaleFactor(tile, image, scaleFactor, log2ScaleFactorDiff, validTile) { const lowerScaleFactor = 2 ** (Math.log2(scaleFactor) - 1); if (lowerScaleFactor <= 0 || log2ScaleFactorDiff <= 0) { return []; } const tilesAtLowerScaleFactor = getTilesAtLowerScaleFactor( tile, image, lowerScaleFactor, validTile ); const allTilesAtLowerScaleFactor = getTilesAtLowerScaleFactor( tile, image, lowerScaleFactor, (_tile) => true ); if (tilesAtLowerScaleFactor.length === allTilesAtLowerScaleFactor.length) { return tilesAtLowerScaleFactor; } else { return [ ...tilesAtLowerScaleFactor, ...recursivelyGetTilesAtLowerScaleFactor( tile, image, lowerScaleFactor, log2ScaleFactorDiff--, validTile ) ]; } } function getTileAtHigherScaleFactor(tile, image, higherScaleFactor, validTile) { const tilesCoveringTileAtHigherScaleFactor = getTilesCoveringTileAtScaleFactor(tile, image, higherScaleFactor, validTile); if (tilesCoveringTileAtHigherScaleFactor.length === 0) { return void 0; } return tilesCoveringTileAtHigherScaleFactor[0]; } function getTilesAtLowerScaleFactor(tile, image, lowerScaleFactor, validTile) { const tilesCoveringTileAtLowerScaleFactor = getTilesCoveringTileAtScaleFactor( tile, image, lowerScaleFactor, validTile ); return tilesCoveringTileAtLowerScaleFactor; } function fetchableTileKey(fetchableTile) { return keyFromMapIdTileUrl(fetchableTile.mapId, fetchableTile.tileUrl); } function keyFromMapIdTileUrl(mapId, tileUrl2) { return `${mapId}:${tileUrl2}`; } function tileKey(tile) { return keyFromScaleFactorRowColumn( tile.tileZoomLevel.scaleFactor, tile.row, tile.column ); } function keyFromScaleFactorRowColumn(scaleFactor, row, column) { return `${scaleFactor}:${row}:${column}`; } export { clipTilePointToTile, computeBboxTile, computeTilesCoveringRingAtTileZoomLevel, fetchableTileKey, getTileAtHigherScaleFactor, getTileResolution, getTileSize, getTileZoomLevelForScale, getTileZoomLevelResolution, getTilesAtLowerScaleFactor, getTilesAtOtherScaleFactors, getTilesAtScaleFactor, getTilesCoveringTileAtScaleFactor, keyFromMapIdTileUrl, keyFromScaleFactorRowColumn, recursivelyGetTilesAtHigherScaleFactor, recursivelyGetTilesAtLowerScaleFactor, resourcePointInTile, squaredDistanceTileToPoint, tileCenter, tileKey, tileToTileOriginPoint }; //# sourceMappingURL=tiles.js.map