diff --git a/__tests__/e2e/trees.spec.ts b/__tests__/e2e/trees.spec.ts index 8662ec01..dc9f0c74 100644 --- a/__tests__/e2e/trees.spec.ts +++ b/__tests__/e2e/trees.spec.ts @@ -135,4 +135,16 @@ describe('trees', () => { }, 1000 * 30, ); + + it( + 'trees?lat=39.0619&lon=-122.6064&lat=38.1519&lon=-123.3139&lat=37.8879&lon=-121.1001', + async () => { + const response = await supertest(app).get( + '/trees?lat=39.0619&lon=-122.6064&lat=38.1519&lon=-123.3139&lat=37.8879&lon=-121.1001', + ); + expect(response.status).toBe(200); + expect(response.body.trees.length).toBe(145); + }, + 1000 * 30, + ); }); diff --git a/server/infra/database/TreeRepository.ts b/server/infra/database/TreeRepository.ts index 4506b2ed..5cea75c2 100644 --- a/server/infra/database/TreeRepository.ts +++ b/server/infra/database/TreeRepository.ts @@ -304,4 +304,41 @@ export default class TreeRepository extends BaseRepository { const object = await this.session.getDB().raw(sql); return object.rows; } + + async getByGeometry( + geometry: { lat: Array; lon: Array }, + totalCount = false, + ) { + const pointArray = geometry.lon.map((item, i) => `ST_MakePoint(${item}, ${geometry.lat[i]})`); + pointArray.push(`ST_MakePoint(${geometry.lon[0]}, ${geometry.lat[0]})`); + + if (totalCount) { + const totalSql = ` + SELECT + COUNT(*) + FROM trees t + WHERE + ST_CONTAINS( + ST_SETSRID(ST_CONVEXHULL(ST_MAKELINE(ARRAY[${pointArray.toString()}])), 4326), + ST_SETSRID(ST_POINT(t.lon, t.lat), 4326) + ) + `; + const total = await this.session.getDB().raw(totalSql); + return parseInt(total.rows[0].count.toString()); + } + + const sql = ` + SELECT + * + FROM trees t + WHERE + ST_CONTAINS( + ST_SETSRID(ST_CONVEXHULL(ST_MAKELINE(ARRAY[${pointArray.toString()}])), 4326), + ST_SETSRID(ST_POINT(t.lon, t.lat), 4326) + ) + `; + + const object = await this.session.getDB().raw(sql); + return object.rows; + } } diff --git a/server/models/Tree.ts b/server/models/Tree.ts index 369b7de0..7df4d2bc 100644 --- a/server/models/Tree.ts +++ b/server/models/Tree.ts @@ -9,6 +9,7 @@ type Filter = Partial<{ date_range: { startDate: string; endDate: string }; tag: string; wallet_id: string; + geometry: { lat: Array; lon: Array }; }>; function getByFilter( @@ -41,6 +42,11 @@ function getByFilter( const trees = await treeRepository.getByWallet(filter.wallet_id, options); return trees; } + if (filter.geometry) { + log.warn('using geometry filter...'); + const trees = await treeRepository.getByGeometry(filter.geometry); + return trees; + } const trees = await treeRepository.getByFilter(filter, options); return trees; @@ -84,6 +90,11 @@ function countByFilter( return total; } + if (filter.geometry) { + log.warn('using geometry filter...'); + const total = await treeRepository.getByGeometry(filter.geometry, true); + return total; + } const total = await treeRepository.countByFilter(filter); return total; }; diff --git a/server/routers/treesRouter.ts b/server/routers/treesRouter.ts index efb09bef..92f13992 100644 --- a/server/routers/treesRouter.ts +++ b/server/routers/treesRouter.ts @@ -15,6 +15,7 @@ type Filter = Partial<{ tag: string; wallet_id: string; active: true; + geometry: { lat: Array; lon: Array }; }>; router.get( @@ -70,6 +71,8 @@ router.get( offset: Joi.number().integer().min(0), startDate: Joi.string().regex(/^\d{4}-\d{2}-\d{2}$/), endDate: Joi.string().regex(/^\d{4}-\d{2}-\d{2}$/), + lat: Joi.array().items(Joi.number()), + lon: Joi.array().items(Joi.number()), }), ); const { @@ -83,6 +86,8 @@ router.get( endDate, tag, wallet_id, + lat, + lon, } = req.query; const repo = new TreeRepository(new Session()); const filter: Filter = { active: true }; @@ -105,6 +110,8 @@ router.get( filter.tag = tag; } else if (wallet_id) { filter.wallet_id = wallet_id; + } else if (lat && lon) { + filter.geometry = { lat, lon }; } const result = await TreeModel.getByFilter(repo)(filter, options);