1
0
Fork 0

issue #846: Add within_bounds function to AQL

This commit is contained in:
Jan Steemann 2014-11-04 12:40:14 +01:00
parent f920a0f8d4
commit 830da6dd51
3 changed files with 205 additions and 5 deletions

View File

@ -23,6 +23,32 @@ AQL offers the following functions to filter data based on [geo indexes](../Glos
the *distancename* argument. The result documents will contain the distance value in
an attribute of that name.
* *WITHIN_RECTANGLE(collection, latitude1, longitude1, latitude2, longitude2)*:
Returns all documents from collection *collection* that are positioned inside the bounding
rectangle with the points (*latitude1*, *longitude1*) and (*latitude2*, *longitude2*).
Note: these functions require the collection *collection* to have at least
one geo index. If no geo index can be found, calling this function will fail
with an error.
with an error.
- *IS_IN_POLYGON(polygon, latitude, longitude)*:
Returns `true` if the point (*latitude*, *longitude*) is inside the polygon specified in the
*polygon* parameter. *latitude* can also be specified as a list with two values. By default,
the first list element will be interpreted as the latitude value and the second list element
as the longitude value. This can be changed by setting the 3rd parameter to `true`.
*polygon* needs to be a list of points, with each point being a list with two values. The
first value of each point is considered to be the latitude value and the second to be the
longitude value, unless the 3rd parameter has a value of `true`. That means latitude and
longitude need to be specified in the same order in the *points* parameter as they are in
the search coordinate.
Examples:
/* will check if the point (lat 4, lon 7) is contained inside the polygon */
RETURN IS_IN_POLYGON([ [ 0, 0 ], [ 0, 10 ], [ 10, 10 ], [ 10, 0 ] ], 4, 7)
/* will check if the point (lat 4, lon 7) is contained inside the polygon */
RETURN IS_IN_POLYGON([ [ 0, 0 ], [ 0, 10 ], [ 10, 10 ], [ 10, 0 ] ], [ 4, 7 ])
/* will check if the point (lat 4, lon 7) is contained inside the polygon */
RETURN IS_IN_POLYGON([ [ 0, 0 ], [ 0, 10 ], [ 10, 10 ], [ 10, 0 ] ], [ 7, 4 ], true)

View File

@ -165,6 +165,8 @@ std::unordered_map<std::string, Function const> const Executor::FunctionNames{
// geo functions
{ "NEAR", Function("NEAR", "AQL_NEAR", "h,n,n|nz,s", false, true, false) },
{ "WITHIN", Function("WITHIN", "AQL_WITHIN", "h,n,n,n|s", false, true, false) },
{ "WITHIN_RECTANGLE", Function("WITHIN_RECTANGLE", "AQL_WITHIN_RECTANGLE", "h,d,d,d,d", false, true, false) },
{ "IS_IN_POLYGON", Function("IS_IN_POLYGON", "AQL_IS_IN_POLYGON", "d,d,l", true, false, true) },
// fulltext functions
{ "FULLTEXT", Function("FULLTEXT", "AQL_FULLTEXT", "h,s,s", false, true, false) },

View File

@ -1,4 +1,4 @@
/*jshint strict: false, unused: false */
/*jshint strict: false, unused: false, bitwise: false */
/*global require, exports, COMPARE_STRING, AQL_TO_BOOL, AQL_TO_NUMBER, AQL_TO_STRING, AQL_WARNING */
////////////////////////////////////////////////////////////////////////////////
@ -263,7 +263,7 @@ function INDEX (collection, indexTypes) {
for (j = 0; j < indexTypes.length; ++j) {
if (index.type === indexTypes[j]) {
return index.id;
return index;
}
}
}
@ -2788,7 +2788,7 @@ function AQL_NEAR (collection, latitude, longitude, limit, distanceAttribute) {
THROW("NEAR", INTERNAL.errors.ERROR_QUERY_GEO_INDEX_MISSING, collection);
}
var result = COLLECTION(collection).NEAR(idx, latitude, longitude, limit);
var result = COLLECTION(collection).NEAR(idx.id, latitude, longitude, limit);
if (distanceAttribute === null || distanceAttribute === undefined) {
return result.documents;
@ -2831,7 +2831,7 @@ function AQL_WITHIN (collection, latitude, longitude, radius, distanceAttribute)
THROW("WITHIN", INTERNAL.errors.ERROR_QUERY_GEO_INDEX_MISSING, collection);
}
var result = COLLECTION(collection).WITHIN(idx, latitude, longitude, radius);
var result = COLLECTION(collection).WITHIN(idx.id, latitude, longitude, radius);
if (distanceAttribute === null || distanceAttribute === undefined) {
return result.documents;
@ -2848,6 +2848,176 @@ function AQL_WITHIN (collection, latitude, longitude, radius, distanceAttribute)
return documents;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return documents within a bounding rectangle
////////////////////////////////////////////////////////////////////////////////
function AQL_WITHIN_RECTANGLE (collection, latitude1, longitude1, latitude2, longitude2) {
"use strict";
if (TYPEWEIGHT(latitude1) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(longitude1) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(latitude2) !== TYPEWEIGHT_NUMBER ||
TYPEWEIGHT(longitude2) !== TYPEWEIGHT_NUMBER) {
WARN("WITHIN_RECTANGLE", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return null;
}
var distanceMeters = function (lat1, lon1, lat2, lon2) {
var deltaLat = (lat2 - lat1) * Math.PI / 180;
var deltaLon = (lon2 - lon1) * Math.PI / 180;
var a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return 6378.137 /* radius of earth in kilometers */
* c
* 1000; // kilometers to meters;
};
var midpoint = [
latitude1 + (latitude2 - latitude1) * 0.5,
longitude1 + (longitude2 - longitude1) * 0.5
];
var idx = INDEX(COLLECTION(collection), [ "geo1", "geo2" ]);
if (idx === null) {
THROW(INTERNAL.errors.ERROR_QUERY_GEO_INDEX_MISSING, collection);
}
var diameter = distanceMeters(latitude1, longitude1, latitude2, longitude2);
var latLower, latUpper, lonLower, lonUpper;
if (latitude1 < latitude2) {
latLower = latitude1;
latUpper = latitude2;
}
else {
latLower = latitude2;
latUpper = latitude1;
}
if (longitude1 < longitude2) {
lonLower = longitude1;
lonUpper = longitude2;
}
else {
lonLower = longitude2;
lonUpper = longitude1;
}
var result = COLLECTION(collection).WITHIN(idx.id, midpoint[0], midpoint[1], diameter);
var documents = [ ];
if (idx.type === 'geo1') {
// geo1, we have both coordinates in a list
var attribute = idx.fields[0];
if (idx.geoJson) {
result.documents.forEach(function(doc) {
// check if within bounding rectangle
// first list value is longitude, then latitude
if (doc[attribute][1] >= latLower && doc[attribute][1] <= latUpper &&
doc[attribute][0] >= lonLower && doc[attribute][0] <= lonUpper) {
documents.push(doc);
}
});
}
else {
result.documents.forEach(function(doc) {
// check if within bounding rectangle
// first list value is latitude, then longitude
if (doc[attribute][0] >= latLower && doc[attribute][0] <= latUpper &&
doc[attribute][1] >= lonLower && doc[attribute][1] <= lonUpper) {
documents.push(doc);
}
});
}
}
else {
// geo2, we have dedicated latitude and longitude attributes
var latAtt = idx.fields[0], lonAtt = idx.fields[1];
result.documents.forEach(function(doc) {
// check if within bounding rectangle
if (doc[latAtt] >= latLower && doc[latAtt] <= latUpper &&
doc[lonAtt] >= lonLower && doc[lonAtt] <= lonUpper) {
documents.push(doc);
}
});
}
return documents;
}
////////////////////////////////////////////////////////////////////////////////
/// @brief return true if a point is contained inside a polygon
////////////////////////////////////////////////////////////////////////////////
function AQL_IS_IN_POLYGON (points, latitude, longitude) {
"use strict";
if (TYPEWEIGHT(points) !== TYPEWEIGHT_LIST) {
WARN("POINT_IN_POLYGON", INTERNAL.errors.ERROR_QUERY_LIST_EXPECTED);
return false;
}
var searchLat, searchLon, pointLat, pointLon, geoJson = false;
if (TYPEWEIGHT(latitude) === TYPEWEIGHT_LIST) {
geoJson = AQL_TO_BOOL(longitude);
if (geoJson) {
// first list value is longitude, then latitude
searchLat = latitude[1];
searchLon = latitude[0];
pointLat = 1;
pointLon = 0;
}
else {
// first list value is latitude, then longitude
searchLat = latitude[0];
searchLon = latitude[1];
pointLat = 0;
pointLon = 1;
}
}
else if (TYPEWEIGHT(latitude) === TYPEWEIGHT_NUMBER &&
TYPEWEIGHT(longitude) === TYPEWEIGHT_NUMBER) {
searchLat = latitude;
searchLon = longitude;
pointLat = 0;
pointLon = 1;
}
else {
WARN("POINT_IN_POLYGON", INTERNAL.errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH);
return false;
}
var i, j = points.length - 1;
var oddNodes = false;
for (i = 0; i < points.length; ++i) {
if (TYPEWEIGHT(points[i]) !== TYPEWEIGHT_LIST) {
continue;
}
if (((points[i][pointLat] < searchLat && points[j][pointLat] >= searchLat) ||
(points[j][pointLat] < searchLat && points[i][pointLat] >= searchLat)) &&
(points[i][pointLon] <= searchLon || points[j][pointLon] <= searchLon)) {
oddNodes ^= ((points[i][pointLon] + (searchLat - points[i][pointLat]) /
(points[j][pointLat] - points[i][pointLat]) *
(points[j][pointLon] - points[i][pointLon])) < searchLon);
}
j = i;
}
if (oddNodes) {
return true;
}
return false;
}
// -----------------------------------------------------------------------------
// --SECTION-- fulltext functions
// -----------------------------------------------------------------------------
@ -6720,6 +6890,8 @@ exports.AQL_STDDEV_SAMPLE = AQL_STDDEV_SAMPLE;
exports.AQL_STDDEV_POPULATION = AQL_STDDEV_POPULATION;
exports.AQL_NEAR = AQL_NEAR;
exports.AQL_WITHIN = AQL_WITHIN;
exports.AQL_WITHIN_RECTANGLE = AQL_WITHIN_RECTANGLE;
exports.AQL_IS_IN_POLYGON = AQL_IS_IN_POLYGON;
exports.AQL_FULLTEXT = AQL_FULLTEXT;
exports.AQL_PATHS = AQL_PATHS;
exports.AQL_SHORTEST_PATH = AQL_SHORTEST_PATH;