Skip to content

Commit

Permalink
Merge pull request #6 from sat-utils/status_query
Browse files Browse the repository at this point in the history
version 0.4.0
  • Loading branch information
Alireza authored Oct 20, 2016
2 parents ea09e1d + 22da880 commit cc378e5
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 147 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ node_modules
#SERVERLESS
admin.env
.env
builder.js

#Ignore _meta folder
_meta
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ We use this library for creating sat-api using API Gateway. You can use this API
$ npm install
$ npm run test

We use [nock](https://github.com/node-nock/nock) to record and save API calls to the ElasticSearch instance to speed up tests and freeze results in time.

To change the behaviour of Nock, update `NOCK_BACK_MODE` to `wild`, `dryrun`, `record` or `lockdown`. More info [here](https://github.com/node-nock/nock#modes).

Default is `lockdown`.


### Express Example:

```js
Expand Down Expand Up @@ -47,4 +54,4 @@ app.listen(port, function() {
```

### About
Sat API Lib was made by [Development Seed](http://developmentseed.org).
Sat API Lib was made by [Development Seed](http://developmentseed.org).
29 changes: 29 additions & 0 deletions libs/aggregations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

var date = function (field) {
return {
scenes_by_date: {
date_histogram: {
format: 'YYYY-MM-dd',
interval: 'day',
field: field,
order: { '_key': 'desc' }
}
}
};
};

var term = function (field) {
var aggs = {};

aggs[`terms_${field}`] = {
terms: {
field: field
}
};

return aggs;
};

module.exports.date = date;
module.exports.term = term;
10 changes: 10 additions & 0 deletions libs/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var winston = require('winston');

var logger = new (winston.Logger)({
level: process.env.LOG_LEVEL || 'info',
transports: [
new (winston.transports.Console)({'timestamp': true})
]
});

module.exports = logger;
204 changes: 117 additions & 87 deletions libs/queries.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,105 @@
'use strict';

var _ = require('lodash');
var ejs = require('elastic.js');

var kinks = require('turf-kinks');
var gjv = require('geojson-validation');

var geojsonError = new Error('Invalid Geojson');

/**
* @apiDefine search
* @apiParam {string} [search] Supports Lucene search syntax for all available fields
* in the landsat meta data. <br> If search is used, all other parameters are ignored.
**/
var legacyParams = function (params, q) {
q.query(ejs.QueryStringQuery(params.search));
return q;
* checks if the polygon is valid, e.g. does not have self intersecting
* points
* @param {object} feature the geojson feature
* @return {boolean} returns true if the polygon is valid otherwise false
*/
var validatePolygon = function (feature) {
var ipoints = kinks(feature);

if (ipoints.features.length > 0) {
throw new Error('Invalid Polgyon: self-intersecting');
}
};

var legacyParams = function (params) {
return {
query_string: {
query: params.search
}
};
};

var geojsonQueryBuilder = function (feature, query) {
var shape = ejs.Shape(feature.geometry.type, feature.geometry.coordinates);
query = query.must(ejs.GeoShapeQuery()
.field('data_geometry')
.shape(shape));
var termQuery = function (field, value) {
var query = {
match: {}
};

query.match[field] = {
query: value,
lenient: false,
zero_terms_query: 'none'
};

return query;
};

/**
* @apiDefine contains
* @apiParam {string} [contains] Evaluates whether the given point is within the
* bounding box of a landsat image.
*
* Accepts `latitude` and `longitude`. They have to be separated by a `,`
* with no spaces in between. Example: `contains=23,21`
**/
var contains = function (params, query) {
var rangeQuery = function (field, frm, to) {
var query = {
range: {}
};

query.range[field] = {
gte: frm,
lte: to
};

return query;
};

var geoShaperQuery = function (field, geometry) {
var _geometry = Object.assign({}, geometry);

var query = {
geo_shape: {}
};

if (_geometry.type === 'Polygon') {
_geometry.type = _geometry.type.toLowerCase();
}

query.geo_shape[field] = {
shape: _geometry
};

return query;
};

var contains = function (params) {
var correctQuery = new RegExp('^[0-9\.\,\-]+$');
if (correctQuery.test(params)) {
var coordinates = params.split(',');
coordinates = coordinates.map(parseFloat);

if (coordinates[0] < -180 || coordinates[0] > 180) {
throw 'Invalid coordinates';
throw new Error('Invalid coordinates');
}

if (coordinates[1] < -90 || coordinates[1] > 90) {
throw 'Invalid coordinates';
throw new Error('Invalid coordinates');
}

var shape = ejs.Shape('circle', coordinates).radius('1km');

query = query.must(ejs.GeoShapeQuery()
.field('data_geometry')
.shape(shape));
return query;
return geoShaperQuery(
'data_geometry',
{
type: 'circle',
coordinates: coordinates,
radius: '1km'
}
);
} else {
throw 'Invalid coordinates';
throw new Error('Invalid coordinates');
}
};

/**
* @apiDefine intersects
* @apiParam {string/geojson} [intersects] Evaluates whether the give geojson is intersects
* with any landsat images.
*
* Accepts valid geojson.
**/
var intersects = function (geojson, query) {
var intersects = function (geojson, queries) {
// if we receive an object, assume it's GeoJSON, if not, try and parse
if (typeof geojson === 'string') {
try {
Expand All @@ -75,12 +110,11 @@ var intersects = function (geojson, query) {
}

if (gjv.valid(geojson)) {
// If it is smaller than Nigeria use geohash
// if (tools.areaNotLarge(geojson)) {
if (geojson.type === 'FeatureCollection') {
for (var i = 0; i < geojson.features.length; i++) {
var feature = geojson.features[i];
query = geojsonQueryBuilder(feature, query);
validatePolygon(feature);
queries.push(geoShaperQuery('data_geometry', feature.geometry));
}
} else {
if (geojson.type !== 'Feature') {
Expand All @@ -90,47 +124,30 @@ var intersects = function (geojson, query) {
'geometry': geojson
};
}
validatePolygon(geojson);

query = geojsonQueryBuilder(geojson, query);
queries.push(geoShaperQuery('data_geometry', geojson.geometry));
}
return query;
return queries;
} else {
throw geojsonError;
}
};

var rangeQuery = function (from, to, field, query) {
if (!_.isUndefined(from) && _.isString(from)) {
from = _.toLower(from);
}
module.exports = function (params) {
var response = {
query: { match_all: {} },
sort: [
{date: {order: 'desc'}}
]
};
var queries = [];

if (!_.isUndefined(to) && _.isString(to)) {
to = _.toLower(to);
}

if (!_.isUndefined(from) && !_.isUndefined(to)) {
return query.must(ejs.RangeQuery(field).gte(from).lte(to));
}

if (!_.isUndefined(from)) {
return query.must(ejs.RangeQuery(field).gte(from));
}
params = _.omit(params, ['limit', 'page', 'skip']);

if (!_.isUndefined(to)) {
return query.must(ejs.RangeQuery(field).lte(to));
if (Object.keys(params).length === 0) {
return response;
}
};

var matchQuery = function (field, param, query) {
return query.must(ejs.MatchQuery(field, param)
.lenient(false)
.zeroTermsQuery('none'));
};

module.exports = function (params, q) {
var query = ejs.BoolQuery();

params = _.omit(params, ['limit', 'page', 'skip']);

var rangeFields = {};

Expand All @@ -147,18 +164,20 @@ module.exports = function (params, q) {

// Do legacy search
if (params.search) {
return legacyParams(params, q);
response.query = legacyParams(params);
return response;
}

// contain search
if (params.contains) {
query = contains(params.contains, query);
queries.push(contains(params.contains));
} else {
params = _.omit(params, ['contains']);
}

// intersects search
if (params.intersects) {
query = intersects(params.intersects, query);
queries = intersects(params.intersects, queries);
params = _.omit(params, ['intersects']);
}

Expand Down Expand Up @@ -194,27 +213,38 @@ module.exports = function (params, q) {

// Range search
_.forEach(rangeFields, function (value, key) {
query = rangeQuery(
_.get(params, _.get(value, 'from')),
_.get(params, _.get(value, 'to')),
value['field'],
query
queries.push(
rangeQuery(
value.field,
_.get(params, _.get(value, 'from')),
_.get(params, _.get(value, 'to'))
)
);
params = _.omit(params, [_.get(value, 'from'), _.get(value, 'to')]);
});

// Term search
for (var i = 0; i < termFields.length; i++) {
if (_.has(params, termFields[i].parameter)) {
query = matchQuery(termFields[i].field, params[termFields[i].parameter], query);
params = _.omit(params, [termFields[i].parameter]);
queries.push(
termQuery(
termFields[i].field,
params[termFields[i].parameter]
)
);
}
}

// For all items that were not matched pass the key to the term query
_.forEach(params, function (value, key) {
query = matchQuery(key, value, query);
queries.push(termQuery(key, value));
});

return q.query(query);
response.query = {
bool: {
must: queries
}
};

return response;
};
Loading

0 comments on commit cc378e5

Please sign in to comment.