diff --git a/packages/api/lib/controllers/boxesController.js b/packages/api/lib/controllers/boxesController.js index 37e331ea..3a2d88ab 100644 --- a/packages/api/lib/controllers/boxesController.js +++ b/packages/api/lib/controllers/boxesController.js @@ -200,6 +200,8 @@ const geoJsonStringifyReplacer = function geoJsonStringifyReplacer (key, box) { * @apiParam {Boolean="true","false"} [classify=false] if specified, the api will classify the boxes accordingly to their last measurements. * @apiParam {Boolean="true","false"} [minimal=false] if specified, the api will only return a minimal set of box metadata consisting of [_id, updatedAt, currentLocation, exposure, name] for a fast response. * @apiParam {Boolean="true","false"} [full=false] if true the API will return populated lastMeasurements (use this with caution for now, expensive on the database) + * @apiParam {String} [near] A comma separated coordinate, if specified, the api will only return senseBoxes within maxDistance (in m) of this location + * @apiParam {Number} [maxDistance=1000] the amount of meters around the near Parameter that the api will search for senseBoxes * @apiUse ExposureFilterParam * @apiUse BBoxParam * @apiSampleRequest https://api.opensensemap.org/boxes @@ -574,6 +576,8 @@ module.exports = { { name: 'classify', defaultValue: 'false', allowedValues: ['true', 'false'] }, { name: 'minimal', defaultValue: 'false', allowedValues: ['true', 'false'] }, { name: 'full', defaultValue: 'false', allowedValues: ['true', 'false'] }, + { name: 'near' }, + { name: 'maxDistance' }, { predef: 'bbox' }, ]), parseAndValidateTimeParamsForFindAllBoxes, diff --git a/packages/api/lib/helpers/userParamHelpers.js b/packages/api/lib/helpers/userParamHelpers.js index b04f2b2d..b2ebf713 100644 --- a/packages/api/lib/helpers/userParamHelpers.js +++ b/packages/api/lib/helpers/userParamHelpers.js @@ -385,6 +385,12 @@ const retrieveParametersPredefs = { 'bbox' () { return { name: 'bbox', dataType: 'bbox' }; }, + 'near' () { + return { name: 'near', dataType: 'as-is' }; + }, + 'maxDistance' () { + return { name: 'maxDistance', dataType: 'as-is' }; + }, 'location' () { return { name: 'location', dataType: ['location'], paramValidatorAndParser: retrieveLocationParameter }; // dataType array ['location'] is needed for setting the userparam correctly }, diff --git a/packages/models/src/box/box.js b/packages/models/src/box/box.js index 83115a6b..fef52999 100644 --- a/packages/models/src/box/box.js +++ b/packages/models/src/box/box.js @@ -938,7 +938,7 @@ boxSchema.methods.getLocations = function getLocations ({ format, fromDate, toDa }; const buildFindBoxesQuery = function buildFindBoxesQuery (opts = {}) { - const { phenomenon, fromDate, toDate, bbox } = opts, + const { phenomenon, fromDate, toDate, bbox, near, maxDistance } = opts, query = {}; // simple string parameters @@ -953,6 +953,19 @@ const buildFindBoxesQuery = function buildFindBoxesQuery (opts = {}) { query['locations'] = { '$geoWithin': { '$geometry': bbox } }; } + // near search parameter + if (near) { + query['currentLocation'] = { + '$near': { + '$geometry': { + type: 'Point', + coordinates: [near.split(',')[0], near.split(',')[1]] + }, + '$maxDistance': maxDistance ? maxDistance : 1000, + } + }; + } + // search for phenomenon only together with time params if (fromDate || toDate) { if (phenomenon) {