diff --git a/src/shelter/ShelterSearch.ts b/src/shelter/ShelterSearch.ts index 205f97e8..5215d37c 100644 --- a/src/shelter/ShelterSearch.ts +++ b/src/shelter/ShelterSearch.ts @@ -1,5 +1,6 @@ import { Prisma } from '@prisma/client'; +import { calculateGeolocationBounds } from '@/utils/utils'; import { SupplyPriority } from 'src/supply/types'; import { PrismaService } from '../prisma/prisma.service'; import { @@ -129,11 +130,31 @@ class ShelterSearch { }; } + get geolocation(): Prisma.ShelterWhereInput { + if (!this.formProps.geolocation) return {}; + + const { minLat, maxLat, minLong, maxLong } = calculateGeolocationBounds( + this.formProps.geolocation, + ); + + return { + latitude: { + gte: minLat, + lte: maxLat, + }, + longitude: { + gte: minLong, + lte: maxLong, + }, + }; + } + get query(): Prisma.ShelterWhereInput { if (Object.keys(this.formProps).length === 0) return {}; const queryData = { AND: [ this.cities, + this.geolocation, { OR: this.search }, { OR: this.shelterStatus }, this.priority(this.formProps.supplyIds), diff --git a/src/shelter/types/search.types.ts b/src/shelter/types/search.types.ts index 88ed410e..b87e653a 100644 --- a/src/shelter/types/search.types.ts +++ b/src/shelter/types/search.types.ts @@ -21,6 +21,14 @@ export type ShelterTagType = z.infer; export type ShelterTagInfo = z.infer; +export const GeolocationFilterSchema = z.object({ + latitude: z.coerce.number(), + longitude: z.coerce.number(), + radiusInMeters: z.coerce.number(), +}); + +export type GeolocationFilter = z.infer; + export const ShelterSearchPropsSchema = z.object({ search: z.string().optional(), priority: z.preprocess( @@ -32,6 +40,7 @@ export const ShelterSearchPropsSchema = z.object({ shelterStatus: z.array(ShelterStatusSchema).optional(), tags: ShelterTagInfoSchema.nullable().optional(), cities: z.array(z.string()).optional(), + geolocation: GeolocationFilterSchema.optional(), }); export type ShelterSearchProps = z.infer; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 772b3e12..dfcbc70f 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,5 @@ import { Logger } from '@nestjs/common'; +import { GeolocationFilter } from 'src/shelter/types/search.types'; class ServerResponse { readonly message: string; @@ -75,10 +76,45 @@ function deepMerge(target: Record, source: Record) { } } +interface Coordinates { + maxLat: number; + minLat: number; + maxLong: number; + minLong: number; +} + +function calculateGeolocationBounds({ + latitude, + longitude, + radiusInMeters, +}: GeolocationFilter): Coordinates { + const earthRadius = 6371000; + + const latRad = (latitude * Math.PI) / 180; + + const radiusRad = radiusInMeters / earthRadius; + + const maxLat = latitude + radiusRad * (180 / Math.PI); + const minLat = latitude - radiusRad * (180 / Math.PI); + + const deltaLong = Math.asin(Math.sin(radiusRad) / Math.cos(latRad)); + + const maxLong = longitude + deltaLong * (180 / Math.PI); + const minLong = longitude - deltaLong * (180 / Math.PI); + + return { + maxLat, + minLat, + maxLong, + minLong, + }; +} + export { ServerResponse, - removeNotNumbers, - getSessionData, - deepMerge, + calculateGeolocationBounds, capitalize, + deepMerge, + getSessionData, + removeNotNumbers, };