Skip to content

Commit

Permalink
Add cost surface tiles endpoint in geo-processing
Browse files Browse the repository at this point in the history
  • Loading branch information
yulia-bel committed Oct 6, 2023
1 parent 26bf0e8 commit da38b1a
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {
Controller,
Get,
Header,
Logger,
Param,
Query,
Res,
} from '@nestjs/common';
import { apiGlobalPrefixes } from '@marxan-geoprocessing/api.config';
import {
ApiBadRequestResponse,
ApiOperation,
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
import { BBox } from 'geojson';

import { Response } from 'express';
import { setTileResponseHeadersForSuccessfulRequests } from '@marxan/tiles';
import { CostSurfaceService, CostSurfaceTileRequest } from "@marxan-geoprocessing/modules/cost-surface/cost-surface.service";

@Controller(`${apiGlobalPrefixes.v1}/cost-surfaces`)
export class FeaturesController {
private readonly logger: Logger = new Logger(FeaturesController.name);

constructor(public service: CostSurfaceService) {}

@ApiOperation({
description: 'Get tile for a cost surface by id.',
})
@ApiParam({
name: 'z',
description: 'The zoom level ranging from 0 - 20',
type: Number,
required: true,
})
@ApiParam({
name: 'x',
description: 'The tile x offset on Mercator Projection',
type: Number,
required: true,
})
@ApiParam({
name: 'y',
description: 'The tile y offset on Mercator Projection',
type: Number,
required: true,
})
@ApiParam({
name: 'id',
description: 'Specific id of the cost surface',
type: String,
required: true,
})
@ApiQuery({
name: 'bbox',
description: 'Bounding box of the project',
type: [Number],
required: false,
example: [-1, 40, 1, 42],
})
@Get(':projectId/cost-surface/:costSurfaceId/preview/tiles/:z/:x/:y.mvt')
@ApiBadRequestResponse()
async getTile(
@Param() TileSpecification: CostSurfaceTileRequest,
@Res() response: Response,
): Promise<Response> {
const tile: Buffer = await this.service.findTile(
TileSpecification
);
setTileResponseHeadersForSuccessfulRequests(response);
return response.send(tile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Injectable, Logger, Inject } from '@nestjs/common';
import { TileService } from '@marxan-geoprocessing/modules/tile/tile.service';
import { InjectRepository } from '@nestjs/typeorm';
import { Brackets, Repository } from "typeorm";
import { GeoFeatureGeometry } from '@marxan/geofeatures';
import { IsArray, IsNumber, IsString, IsOptional, IsDefined } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { BBox } from 'geojson';
import { antimeridianBbox, nominatim2bbox } from '@marxan/utils/geo';

import { TileRequest } from '@marxan/tiles';
import { ProtectedAreaTileRequest } from "@marxan-geoprocessing/modules/protected-areas/protected-area-tile-request";
import { QueryResult } from "pg";
import { TileSpecification } from "@marxan-geoprocessing/modules/features/features.service";
import { CostSurfacePuDataEntity } from "@marxan/cost-surfaces";
export class CostSurfaceTileRequest extends TileRequest {
@ApiProperty()
@IsString()
projectId!: string;

@ApiProperty()
@IsString()
costSurfaceId!: string;
}

export class CostSurfaceFilters {
@IsOptional()
@IsArray()
@IsNumber({}, { each: true })
@Transform((value: string): BBox => JSON.parse(value))
bbox?: BBox;
}


@Injectable()
export class CostSurfaceService {
private readonly logger: Logger = new Logger(CostSurfaceService.name);

constructor(
@InjectRepository(GeoFeatureGeometry)
private readonly costSurfaceDataRepository: Repository<CostSurfacePuDataEntity>,
private readonly tileService: TileService,
) {}


buildFeaturesWhereQuery(id: string, bbox?: BBox): string {
let whereQuery = `cost_surface_id = '${id}'`;

if (bbox) {
const { westBbox, eastBbox } = antimeridianBbox(nominatim2bbox(bbox));
whereQuery += `AND
(st_intersects(
st_intersection(st_makeenvelope(${eastBbox}, 4326),
ST_MakeEnvelope(0, -90, 180, 90, 4326)),
the_geom
) or st_intersects(
st_intersection(st_makeenvelope(${westBbox}, 4326),
ST_MakeEnvelope(-180, -90, 0, 90, 4326)),
the_geom
))`;
}
return whereQuery;
}

public findTile(
tileSpecification: CostSurfaceTileRequest,
bbox?: BBox,
): Promise<Buffer> {
const { z, x, y, costSurfaceId } = tileSpecification;
const simplificationLevel = 360 / (Math.pow(2, z + 1) * 100);
const attributes = 'cost_surface_id, properties';
const table = `(select ST_RemoveRepeatedPoints((st_dump(the_geom)).geom, ${simplificationLevel}) as the_geom,
(coalesce(properties,'{}'::jsonb) || jsonb_build_object('cost', cost)) as properties,
cost_surface_id
from "${this.costSurfaceDataRepository.metadata.tableName}")
inner join projects_pu on project_pu.id = cost_surface_pu_dat.projects_pu_id`;

const customQuery = this.buildFeaturesWhereQuery(costSurfaceId, bbox);
return this.tileService.getTile({
z,
x,
y,
table,
customQuery,
attributes,
});
}
}

0 comments on commit da38b1a

Please sign in to comment.