forked from VinokurovVE/tests
167 lines
7.2 KiB
TypeScript
167 lines
7.2 KiB
TypeScript
import sharp from "sharp"
|
|
import { epsg3857extent } from "../constants"
|
|
import { Coordinate, Extent } from "../interfaces/map"
|
|
import path from "path"
|
|
import fs from 'fs'
|
|
|
|
function getTilesPerSide(zoom: number) {
|
|
return Math.pow(2, zoom)
|
|
}
|
|
|
|
function normalize(value: number, min: number, max: number) {
|
|
return (value - min) / (max - min)
|
|
}
|
|
|
|
function getTileIndex(normalized: number, tilesPerSide: number) {
|
|
return Math.floor(normalized * tilesPerSide)
|
|
}
|
|
|
|
function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) {
|
|
const tilesPerSide = getTilesPerSide(zoom)
|
|
const minX = extent[0]
|
|
const minY = extent[1]
|
|
const maxX = extent[2]
|
|
const maxY = extent[3]
|
|
const xNormalized = normalize(x, minX, maxX)
|
|
const yNormalized = normalize(y, minY, maxY)
|
|
const tileX = getTileIndex(xNormalized, tilesPerSide)
|
|
const tileY = getTileIndex(1 - yNormalized, tilesPerSide)
|
|
return { tileX, tileY }
|
|
}
|
|
|
|
function calculateRotationAngle(bottomLeft: Coordinate, bottomRight: Coordinate) {
|
|
const deltaX = bottomRight.x - bottomLeft.x
|
|
const deltaY = bottomRight.y - bottomLeft.y
|
|
const angle = -Math.atan2(deltaY, deltaX)
|
|
return angle
|
|
}
|
|
|
|
function roundUpToNearest(number: number, mod: number) {
|
|
return Math.floor(number / mod) * mod
|
|
}
|
|
|
|
export async function generateTilesForZoomLevel(uploadDir: string, tileFolder: string, file: Express.Multer.File, polygonExtent: Extent, bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate, zoomLevel: number) {
|
|
const angleDegrees = calculateRotationAngle(bottomLeft, bottomRight) * 180 / Math.PI
|
|
|
|
const { tileX: blX, tileY: blY } = getGridCellPosition(bottomLeft.x, bottomLeft.y, epsg3857extent, zoomLevel)
|
|
const { tileX: tlX, tileY: tlY } = getGridCellPosition(topLeft.x, topLeft.y, epsg3857extent, zoomLevel)
|
|
const { tileX: trX, tileY: trY } = getGridCellPosition(topRight.x, topRight.y, epsg3857extent, zoomLevel)
|
|
const { tileX: brX, tileY: brY } = getGridCellPosition(bottomRight.x, topRight.y, epsg3857extent, zoomLevel)
|
|
|
|
const minX = Math.min(blX, tlX, trX, brX)
|
|
const maxX = Math.max(blX, tlX, trX, brX)
|
|
const minY = Math.min(blY, tlY, trY, brY)
|
|
const maxY = Math.max(blY, tlY, trY, brY)
|
|
|
|
const mapWidth = Math.abs(epsg3857extent[0] - epsg3857extent[2])
|
|
const mapHeight = Math.abs(epsg3857extent[1] - epsg3857extent[3])
|
|
|
|
const tilesH = Math.sqrt(Math.pow(4, zoomLevel))
|
|
const tileWidth = mapWidth / (Math.sqrt(Math.pow(4, zoomLevel)))
|
|
const tileHeight = mapHeight / (Math.sqrt(Math.pow(4, zoomLevel)))
|
|
|
|
|
|
let minPosX = minX - (tilesH / 2)
|
|
let maxPosX = maxX - (tilesH / 2) + 1
|
|
let minPosY = -(minY - (tilesH / 2))
|
|
let maxPosY = -(maxY - (tilesH / 2) + 1)
|
|
|
|
const newMinX = tileWidth * minPosX
|
|
const newMaxX = tileWidth * maxPosX
|
|
const newMinY = tileHeight * maxPosY
|
|
const newMaxY = tileHeight * minPosY
|
|
|
|
|
|
const paddingLeft = Math.abs(polygonExtent[0] - newMinX)
|
|
const paddingRight = Math.abs(polygonExtent[2] - newMaxX)
|
|
const paddingTop = Math.abs(polygonExtent[3] - newMaxY)
|
|
const paddingBottom = Math.abs(polygonExtent[1] - newMinY)
|
|
|
|
const pixelWidth = Math.abs(minX - (maxX + 1)) * 256
|
|
const pixelHeight = Math.abs(minY - (maxY + 1)) * 256
|
|
|
|
const width = Math.abs(newMinX - newMaxX)
|
|
|
|
try {
|
|
let perPixel = width / pixelWidth
|
|
|
|
// constraint to original image width
|
|
const imageMetadata = await sharp(file.path).metadata().then(res => {
|
|
if (res.width) {
|
|
perPixel = pixelWidth <= res.width ? perPixel : width / res.width
|
|
}
|
|
})
|
|
|
|
const paddingLeftPixel = paddingLeft / perPixel
|
|
const paddingRightPixel = paddingRight / perPixel
|
|
const paddingTopPixel = paddingTop / perPixel
|
|
const paddingBottomPixel = paddingBottom / perPixel
|
|
|
|
const boundsWidthPixel = Math.abs(polygonExtent[0] - polygonExtent[2]) / perPixel
|
|
const boundsHeightPixel = Math.abs(polygonExtent[1] - polygonExtent[3]) / perPixel
|
|
|
|
if (!fs.existsSync(path.join(tileFolder, 'custom', zoomLevel.toString()))) {
|
|
fs.mkdirSync(path.join(tileFolder, 'custom', zoomLevel.toString()), { recursive: true });
|
|
}
|
|
|
|
const initialZoomImage = await sharp(path.join(uploadDir, file.filename))
|
|
.rotate(Math.ceil(angleDegrees), {
|
|
background: '#00000000'
|
|
})
|
|
.resize({
|
|
width: Math.ceil(boundsWidthPixel),
|
|
height: Math.ceil(boundsHeightPixel),
|
|
background: '#00000000'
|
|
})
|
|
.extend({
|
|
top: Math.ceil(paddingTopPixel),
|
|
left: Math.ceil(paddingLeftPixel),
|
|
bottom: Math.ceil(paddingBottomPixel),
|
|
right: Math.ceil(paddingRightPixel),
|
|
background: '#00000000'
|
|
})
|
|
.toFormat('png')
|
|
.toBuffer({ resolveWithObject: true })
|
|
|
|
if (initialZoomImage) {
|
|
await sharp(initialZoomImage.data.buffer)
|
|
.resize({
|
|
width: roundUpToNearest(Math.ceil(boundsWidthPixel) + Math.ceil(paddingLeftPixel) + Math.ceil(paddingRightPixel), Math.abs(minX - (maxX + 1))),
|
|
height: roundUpToNearest(Math.ceil(boundsHeightPixel) + Math.ceil(paddingTopPixel) + Math.ceil(paddingBottomPixel), Math.abs(minY - (maxY + 1))),
|
|
})
|
|
.toFile(path.join(tileFolder, 'custom', zoomLevel.toString(), zoomLevel.toString() + '.png'))
|
|
.then(async (res) => {
|
|
let left = 0
|
|
for (let x = minX; x <= maxX; x++) {
|
|
if (!fs.existsSync(path.join(tileFolder, 'custom', zoomLevel.toString(), x.toString()))) {
|
|
fs.mkdirSync(path.join(tileFolder, 'custom', zoomLevel.toString(), x.toString()), { recursive: true });
|
|
}
|
|
|
|
let top = 0
|
|
for (let y = minY; y <= maxY; y++) {
|
|
console.log(`z: ${zoomLevel} x: ${x} y: ${y}`)
|
|
|
|
try {
|
|
await sharp(path.join(tileFolder, 'custom', zoomLevel.toString(), zoomLevel.toString() + '.png'))
|
|
.extract({
|
|
width: res.width / Math.abs(minX - (maxX + 1)),
|
|
height: res.height / Math.abs(minY - (maxY + 1)),
|
|
left: left,
|
|
top: top
|
|
})
|
|
.toFile(path.join(tileFolder, 'custom', zoomLevel.toString(), x.toString(), y.toString() + '.png'))
|
|
.then(() => {
|
|
top = top + res.height / Math.abs(minY - (maxY + 1))
|
|
})
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
}
|
|
left = left + res.width / Math.abs(minX - (maxX + 1))
|
|
}
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.log(error)
|
|
}
|
|
} |