1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

wip: pmtiles server

This commit is contained in:
Zack Pollard 2024-09-24 13:05:08 +01:00
parent b0947bf9b9
commit eabf535246
2 changed files with 14 additions and 85 deletions

View file

@ -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 { ApiTags } from '@nestjs/swagger';
import { Response } from 'express'; 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 { AuthDto } from 'src/dtos/auth.dto';
import { import {
MapMarkerDto, MapMarkerDto,
@ -20,9 +16,6 @@ import { MapService } from 'src/services/map.service';
export class MapController { export class MapController {
constructor(private service: MapService) {} constructor(private service: MapService) {}
source = new FileSource('./resources/v1.pmtiles');
pmtiles = new PMTiles(this.source);
@Get('markers') @Get('markers')
@Authenticated() @Authenticated()
getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> { getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
@ -36,82 +29,11 @@ export class MapController {
return this.service.reverseGeocode(dto); return this.service.reverseGeocode(dto);
} }
@Get('tiles.json') @Authenticated()
async getTilesJson() { @Get('pmtiles')
// eslint-disable-next-line unicorn/no-await-expression-member @HttpCode(HttpStatus.OK)
return JSON.parse((await fs.readFile(`./resources/tiles.json`)).toString()); async getPMTiles(@Res() res: Response) {
} const tileStream = await this.service.getLocalTilesStream();
return tileStream.pipe(res);
@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);
} }
} }
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<void>((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;
};
}

View file

@ -6,6 +6,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMapRepository } from 'src/interfaces/map.interface'; import { IMapRepository } from 'src/interfaces/map.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { getMyPartnerIds } from 'src/utils/asset.util'; import { getMyPartnerIds } from 'src/utils/asset.util';
@ -18,6 +19,7 @@ export class MapService {
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
@Inject(IMapRepository) private mapRepository: IMapRepository, @Inject(IMapRepository) private mapRepository: IMapRepository,
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository, @Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
) { ) {
this.logger.setContext(MapService.name); this.logger.setContext(MapService.name);
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger); this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
@ -43,6 +45,11 @@ export class MapService {
return this.mapRepository.getMapMarkers(userIds, albumIds, options); 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) { async reverseGeocode(dto: MapReverseGeocodeDto) {
const { lat: latitude, lon: longitude } = dto; const { lat: latitude, lon: longitude } = dto;
// eventually this should probably return an array of results // eventually this should probably return an array of results