From eabf53524616845237b08831913879a4ad6aa885 Mon Sep 17 00:00:00 2001 From: Zack Pollard Date: Tue, 24 Sep 2024 13:05:08 +0100 Subject: [PATCH] wip: pmtiles server --- server/src/controllers/map.controller.ts | 92 ++---------------------- server/src/services/map.service.ts | 7 ++ 2 files changed, 14 insertions(+), 85 deletions(-) diff --git a/server/src/controllers/map.controller.ts b/server/src/controllers/map.controller.ts index a48ebda070..2079cb8774 100644 --- a/server/src/controllers/map.controller.ts +++ b/server/src/controllers/map.controller.ts @@ -1,10 +1,6 @@ -import { Controller, Get, NotFoundException, Param, HttpCode, HttpStatus, Query, Res } from '@nestjs/common'; +import { Controller, Get, HttpCode, HttpStatus, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; -import fsSync from 'node:fs'; -import fs from 'node:fs/promises'; -// @ts-expect-error test -import { PMTiles, RangeResponse, Source } from 'pmtiles'; import { AuthDto } from 'src/dtos/auth.dto'; import { MapMarkerDto, @@ -20,9 +16,6 @@ import { MapService } from 'src/services/map.service'; export class MapController { constructor(private service: MapService) {} - source = new FileSource('./resources/v1.pmtiles'); - pmtiles = new PMTiles(this.source); - @Get('markers') @Authenticated() getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise { @@ -36,82 +29,11 @@ export class MapController { return this.service.reverseGeocode(dto); } - @Get('tiles.json') - async getTilesJson() { - // eslint-disable-next-line unicorn/no-await-expression-member - return JSON.parse((await fs.readFile(`./resources/tiles.json`)).toString()); - } - - @Get('tiles/:z/:x/:y.:format') - async getTiles( - @Param('z') z: number, - @Param('x') x: number, - @Param('y') y: number, - @Param('format') format: string, - @Res({ passthrough: true }) res: Response, - ) { - // load data based on tile request - console.log('getting tile'); - const tile = await this.pmtiles.getZxy(Number(z), Number(x), Number(y)); - console.log('tile', tile); - if (!tile) { - throw new NotFoundException('Tile not found.'); - } - console.log('getting'); - const data = Buffer.from(tile.data); - console.log('got buffer'); - - // determine content-type header based on data - // (assume pbf for now) - console.log('getting header'); - const header = await this.pmtiles.getHeader(); - console.log('header', header); - switch (header.tileType) { - case 0: { - console.log('Unknown tile type.'); - break; - } - case 1: { - res.setHeader('Content-Type', 'application/x-protobuf'); - break; - } - } - - res.status(200).send(data); + @Authenticated() + @Get('pmtiles') + @HttpCode(HttpStatus.OK) + async getPMTiles(@Res() res: Response) { + const tileStream = await this.service.getLocalTilesStream(); + return tileStream.pipe(res); } } - -class FileSource implements Source { - filename: string; - fileDescriptor: number; - - constructor(filename: string) { - this.filename = filename; - this.fileDescriptor = fsSync.openSync(filename, 'r'); - } - - getKey() { - return this.filename; - } - - // helper async function to read in bytes from file - readBytesIntoBuffer = async (buffer: Buffer, offset: number) => - new Promise((resolve, reject) => { - fsSync.read(this.fileDescriptor, buffer, 0, buffer.length, offset, (err) => { - if (err) { - return reject(err); - } - resolve(); - }); - }); - - getBytes = async (offset: number, length: number) => { - // create buffer and read in byes from file - const buffer = Buffer.alloc(length); - await this.readBytesIntoBuffer(buffer, offset); - - const data = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength); - - return { data } as RangeResponse; - }; -} diff --git a/server/src/services/map.service.ts b/server/src/services/map.service.ts index 5836505e54..c91808ac1d 100644 --- a/server/src/services/map.service.ts +++ b/server/src/services/map.service.ts @@ -6,6 +6,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMapRepository } from 'src/interfaces/map.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface'; +import { IStorageRepository } from 'src/interfaces/storage.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { getMyPartnerIds } from 'src/utils/asset.util'; @@ -18,6 +19,7 @@ export class MapService { @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, @Inject(IMapRepository) private mapRepository: IMapRepository, @Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository, + @Inject(IStorageRepository) private storageRepository: IStorageRepository, ) { this.logger.setContext(MapService.name); this.configCore = SystemConfigCore.create(systemMetadataRepository, logger); @@ -43,6 +45,11 @@ export class MapService { return this.mapRepository.getMapMarkers(userIds, albumIds, options); } + async getLocalTilesStream() { + const readStream = await this.storageRepository.createReadStream('/usr/src/app/uploads/tiles/tiles.pmtiles'); + return readStream.stream; + } + async reverseGeocode(dto: MapReverseGeocodeDto) { const { lat: latitude, lon: longitude } = dto; // eventually this should probably return an array of results