Skip to content

Commit

Permalink
abstracted GCS to generic file cache module
Browse files Browse the repository at this point in the history
  • Loading branch information
AdenForshaw committed Nov 9, 2024
1 parent d4bcf40 commit 71c451e
Show file tree
Hide file tree
Showing 20 changed files with 256 additions and 100 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "overture-maps-api",
"version": "0.0.9",
"version": "0.1.0",
"description": "",
"author": "",
"private": true,
Expand Down
8 changes: 6 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { CloudstorageCacheModule } from './cloudstorage-cache/cloudstorage-cache.module';
import { BuildingsModule } from './buildings/buildings.module';
import { PlacesModule } from './places/places.module';
import { PlacesService } from './places/places.service';
// src/app.module.ts

import { Module, NestMiddleware, MiddlewareConsumer, Logger, RequestMethod } from '@nestjs/common';
import { PlacesController } from './places/places.controller';
import { BigQueryService } from './bigquery/bigquery.service';
Expand All @@ -13,9 +15,11 @@ import { AppService } from './app.service';

@Module({
imports: [
CloudstorageCacheModule,
BuildingsModule,
PlacesModule, ConfigModule.forRoot()],
controllers: [AppController],
providers: [ BigQueryService, GcsService, AppService],
providers: [BigQueryService, GcsService, AppService],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
Expand Down
93 changes: 41 additions & 52 deletions src/bigquery/bigquery.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// src/bigquery/bigquery.service.ts

import { Injectable, Logger } from '@nestjs/common';
import { BigQuery } from '@google-cloud/bigquery';
import { Place } from '../places/interfaces/place.interface';
import { Building } from '../buildings/interfaces/building.interface';
import { parsePointToGeoJSON, parsePolygonToGeoJSON } from '../utils/geojson';

interface IQueryStatistics {
totalBytesProcessed: number;
Expand All @@ -28,14 +30,21 @@ export class BigQueryService {
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS,
});
}

private parseBuildingRow(row: any): Building {

this.logger.log(row);
return {

id: row.id,
geometry: parsePolygonToGeoJSON(row.geometry),
}
}

private parsePlaceRow(row: any): Place {
return {
id: row.id,
geometry: {
type: 'Point',
coordinates: this.parseGeometry(row.geometry),
},
geometry: parsePointToGeoJSON(row.geometry),
bbox: {
xmin: parseFloat(row.bbox.xmin),
xmax: parseFloat(row.bbox.xmax),
Expand Down Expand Up @@ -83,23 +92,6 @@ export class BigQueryService {
}
}

// Function to parse the "POINT" geometry string into coordinates array
private parseGeometry(geometry: string): number[] {
try{
//@ts-ignore
const match = geometry.value.match(/POINT\(([^ ]+) ([^ ]+)\)/);
if (match) {
const longitude = parseFloat(match[1]);
const latitude = parseFloat(match[2]);
return [longitude, latitude];
}
}catch(err){
this.logger.error(err);
this.logger.error(`Error parsing geometry: ${JSON.stringify(geometry)}`);
}
return [];
}

// New method to get brands based on country or lat/lng/radius
async getBrandsNearby(
country_code?: string,
Expand Down Expand Up @@ -199,40 +191,37 @@ export class BigQueryService {
latitude: number,
longitude: number,
radius: number = 1000,
min_confidence?: number,
limit?: number
): Promise<Place[]> {

let queryParts: string[] = [];

// Base query and distance calculation if latitude and longitude are provided
queryParts.push(`-- Overture Maps API: Get buildings nearby \n`);
queryParts.push(`SELECT *`);

if (latitude && longitude) {
queryParts.push(`, ST_Distance(geometry, ST_GeogPoint(${latitude}, ${longitude})) AS distance_m`);
}

queryParts.push(`FROM \`${SOURCE_DATASET}.place\``);

// Conditional filters
let whereClauses: string[] = [];

if (latitude && longitude && radius) {
whereClauses.push(`ST_DWithin(geometry, ST_GeogPoint(${longitude}, ${latitude}), ${radius})`);
}

whereClauses.push(`categories.primary = "building"`);

if (min_confidence) {
whereClauses.push(`confidence >= ${min_confidence}`);
}

// Combine where clauses
if (whereClauses.length > 0) {
queryParts.push(`WHERE ${whereClauses.join(' AND ')}`);
}

queryParts.push(
`-- Overture Maps API: Get Buildings Nearby
-- Step 1: define the search area as the largest neighborhood that contains the point
DECLARE search_area_geometry GEOGRAPHY;
SET search_area_geometry = (
SELECT
geometry
FROM
\`bigquery-public-data.overture_maps.division_area\`
WHERE
country = 'AU'
AND subtype = "neighborhood"
AND ST_INTERSECTS(geometry, ST_GeogPoint(${longitude}, ${latitude}))
ORDER BY
ST_AREA(geometry) DESC
LIMIT 1
);
-- Step 2: Select buildings within the search area
SELECT
*
FROM
\`bigquery-public-data.overture_maps.building\` AS s
WHERE ST_WITHIN(s.geometry, search_area_geometry) and ST_DWithin(geometry, ST_GeogPoint(${longitude}, ${latitude}), 1000)
`)
// Order by distance if latitude and longitude are provided
if (latitude && longitude) {
queryParts.push(`ORDER BY distance_m`);
Expand Down Expand Up @@ -271,7 +260,7 @@ export class BigQueryService {
queryParts.push(`SELECT *`);

if (latitude && longitude) {
queryParts.push(`, ST_Distance(geometry, ST_GeogPoint(${latitude}, ${longitude})) AS distance_m`);
queryParts.push(`, ST_Distance(geometry, ST_GeogPoint(${longitude}, ${latitude})) AS distance_m`);
}

queryParts.push(`FROM \`${SOURCE_DATASET}.place\``);
Expand Down
8 changes: 8 additions & 0 deletions src/buildings/buildings.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
https://docs.nestjs.com/controllers#controllers
*/

import { Controller } from '@nestjs/common';

@Controller()
export class BuildingsController { }
21 changes: 21 additions & 0 deletions src/buildings/buildings.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BuildingsService } from './buildings.service';
import { BuildingsController } from './buildings.controller';
import { BigQueryService } from '../bigquery/bigquery.service';
import { GcsService } from '../gcs/gcs.service';
import { ConfigModule } from '@nestjs/config';
/*
https://docs.nestjs.com/modules
*/

import { Module } from '@nestjs/common';
import { CloudstorageCacheModule } from '../cloudstorage-cache/cloudstorage-cache.module';

@Module({
imports: [ConfigModule, CloudstorageCacheModule],
controllers: [
BuildingsController],
providers: [
BuildingsService, BigQueryService, GcsService],
exports: [BuildingsService]
})
export class BuildingsModule { }
41 changes: 41 additions & 0 deletions src/buildings/buildings.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
https://docs.nestjs.com/providers#services
*/

import { Injectable, Logger } from '@nestjs/common';
import { BigQueryService } from '../bigquery/bigquery.service';
import { GcsService } from '../gcs/gcs.service';
import { IsAuthenticatedGuard } from '../guards/is-authenticated.guard';
import { AuthedUser, User } from '../decorators/authed-user.decorator';
import { ValidateLatLngUser } from '../decorators/validate-lat-lng-user.decorator';
import { ConfigService } from '@nestjs/config';
import { CloudStorageCacheService } from '../cloudstorage-cache/cloudstorage-cache.service';
import { GetBuildingsQuery } from './dto/get-buildings-query.dto';
import { Building } from './interfaces/building.interface';

@Injectable()
export class BuildingsService {

logger = new Logger('PlacesService');

constructor(
private readonly configService: ConfigService,
private readonly bigQueryService: BigQueryService,
private readonly cloudStorageCache: CloudStorageCacheService,
) {}

async getBuildings(query: GetBuildingsQuery): Promise<Building[]> {
const { lat, lng, radius } = query;

// Check if cached results exist in GCS
const cacheKey = `get-places-brands-${JSON.stringify(query)}`;
const cachedResult = await this.cloudStorageCache.getJSON(cacheKey);
if (cachedResult) {
return cachedResult;
}

const results = await this.bigQueryService.getBuildingsNearby( lat, lng, radius,1);
await this.cloudStorageCache.storeJSON (results,cacheKey);
return results;//
}
}
35 changes: 35 additions & 0 deletions src/buildings/dto/get-buildings-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsNumber, IsOptional, IsString, MaxLength, Min, MinLength, ValidateIf } from 'class-validator';

export class GetBuildingsQuery {

@ApiPropertyOptional({
description: 'Latitude coordinate. Required if country code is not provided.',
example: 40.7128,
})
@ValidateIf(o => !o.country)
@IsNumber()
lat?: number;

@ApiPropertyOptional({
description: 'Longitude coordinate. Required if country code is not provided.',
example: -74.0060,
})
@ValidateIf(o => !o.country)
@IsNumber()
lng?: number;

@ApiPropertyOptional({
description: 'Search radius in meters, defaulting to 1000 meters if not provided.',
example: 1000,
minimum: 1,
default: 1000,
})
@ValidateIf(o => !o.country)
@IsOptional()
@IsNumber()
@Min(1)
radius?: number = 1000; // Default radius is 1000 meters if not provided

}
5 changes: 5 additions & 0 deletions src/buildings/interfaces/building.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@


export interface Building {

}
Empty file.
16 changes: 16 additions & 0 deletions src/cloudstorage-cache/cloudstorage-cache.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
https://docs.nestjs.com/modules
*/

import { Module } from '@nestjs/common';
import { CloudStorageCacheService } from './cloudstorage-cache.service';
import { GcsService } from '../gcs/gcs.service';
import { ConfigModule } from '@nestjs/config';

@Module({
imports: [ConfigModule],
controllers: [],
providers: [CloudStorageCacheService, GcsService],
exports: [CloudStorageCacheService]
})
export class CloudstorageCacheModule {}
43 changes: 43 additions & 0 deletions src/cloudstorage-cache/cloudstorage-cache.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import { Injectable, Logger } from '@nestjs/common';
import { BigQuery } from '@google-cloud/bigquery';
import { Place } from '../places/interfaces/place.interface';
import { ConfigService } from '@nestjs/config';
import { GcsService } from '../gcs/gcs.service';
@Injectable()
export class CloudStorageCacheService {

constructor(
private readonly configService: ConfigService,
private readonly gcsService: GcsService) {

}


logger = new Logger('CloudStorageCacheService');

async getJSON (cacheKey: string): Promise<any[]|null> {
const gcs = new Storage();
const bucket = gcs.bucket(process.env.GCS_BUCKET);
const file = bucket.file(cacheKey);
try {
const data = await file.download();
return JSON.parse(data.toString());
} catch (error) {
return null;
}
}

async storeJSON(data: any[], cacheKey: string): Promise<boolean> {
const gcs = new Storage();
const bucket = gcs.bucket(process.env.GCS_BUCKET);
const file = bucket.file(cacheKey);
try {
await file.save(JSON.stringify(data));
return true
} catch (error) {
this.logger.error(`Error storing cache: ${error}`);
return false
}
}
}
3 changes: 2 additions & 1 deletion src/gcs/gcs.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/gcs/gcs.service.ts

import { Injectable, Logger } from '@nestjs/common';
import { Storage, Bucket } from '@google-cloud/storage';
import { createHash } from 'crypto';
Expand Down Expand Up @@ -35,6 +35,7 @@ export class GcsService {
}

async getJSON(fileName: string): Promise<any | null> {

const file = this.bucket.file(this.generateCacheFileName(fileName));

try {
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/main.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
Expand Down
2 changes: 1 addition & 1 deletion src/places/interfaces/place.interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/places/interfaces/place.interface.ts

export interface Place {
id: string;
geometry: Geometry;
Expand Down
Empty file.
2 changes: 1 addition & 1 deletion src/places/places.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// src/places/places.controller.ts

import { Controller, Get, Logger, Query, UseGuards } from '@nestjs/common';
import { BigQueryService } from '../bigquery/bigquery.service';
import { GcsService } from '../gcs/gcs.service';
Expand Down
3 changes: 2 additions & 1 deletion src/places/places.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { PlacesController } from './places.controller';
import { BigQueryService } from '../bigquery/bigquery.service';
import { GcsService } from '../gcs/gcs.service';
import { ConfigModule } from '@nestjs/config';
import { CloudstorageCacheModule } from '../cloudstorage-cache/cloudstorage-cache.module';

@Module({
imports: [ConfigModule],
imports: [ConfigModule, CloudstorageCacheModule],
controllers: [PlacesController],
providers: [PlacesService, BigQueryService, GcsService],
exports: [PlacesService]
Expand Down
Loading

0 comments on commit 71c451e

Please sign in to comment.