Skip to content

Commit

Permalink
Added limit, place_lat, and place_lng params to taxa/suggest (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
kueda authored Dec 7, 2020
1 parent 29e5742 commit 27118b1
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 12 deletions.
5 changes: 3 additions & 2 deletions lib/controllers/v1/places_controller.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const _ = require( "lodash" );
const esClient = require( "../../es_client" );
const InaturalistAPI = require( "../../inaturalist_api" );
const util = require( "../../util" );

const PlacesController = { };

Expand Down Expand Up @@ -223,8 +224,7 @@ PlacesController.containing = async req => {
const floatLat = parseFloat( req.query.lat );
const floatLng = parseFloat( req.query.lng );
if ( ( !floatLat && floatLat !== 0 ) || ( !floatLng && floatLng !== 0 ) ) {
// { error: "Must provide `lat` and `lng`", status: 422 }
throw new Error( 422 );
throw util.httpError( 422, "Must provide `lat` and `lng`" );
}
const filters = [
{ range: { bbox_area: { gt: 0 } } },
Expand Down Expand Up @@ -275,6 +275,7 @@ module.exports = {
autocomplete: PlacesController.autocomplete,
containing: PlacesController.containing,
nearby: PlacesController.nearby,
nearbyQuery: PlacesController.nearbyQuery,
nearbyQueryBody: PlacesController.nearbyQueryBody,
show: PlacesController.show
};
26 changes: 26 additions & 0 deletions lib/controllers/v1/taxa_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,8 @@ TaxaController.suggestifyRequest = async req => {
// eslint-disable-next-line global-require
const ObservationsController = require( "./observations_controller" );
// eslint-disable-next-line global-require
const PlacesController = require( "./places_controller" );
// eslint-disable-next-line global-require
const Observation = require( "../../models/observation" );

req.suggestParams = req.query;
Expand Down Expand Up @@ -920,6 +922,27 @@ TaxaController.suggestifyRequest = async req => {
if ( obs[0].place_ids && obs[0].place_ids.length > 0 ) {
req.suggestParams.place_id = obs[0].place_ids[obs[0].place_ids.length - 1];
}
} else if (
!req.suggestParams.place_id
&& !req.suggestParams.lat
&& req.suggestParams.place_lat
&& req.suggestParams.place_lng
) {
const nearbyRsp = await PlacesController.containing( {
query: {
lat: req.suggestParams.place_lat,
lng: req.suggestParams.place_lng
}
} );
const standardPlace = _.last(
_.sortBy(
_.filter( nearbyRsp.results || [], p => [-1, 0, 1, 2].includes( p.admin_level ) ),
p => p.admin_level
)
);
if ( standardPlace ) {
req.suggestParams.place_id = standardPlace.id;
}
}
};

Expand Down Expand Up @@ -1112,6 +1135,9 @@ TaxaController.suggest = async req => {
req.suggestParams.order_by === "taxonomy"
? `${r.taxon.ancestry}/${r.taxon.id}` : ( r.score * -1 )
) );
if ( parseInt( req.suggestParams.limit, 0 ) > 0 ) {
response.results = _.slice( response.results, 0, parseInt( req.suggestParams.limit, 0 ) );
}
return Object.assign( response, {
query: req.suggestParams
} );
Expand Down
8 changes: 8 additions & 0 deletions openapi/schema/request/taxa_suggest.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = Joi.object( ).keys( {
featured_observation_id: Joi.string( ).guid( )
.description( "When `source` is `observations`, ignore this observation" ),
fields: Joi.any( ),
limit: Joi.number( ).min( 0 ).description( "Number of results to return" ),
lat: Joi.number( ).min( -90 ).max( 90 )
.description( "Coordinate used when fetching nearby results `source` is `visual` or `*observations`" ),
lng: Joi.number( ).min( -180 ).max( 180 )
Expand All @@ -29,6 +30,13 @@ module.exports = Joi.object( ).keys( {
Only retrieve suggestions from this place when \`source\` is \`checklist\`
or \`*observations\`
`.replace( /\s+/m, " " ) ),
place_lat: Joi.number( ).min( -90 ).max( 90 )
.description( `
Coordinate used to set a place filter when source is \`*observations\` by
choosing the place whose boundary contains the coordinate. Only chooses
from places curated by staff (aka "standard" places)
`.replace( /\s+/m, " " ) ),
place_lng: Joi.number( ).min( -180 ).max( 180 ).description( "See `place_lat`" ),
source: Joi.string( )
.valid(
"captive_observations",
Expand Down
116 changes: 116 additions & 0 deletions schema/fixtures.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,56 @@
"name": "a-place-in-a-place",
"display_name_autocomplete": "A Place In A Place",
"ancestor_place_ids": [432, 433]
},
{
"id": 2020120501,
"uuid": "9dc9c5a9-41b4-4a45-9a20-41c4a0be2ae8",
"name": "Alameda County",
"slug": "alameda-county",
"display_name_autocomplete": "Alameda County",
"location": "37.6505468421,-121.9178854495",
"admin_level": 2,
"bbox_area": 0.408537282384,
"geometry_geojson": {
"type": "MultiPolygon",
"coordinates": [
[
[
[-122.28088,37.70723],
[-122.373782,37.883725],
[-122.264027,37.903775],
[-122.217376,37.871724],
[-122.185977,37.820726],
[-122.045473,37.798126],
[-121.997771,37.763227],
[-122.011771,37.747428],
[-121.96077,37.718629],
[-121.55916,37.818927],
[-121.556655,37.542732],
[-121.501475,37.525003],
[-121.469275,37.489093],
[-121.865267,37.484637],
[-121.925041,37.454186],
[-121.944914,37.469163],
[-122.051244,37.459007],
[-122.109574,37.497637],
[-122.28088,37.70723]
]
]
]
},
"bounding_box_geojson": {
"type": "Polygon",
"coordinates": [
[
[-122.373782,37.454186],
[-122.373782,37.905824],
[-121.469214,37.905824],
[-121.469214,37.454186],
[-122.373782,37.454186]
]
]
}
}
]
},
Expand Down Expand Up @@ -974,6 +1024,72 @@
"private_geojson": { "type": "Point", "coordinates": [ 2, 1 ] },
"photo_licenses": [],
"photos_count": 1
},
{
"id": 2020120501,
"uuid": "e6cc40bf-8de3-4b89-bc05-0e05e59ad6d7",
"description": "Needs ID of taxon 6 in Massachusetts",
"user": {
"id": 123,
"login": "a-user",
"name": "A User"
},
"created_at": "2015-12-31T00:00:00",
"quality_grade": "needs_id",
"identification_categories": ["leading"],
"ident_taxon_ids": [6],
"license_code": "cc-by",
"taxon": {
"id": 6,
"uuid": "94dbefce-e621-4aae-85e0-c2f644c99091",
"iconic_taxon_id": 101,
"is_active": true,
"ancestor_ids": [1,2,3,4,6],
"min_species_ancestry": "1,2,3,4,6",
"min_species_taxon_id": 6,
"rank_level": 10,
"rank": "species"
},
"place_ids": [1,2],
"private_place_ids": [1,2],
"location": "42.7,-73.4",
"private_location": "42.7,-73.4",
"geojson": { "type": "Point", "coordinates": [ -73.4, 42.7 ] },
"private_geojson": { "type": "Point", "coordinates": [ -73.4, 42.7 ] }
},
{
"id": 2020120501,
"uuid": "e6cc40bf-8de3-4b89-bc05-0e05e59ad6d7",
"description": "Needs ID of taxon 7 in Alameda County",
"user": {
"id": 123,
"login": "a-user",
"name": "A User"
},
"created_at": "2015-12-31T00:00:00",
"quality_grade": "needs_id",
"identification_categories": ["leading"],
"ident_taxon_ids": [6],
"license_code": "cc-by",
"taxon": {
"ancestor_ids": [ 1001, 1, 2 ],
"iconic_taxon_id": 101,
"id": 7,
"is_active": true,
"min_species_ancestry": "1001,1,2,7",
"min_species_taxon_id": 7,
"name": "Los",
"parent_id": 1,
"rank": "species",
"rank_level": 10,
"uuid": "81bbc106-b372-4df5-af7d-50452cf9956e"
},
"place_ids": [2020120501],
"private_place_ids": [2020120501],
"location": "37.6505468421,-121.9178854495,",
"private_location": "37.6505468421,-121.9178854495,",
"geojson": { "type": "Point", "coordinates": [ -121.9178854495, 42.7 ] },
"private_geojson": { "type": "Point", "coordinates": [ -121.9178854495, 37.6505468421 ] }
}
]
},
Expand Down
8 changes: 3 additions & 5 deletions test/integration/v1/observations.js
Original file line number Diff line number Diff line change
Expand Up @@ -1194,16 +1194,14 @@ describe( "Observations", ( ) => {

it( "sorts by count desc by default", done => {
request( app ).get( "/v1/observations/species_counts" ).expect( res => {
expect( res.body.results[0].count ).to.eq( 2 );
expect( res.body.results[1].count ).to.eq( 1 );
expect( res.body.results[0].count ).to.be.at.least( res.body.results[1].count );
} ).expect( "Content-Type", /json/ )
.expect( 200, done );
} );

it( "can sort by count asc", done => {
request( app ).get( "/v1/observations/species_counts?order=asc" ).expect( res => {
expect( res.body.results[0].count ).to.eq( 1 );
expect( res.body.results[1].count ).to.eq( 2 );
expect( res.body.results[1].count ).to.be.at.least( res.body.results[0].count );
} ).expect( "Content-Type", /json/ )
.expect( 200, done );
} );
Expand Down Expand Up @@ -1351,7 +1349,7 @@ describe( "Observations", ( ) => {
it( "returns category counts", done => {
request( app ).get( "/v1/observations/identification_categories" ).expect( res => {
expect( res.body.results[0].category ).to.eq( "leading" );
expect( res.body.results[0].count ).to.eq( 1 );
expect( res.body.results[0].count ).to.be.at.least( 1 );
} ).expect( "Content-Type", /json/ )
.expect( 200, done );
} );
Expand Down
5 changes: 0 additions & 5 deletions test/integration/v1/places.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ describe( "Places", ( ) => {
.expect( res => {
const places = _.filter( fixtures.elasticsearch.places.place,
p => !_.isNil( p.geometry_geojson ) );
const standardPlaces = _.filter( places, p => p.admin_level !== null );
const communityPlaces = _.filter( places, p => p.admin_level === null );
expect( res.body.page ).to.eq( 1 );
expect( res.body.per_page ).to.eq( places.length );
expect( res.body.total_results ).to.eq( places.length );
expect( res.body.results.standard.length ).to.eq( standardPlaces.length );
expect( res.body.results.community.length ).to.eq( communityPlaces.length );
expect( res.body.results.standard[0].name ).to.eq( "United States" );
expect( res.body.results.standard[1].name ).to.eq( "Massachusetts" );
expect( res.body.results.community[0].name ).to.eq( communityPlaces[0].name );
Expand Down
45 changes: 45 additions & 0 deletions test/integration/v2/taxa.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,51 @@ describe( "Taxa", ( ) => {
} )
.expect( 200, done );
} );
it( "limit response size with the limit param", done => {
const limit = 1;
request( app )
.get( `/v2/taxa/suggest?source=observations&limit=${limit}` )
.set( "Authorization", token )
.expect( 200 )
.expect( res => {
expect( res.body.results.length ).to.eq( limit );
} )
.expect( 200, done );
} );
it( "sets place based on place_lat and place_lng params", done => {
const place = _.find( fixtures.elasticsearch.places.place, p => p.name === "Massachusetts" );
const taxonInPlace = _.find(
fixtures.elasticsearch.observations.observation,
o => (
( o.quality_grade === "needs_id" || o.quality_grade === "research" )
&& o.place_ids
&& o.place_ids.includes( place.id )
)
).taxon;
const taxonNotInPlace = _.find(
fixtures.elasticsearch.observations.observation,
o => (
( o.quality_grade === "needs_id" || o.quality_grade === "research" )
&& o.place_ids
&& !o.place_ids.includes( place.id )
)
).taxon;
const [placeLat, placeLng] = place.location.split( "," ).map( c => parseInt( c, 0 ) );
request( app )
.get( `/v2/taxa/suggest?source=observations&place_lat=${placeLat}&place_lng=${placeLng}` )
.set( "Authorization", token )
.expect( 200 )
.expect( res => {
expect( res.body.query.place_id ).to.eq( place.id );
expect(
_.find( res.body.results, r => r.taxon.id === taxonInPlace.id )
).not.to.be.undefined;
expect(
_.find( res.body.results, r => r.taxon.id === taxonNotInPlace.id )
).to.be.undefined;
} )
.expect( 200, done );
} );
describe( "with image upload", ( ) => {
const sandbox = sinon.createSandbox( );
afterEach( ( ) => sandbox.restore( ) );
Expand Down

0 comments on commit 27118b1

Please sign in to comment.