mirror of https://gitee.com/bigwinds/arangodb
1284 lines
53 KiB
JavaScript
1284 lines
53 KiB
JavaScript
/*jshint globalstrict:false, strict:false, maxlen: 500 */
|
|
/*global assertEqual */
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tests for query language, geo queries
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright 2010-2012 triagens GmbH, Cologne, Germany
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// @author Jan Steemann
|
|
/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
var jsunity = require("jsunity");
|
|
var db = require("@arangodb").db;
|
|
var errors = require("@arangodb").errors;
|
|
var helper = require("@arangodb/aql-helper");
|
|
var getQueryResults = helper.getQueryResults;
|
|
var assertQueryError = helper.assertQueryError;
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test suite
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
function ahuacatlLegacyGeoTestSuite () {
|
|
var locations = null;
|
|
var locationsNon = null;
|
|
|
|
function runQuery (query) {
|
|
var result = getQueryResults(query);
|
|
|
|
result = result.map(function(row) {
|
|
var r;
|
|
if (row !== null && typeof row === 'object') {
|
|
for (r in row) {
|
|
if (row.hasOwnProperty(r)) {
|
|
var value = row[r];
|
|
if (typeof(value) === "number") {
|
|
if (value !== parseFloat(parseInt(value))) {
|
|
row[r] = Number(value).toFixed(5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return row;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
return {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
setUp : function () {
|
|
var lat, lon;
|
|
db._drop("UnitTestsAhuacatlLocations");
|
|
db._drop("UnitTestsAhuacatlLocationsNon");
|
|
|
|
locations = db._create("UnitTestsAhuacatlLocations");
|
|
for (lat = -40; lat <= 40; ++lat) {
|
|
for (lon = -40; lon <= 40; ++lon) {
|
|
locations.save({"latitude" : lat, "longitude" : lon });
|
|
}
|
|
}
|
|
|
|
locations.ensureGeoIndex("latitude", "longitude");
|
|
|
|
//locations without index
|
|
locationsNon = db._create("UnitTestsAhuacatlLocationsNon");
|
|
for (lat = -40; lat <= 40; ++lat) {
|
|
for (lon = -40; lon <= 40; ++lon) {
|
|
locationsNon.save({"latitude" : lat, "longitude" : lon });
|
|
}
|
|
}
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tear down
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
tearDown : function () {
|
|
db._drop("UnitTestsAhuacatlLocations");
|
|
db._drop("UnitTestsAhuacatlLocationsNon");
|
|
},
|
|
|
|
testLegacyNearInvalidCoordinate : function () {
|
|
assertQueryError(errors.ERROR_QUERY_INVALID_GEO_VALUE.code, "RETURN NEAR(" + locations.name() + ", 1000, 1000, 10)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyNear1 : function () {
|
|
var expected = [ { "distance" : "111194.92664", "latitude" : -1, "longitude" : 0 }, { "distance" : "111194.92664", "latitude" : 0, "longitude" : -1 }, { "distance" : "111194.92664", "latitude" : 0, "longitude" : 1 }, { "distance" : "111194.92664", "latitude" : 1, "longitude" : 0 }, { "distance" : 0, "latitude" : 0, "longitude" : 0 } ];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", 0, 0, 5, \"distance\") SORT x.distance DESC, x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyNear2 : function () {
|
|
var expected = [ { "latitude" : -10, "longitude" : 24 }, { "latitude" : -10, "longitude" : 25 }, { "latitude" : -10, "longitude" : 26 } ];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -10, 25, 3) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyNear3 : function () {
|
|
var expected = [ { "distance" : "14891044.54146", "latitude" : 40, "longitude" : -40 },
|
|
{ "distance" : "14853029.30724", "latitude" : 40, "longitude" : -39 },
|
|
{ "distance" : "14815001.47646", "latitude" : 40, "longitude" : -38 } ];
|
|
var query = "FOR x IN NEAR(" + locations.name() + ", -70, 70, 10000, \"distance\") " +
|
|
"SORT x.distance DESC LIMIT 3 RETURN x";
|
|
var actual = runQuery(query);
|
|
assertEqual(expected, actual);
|
|
|
|
expected = [ { "distance" : "4487652.12954", "latitude" : -37, "longitude" : 26 },
|
|
{ "distance" : "4485565.93668", "latitude" : -39, "longitude" : 20 },
|
|
{ "distance" : "4484371.86154", "latitude" : -38, "longitude" : 23 } ];
|
|
query = "FOR x IN NEAR(" + locations.name() + ", -70, 70, 100, \"distance\") "+
|
|
"SORT x.distance DESC LIMIT 3 RETURN x";
|
|
actual = runQuery(query);
|
|
assertEqual(expected, actual);
|
|
|
|
//until changed null will result in the default value of 100
|
|
//expected = [ { "distance" : "14891044.54146", "latitude" : 40, "longitude" : -40 },
|
|
// { "distance" : "14853029.30724", "latitude" : 40, "longitude" : -39 },
|
|
// { "distance" : "14815001.47646", "latitude" : 40, "longitude" : -38 } ];
|
|
query = "FOR x IN NEAR(" + locations.name() + ", -70, 70, null, \"distance\") "+
|
|
"SORT x.distance DESC LIMIT 3 RETURN x";
|
|
actual = runQuery(query);
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyNear4 : function () {
|
|
var expected = [ {"latitude" : -40, "longitude" : 40 }, { "latitude" : -40, "longitude" : 39 }, { "latitude" : -40, "longitude" : 38 } ];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, null) SORT x.latitude, x.longitude DESC LIMIT 3 RETURN x");
|
|
assertEqual(expected, actual);
|
|
|
|
expected = [ { "latitude" : -40, "longitude" : 40 }, { "latitude" : -40, "longitude" : 39 } ];
|
|
actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, 2) SORT x.latitude, x.longitude DESC LIMIT 3 RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
testLegacyWithinInvalidCoordinate : function () {
|
|
assertQueryError(errors.ERROR_QUERY_INVALID_GEO_VALUE.code, "RETURN WITHIN(" + locations.name() + ", 1000, 1000, 100000)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyWithin1 : function () {
|
|
var expected = [ { "distance" : 0, "latitude" : 0, "longitude" : 0 } ];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", 0, 0, 10000, \"distance\") SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyWithin2 : function () {
|
|
var expected = [ { "distance" : "111194.92664", "latitude" : -1, "longitude" : 0 }, { "distance" : "111194.92664", "latitude" : 0, "longitude" : -1 }, { "distance" : 0, "latitude" : 0, "longitude" : 0 }, { "distance" : "111194.92664", "latitude" : 0, "longitude" : 1 }, { "distance" : "111194.92664", "latitude" : 1, "longitude" : 0 } ];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", 0, 0, 150000, \"distance\") SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyWithin3 : function () {
|
|
var expected = [ { "latitude" : -10, "longitude" : 25 } ];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -10, 25, 10000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyWithin4 : function () {
|
|
var expected = [
|
|
{ "latitude" : -11, "longitude" : 25 },
|
|
{ "latitude" : -10, "longitude" : 24 },
|
|
{ "latitude" : -10, "longitude" : 25 },
|
|
{ "latitude" : -10, "longitude" : 26 },
|
|
{ "latitude" : -9, "longitude" : 25 }
|
|
];
|
|
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -10, 25, 150000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyWithin5 : function () {
|
|
var expected = [ ];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -90, 90, 10000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test without geo index available
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyNonIndexed : function () {
|
|
assertQueryError(errors.ERROR_QUERY_GEO_INDEX_MISSING.code, "RETURN NEAR(" + locationsNon.name() + ", 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_GEO_INDEX_MISSING.code, "RETURN WITHIN(" + locationsNon.name() + ", 0, 0, 10)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid NEAR arguments count
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyInvalidNearArgument : function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, \"foo\")");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, 10, true)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid WITHIN arguments count
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyInvalidWithinArgument : function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, \"foo\")");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, 0, [ ])");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid collection parameter
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testLegacyInvalidCollectionArgument : function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(1234, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(false, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(true, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN([ ], 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN({ }, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code, "RETURN WITHIN(@name, 0, 0, 10)", { name: "foobarbazcoll" });
|
|
assertQueryError(errors.ERROR_QUERY_BIND_PARAMETER_MISSING.code, "RETURN WITHIN(@name, 0, 0, 10)");
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
function legacyGeoTestSuite() {
|
|
var locations = null;
|
|
var locationsNon = null;
|
|
|
|
function runQuery(query) {
|
|
var result = getQueryResults(query);
|
|
|
|
result = result.map(function (row) {
|
|
var r;
|
|
if (row !== null && typeof row === 'object') {
|
|
for (r in row) {
|
|
if (row.hasOwnProperty(r)) {
|
|
var value = row[r];
|
|
if (typeof (value) === "number") {
|
|
if (value !== parseFloat(parseInt(value))) {
|
|
row[r] = Number(value).toFixed(5);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return row;
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
return {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
setUp: function () {
|
|
var lat, lon;
|
|
db._drop("UnitTestsAhuacatlLocations");
|
|
db._drop("UnitTestsAhuacatlLocationsNon");
|
|
|
|
locations = db._create("UnitTestsAhuacatlLocations");
|
|
for (lat = -40; lat <= 40; ++lat) {
|
|
for (lon = -40; lon <= 40; ++lon) {
|
|
locations.save({ "latitude": lat, "longitude": lon });
|
|
}
|
|
}
|
|
|
|
locations.ensureIndex({ type: "geo", fields: ["latitude", "longitude"] });
|
|
|
|
locationsNon = db._create("UnitTestsAhuacatlLocationsNon");
|
|
|
|
for (lat = -40; lat <= 40; ++lat) {
|
|
for (lon = -40; lon <= 40; ++lon) {
|
|
locationsNon.save({ "latitude": lat, "longitude": lon });
|
|
}
|
|
}
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tear down
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
tearDown: function () {
|
|
db._drop("UnitTestsAhuacatlLocations");
|
|
db._drop("UnitTestsAhuacatlLocationsNon");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testNear1: function () {
|
|
var expected = [{ "distance": "111194.92664", "latitude": -1, "longitude": 0 },
|
|
{ "distance": "111194.92664", "latitude": 0, "longitude": -1 },
|
|
{ "distance": "111194.92664", "latitude": 0, "longitude": 1 },
|
|
{ "distance": "111194.92664", "latitude": 1, "longitude": 0 },
|
|
{ "distance": 0, "latitude": 0, "longitude": 0 }];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", 0, 0, 5) " +
|
|
"LET d = DISTANCE(0,0,x.latitude,x.longitude) " +
|
|
"SORT d DESC, x.latitude, x.longitude RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testNear2: function () {
|
|
var expected = [{ "latitude": -10, "longitude": 24 }, { "latitude": -10, "longitude": 25 }, { "latitude": -10, "longitude": 26 }];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -10, 25, 3) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testNear3: function () {
|
|
var expected = [{ "distance": "14891044.54146", "latitude": 40, "longitude": -40 },
|
|
{ "distance": "14853029.30724", "latitude": 40, "longitude": -39 },
|
|
{ "distance": "14815001.47646", "latitude": 40, "longitude": -38 }];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, 10000) " +
|
|
"LET d = DISTANCE(-70,70,x.latitude,x.longitude) " +
|
|
"SORT d DESC LIMIT 3 RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
|
|
expected = [{ "distance": "4487652.12954", "latitude": -37, "longitude": 26 },
|
|
{ "distance": "4485565.93668", "latitude": -39, "longitude": 20 },
|
|
{ "distance": "4484371.86154", "latitude": -38, "longitude": 23 }];
|
|
actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, 100) " +
|
|
"LET d = DISTANCE(-70,70,x.latitude,x.longitude) " +
|
|
"SORT d DESC LIMIT 3 RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
|
|
//until changed null will result in the default value of 100
|
|
//expected = [{ "distance": "14891044.54146", "latitude": 40, "longitude": -40 },
|
|
// { "distance": "14853029.30724", "latitude": 40, "longitude": -39 },
|
|
// { "distance": "14815001.47646", "latitude": 40, "longitude": -38 }];
|
|
actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, null) " +
|
|
"LET d = DISTANCE(-70,70,x.latitude,x.longitude) " +
|
|
"SORT d DESC LIMIT 3 RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test near function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testNear4: function () {
|
|
var expected = [{ "latitude": -40, "longitude": 40 }, { "latitude": -40, "longitude": 39 }, { "latitude": -40, "longitude": 38 }];
|
|
var actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, null) SORT x.latitude, x.longitude DESC LIMIT 3 RETURN x");
|
|
assertEqual(expected, actual);
|
|
|
|
expected = [{ "latitude": -40, "longitude": 40 }, { "latitude": -40, "longitude": 39 }];
|
|
actual = runQuery("FOR x IN NEAR(" + locations.name() + ", -70, 70, 2) SORT x.latitude, x.longitude DESC LIMIT 3 RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testWithin1: function () {
|
|
var expected = [{ "distance": 0, "latitude": 0, "longitude": 0 }];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", 0, 0, 10000) " +
|
|
"LET d = DISTANCE(0,0,x.latitude,x.longitude) " +
|
|
"SORT x.latitude, x.longitude RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testWithin2: function () {
|
|
var expected = [{ "distance": "111194.92664", "latitude": -1, "longitude": 0 }, { "distance": "111194.92664", "latitude": 0, "longitude": -1 }, { "distance": 0, "latitude": 0, "longitude": 0 }, { "distance": "111194.92664", "latitude": 0, "longitude": 1 }, { "distance": "111194.92664", "latitude": 1, "longitude": 0 }];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", 0, 0, 150000) " +
|
|
"LET d = DISTANCE(0,0,x.latitude,x.longitude) " +
|
|
"SORT x.latitude, x.longitude RETURN MERGE(x, {distance:d})");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testWithin3: function () {
|
|
var expected = [{ "latitude": -10, "longitude": 25 }];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -10, 25, 10000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testWithin4: function () {
|
|
var expected = [
|
|
{ "latitude": -11, "longitude": 25 },
|
|
{ "latitude": -10, "longitude": 24 },
|
|
{ "latitude": -10, "longitude": 25 },
|
|
{ "latitude": -10, "longitude": 26 },
|
|
{ "latitude": -9, "longitude": 25 }
|
|
];
|
|
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -10, 25, 150000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test within function
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testWithin5: function () {
|
|
var expected = [];
|
|
var actual = runQuery("FOR x IN WITHIN(" + locations.name() + ", -90, 90, 10000) SORT x.latitude, x.longitude RETURN x");
|
|
assertEqual(expected, actual);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test without geo index available
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testNonIndexed: function () {
|
|
assertQueryError(errors.ERROR_QUERY_GEO_INDEX_MISSING.code, "RETURN NEAR(" + locationsNon.name() + ", 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_GEO_INDEX_MISSING.code, "RETURN WITHIN(" + locationsNon.name() + ", 0, 0, 10)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid NEAR arguments count
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testInvalidNearArgument: function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, \"foo\")");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN NEAR(\"" + locations.name() + "\", 0, 0, 10, true)");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid WITHIN arguments count
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testInvalidWithinArgument: function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, \"foo\")");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, 0, true)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(\"" + locations.name() + "\", 0, 0, 0, [ ])");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test invalid collection parameter
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testInvalidCollectionArgument: function () {
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(1234, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(false, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN(true, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN([ ], 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_QUERY_FUNCTION_ARGUMENT_TYPE_MISMATCH.code, "RETURN WITHIN({ }, 0, 0, 10)");
|
|
assertQueryError(errors.ERROR_ARANGO_DATA_SOURCE_NOT_FOUND.code, "RETURN WITHIN(@name, 0, 0, 10)", { name: "foobarbazcoll" });
|
|
assertQueryError(errors.ERROR_QUERY_BIND_PARAMETER_MISSING.code, "RETURN WITHIN(@name, 0, 0, 10)");
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
function pointsTestSuite() {
|
|
|
|
// Test queries with index usage and without
|
|
function runQuery(query) {
|
|
let result1 = getQueryResults(query.string, query.bindVars || {}, false);
|
|
let result2 = getQueryResults(query.string, query.bindVars || {}, false,
|
|
{ optimizer: { rules: ["-all"] } });
|
|
// due to bad experiences with interchange-adjacent-enumerations
|
|
let result3 = getQueryResults(query.string, query.bindVars || {}, false,
|
|
{ optimizer: { rules: ["-interchange-adjacent-enumerations"] } });
|
|
assertEqual(query.expected, result1, query.string);
|
|
assertEqual(query.expected, result2, query.string);
|
|
assertEqual(query.expected, result3, "Breaks with 'interchange-adjacent-enumerations': " + query.string);
|
|
}
|
|
|
|
let locations;
|
|
return {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
setUp: function () {
|
|
var lat, lon;
|
|
db._drop("UnitTestsPointsTestSuite");
|
|
|
|
locations = db._create("UnitTestsPointsTestSuite");
|
|
for (lat = -40; lat <= 40; ++lat) {
|
|
for (lon = -40; lon <= 40; ++lon) {
|
|
locations.save({ "lat": lat, "lng": lon });
|
|
}
|
|
}
|
|
|
|
locations.ensureIndex({ type: "geo", fields: ["lat", "lng"]});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tear down
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
tearDown: function () {
|
|
db._drop("UnitTestsPointsTestSuite");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle1: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER DISTANCE(-10, 25, x.lat, x.lng) <= 10000 RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [{ "lat": -10, "lng": 25 }]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle2: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER DISTANCE(-10, 25, x.lat, x.lng) <= 150000 SORT x.lat, x.lng RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -10, "lng": 24 },
|
|
{ "lat": -10, "lng": 25 },
|
|
{ "lat": -10, "lng": 26 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle3: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER DISTANCE(-90, -90, x.lat, x.lng) <= 10000 SORT x.lat, x.lng RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: []
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle4: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) <= 10000 RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [{ "lat": -10, "lng": 25 }]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle5: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) <= 150000 SORT x.lat, x.lng RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -10, "lng": 24 },
|
|
{ "lat": -10, "lng": 25 },
|
|
{ "lat": -10, "lng": 26 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsCircle6: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE([-90, -90], [x.lng, x.lat]) <= 10000 SORT x.lat, x.lng RETURN x",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: []
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on sphere (a donut)
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus1: function () {
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER DISTANCE(-10, 25, x.lat, x.lng) <= 150000
|
|
FILTER DISTANCE(-10, 25, x.lat, x.lng) > 108500
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -10, "lng": 24 },
|
|
{ "lat": -10, "lng": 26 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on sphere (a donut)
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus2: function () {
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER DISTANCE(-10, 25, x.lat, x.lng) <= 150000
|
|
FILTER DISTANCE(-10, 25, x.lat, x.lng) > 109545
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on sphere (a donut)
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus3: function () {
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) <= 150000
|
|
FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) > 108500
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -10, "lng": 24 },
|
|
{ "lat": -10, "lng": 26 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on a sphere (a donut)
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus4: function () {
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) <= 150000
|
|
FILTER GEO_DISTANCE([25, -10], [x.lng, x.lat]) > 109545
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on sphere (a donut) using refernces
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus5: function () {
|
|
runQuery({
|
|
string: `LET lat = -10
|
|
LET lng = 25
|
|
FOR x IN @@cc
|
|
FILTER DISTANCE(lat, lng, x.lat, x.lng) <= 150000
|
|
FILTER DISTANCE(lat, lng, x.lat, x.lng) > 109545
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on a sphere (a donut) using reference
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus6: function () {
|
|
runQuery({
|
|
string: `LET latLng = [25, -10]
|
|
FOR x IN @@cc
|
|
FILTER GEO_DISTANCE(latLng, [x.lng, x.lat]) <= 150000
|
|
FILTER GEO_DISTANCE(latLng, [x.lng, x.lat]) > 109545
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple annulus on a sphere (a donut) with nested FOR loops
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsAnnulus7: function () {
|
|
runQuery({
|
|
string: `LET coords = [[25, -10], [150, 70]]
|
|
FOR latLng IN coords
|
|
FOR x IN @@cc
|
|
FILTER GEO_DISTANCE(latLng, [x.lng, x.lat]) <= 150000
|
|
FILTER GEO_DISTANCE(latLng, [x.lng, x.lat]) > 109545
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [
|
|
{ "lat": -11, "lng": 25 },
|
|
{ "lat": -9, "lng": 25 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test containment with a polygon
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsPolygon: function () {
|
|
const polygon = {
|
|
"type": "Polygon",
|
|
"coordinates": [[[-11.5, 23.5], [-6, 26], [-10.5, 26.1], [-11.5, 23.5]]]
|
|
};
|
|
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER GEO_CONTAINS(@poly, [x.lng, x.lat])
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": polygon
|
|
},
|
|
expected: [
|
|
{ "lat": 24, "lng": -11 },
|
|
{ "lat": 25, "lng": -10 },
|
|
{ "lat": 25, "lng": -9 },
|
|
{ "lat": 26, "lng": -10 },
|
|
{ "lat": 26, "lng": -9 },
|
|
{ "lat": 26, "lng": -8 },
|
|
{ "lat": 26, "lng": -7 },
|
|
{ "lat": 26, "lng": -6 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test containment in a nested FOR loop
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsPolygonNested: function () {
|
|
const polygon1 = [[[-11.5, 12.5], [-11.5, 13.5], [-10.5, 13.5], [-10.5, 12.5], [-11.5, 12.5]]];
|
|
const polygon2 = [[[11.5, -12.5], [11.5, -13.5], [10.5, -13.5], [10.5, -12.5], [11.5, -12.5]]];
|
|
|
|
runQuery({
|
|
string: `
|
|
FOR poly IN [GEO_POLYGON(@poly1), GEO_POLYGON(@poly2)]
|
|
FOR doc IN @@cc
|
|
FILTER GEO_CONTAINS(poly, [doc.lng, doc.lat])
|
|
SORT doc.lat, doc.lng
|
|
RETURN doc
|
|
`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly1": polygon1,
|
|
"poly2": polygon2
|
|
},
|
|
expected: [
|
|
{ "lat": -13, "lng": 11 },
|
|
{ "lat": 13, "lng": -11 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test intersection with a polygon
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testIntersectsPolygon: function () {
|
|
const polygon = {
|
|
"type": "Polygon",
|
|
"coordinates": [[[-11.5, 23.5], [-6, 26], [-10.5, 26.1], [-11.5, 23.5]]]
|
|
};
|
|
|
|
runQuery({
|
|
string: `FOR x IN @@cc
|
|
FILTER GEO_INTERSECTS(@poly, [x.lng, x.lat])
|
|
SORT x.lat, x.lng RETURN x`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": polygon
|
|
},
|
|
expected: [
|
|
{ "lat": 24, "lng": -11 },
|
|
{ "lat": 25, "lng": -10 },
|
|
{ "lat": 25, "lng": -9 },
|
|
{ "lat": 26, "lng": -10 },
|
|
{ "lat": 26, "lng": -9 },
|
|
{ "lat": 26, "lng": -8 },
|
|
{ "lat": 26, "lng": -7 },
|
|
{ "lat": 26, "lng": -6 }
|
|
]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test intersection in a nested FOR loop
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testIntersectsPolygonNested: function () {
|
|
const polygon1 = [[[-11.5, 12.5], [-11.5, 13.5], [-10.5, 13.5], [-10.5, 12.5], [-11.5, 12.5]]];
|
|
const polygon2 = [[[11.5, -12.5], [11.5, -13.5], [10.5, -13.5], [10.5, -12.5], [11.5, -12.5]]];
|
|
|
|
runQuery({
|
|
string: `
|
|
FOR poly IN [GEO_POLYGON(@poly1), GEO_POLYGON(@poly2)]
|
|
FOR doc IN @@cc
|
|
FILTER GEO_INTERSECTS(poly, [doc.lng, doc.lat])
|
|
SORT doc.lat, doc.lng
|
|
RETURN doc
|
|
`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly1": polygon1,
|
|
"poly2": polygon2
|
|
},
|
|
expected: [
|
|
{ "lat": -13, "lng": 11 },
|
|
{ "lat": 13, "lng": -11 }
|
|
]
|
|
});
|
|
}
|
|
|
|
};
|
|
}
|
|
|
|
const internal = require('internal');
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief Querys on a collection of GeoJson objects
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
function geoJsonTestSuite() {
|
|
let locations;
|
|
|
|
// Test queries with index usage and without
|
|
function runQuery(query) {
|
|
var result1 = getQueryResults(query.string, query.bindVars || {}, false);
|
|
var result2 = getQueryResults(query.string, query.bindVars || {}, false,
|
|
{ optimizer: { rules: ["-all"] } });
|
|
let expected = query.expected.slice().sort();
|
|
|
|
assertEqual(expected, result1.sort(), query.string);
|
|
assertEqual(expected, result2.sort(), query.string);
|
|
}
|
|
|
|
// GeoJSON test data, located over Borneo, Sumatra and malaysia
|
|
// Mostly from the spec: http://geojson.org/geojson-spec.html.
|
|
let indonesia = [
|
|
{ "type": "Polygon", "coordinates": [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]] },
|
|
{ "type": "LineString", "coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]] },
|
|
{ "type": "Point", "coordinates": [102.0, 0.5] },
|
|
{ "type": "MultiPolygon", "coordinates": [
|
|
[[[102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0]]],
|
|
[[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]],
|
|
[[100.2, 0.2], [100.2, 0.8], [100.8, 0.8], [100.8, 0.2], [100.2, 0.2]]]]
|
|
}];
|
|
let indonesiaKeys = [];
|
|
|
|
// EMEA region
|
|
let emea = [ // TODO implement multi-polygon
|
|
/*{ "type": "MultiPolygon", "coordinates": [ [[[40, 40], [20, 45], [45, 30], [40, 40]]],
|
|
[[[20, 35], [10, 30], [10, 10], [30, 5], [45, 20], [20, 35]], [[30, 20], [20, 15], [20, 25], [30, 20]]]] }*/
|
|
{ "type": "Polygon", "coordinates": [[[35, 10], [45, 45], [15, 40], [10, 20], [35, 10]], [[20, 30], [35, 35], [30, 20], [20, 30]]] },
|
|
{ "type": "LineString", "coordinates": [[25, 10], [10, 30], [25, 40]] },
|
|
{ "type": "MultiLineString", "coordinates": [[[10, 10], [20, 20], [10, 40]], [[40, 40], [30, 30], [40, 20], [30, 10]]] },
|
|
{ "type": "MultiPoint", "coordinates": [[10, 40], [40, 30], [20, 20], [30, 10]] }
|
|
];
|
|
let emeaKeys = [];
|
|
let rectEmea1 = { "type": "Polygon", "coordinates": [[[2, 4], [26, 4], [26, 49], [2, 49], [2, 4]]] };
|
|
let rectEmea2 = { "type": "Polygon", "coordinates": [[[35, 42], [49, 42], [49, 50], [35, 51], [35, 42]]] };
|
|
let rectEmea3 = { "type": "Polygon", "coordinates": [[[35, 42], [49, 42], [49, 50], [35, 50], [35, 42]]] };
|
|
|
|
return {
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
setUp: function () {
|
|
var lat, lon;
|
|
db._drop("UnitTestsGeoJsonTestSuite");
|
|
|
|
locations = db._create("UnitTestsGeoJsonTestSuite");
|
|
locations.ensureIndex({ type: "geo", fields: ["geometry"], geoJson: true});
|
|
|
|
indonesiaKeys = indonesia.map(doc => locations.save({ geometry: doc })._key);
|
|
emeaKeys = emea.map(doc => locations.save({ geometry: doc })._key);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief tear down
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
tearDown: function () {
|
|
db._drop("UnitTestsGeoJsonTestSuite");
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsCircle1: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE(GEO_POINT(102, 0), x.geometry) <= 450000 RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: indonesiaKeys
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere (without circle edge included)
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsCircle2: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE(GEO_POINT(101, 0), x.geometry) < 283439.318405 RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: [indonesiaKeys[0], indonesiaKeys[2], indonesiaKeys[3]]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsCircle3: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE(GEO_POINT(101, 0), x.geometry) <= 100000 RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: indonesiaKeys.slice(0, 1)
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test simple circle on sphere
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsCircle4: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_DISTANCE(GEO_POINT(101, 0), x.geometry) <= 100000 RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
},
|
|
expected: indonesiaKeys.slice(0, 1)
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon contains
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsPolygon1: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_CONTAINS(GEO_POLYGON(@poly), x.geometry) RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": rectEmea1.coordinates
|
|
},
|
|
expected: [emeaKeys[1]]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon contains
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonContainsPolygon2: function () {
|
|
runQuery({
|
|
string: `LET poly = GEO_POLYGON(@coords)
|
|
FOR x IN @@cc
|
|
FILTER GEO_CONTAINS(poly, x.geometry)
|
|
RETURN x._key`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"coords": rectEmea1.coordinates
|
|
},
|
|
expected: [emeaKeys[1]]
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon intersection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonIntersectsPolygon1: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_INTERSECTS(GEO_POLYGON(@poly), x.geometry) RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": rectEmea1.coordinates
|
|
},
|
|
expected: emeaKeys
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon intersection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonIntersectsPolygon2: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_INTERSECTS(GEO_POLYGON(@poly), x.geometry) RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": rectEmea2.coordinates
|
|
},
|
|
expected: emeaKeys.slice(0, 1)
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon intersection
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonIntersectsPolygon3: function () {
|
|
runQuery({
|
|
string: "FOR x IN @@cc FILTER GEO_INTERSECTS(GEO_POLYGON(@poly), x.geometry) RETURN x._key",
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"poly": rectEmea3.coordinates
|
|
},
|
|
expected: emeaKeys.slice(0, 1)
|
|
});
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief test polygon intersection using a reference
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testJsonIntersectsPolygon4: function () {
|
|
runQuery({
|
|
string: `LET poly = GEO_POLYGON(@coords)
|
|
FOR x IN @@cc
|
|
FILTER GEO_INTERSECTS(poly, x.geometry)
|
|
RETURN x._key`,
|
|
bindVars: {
|
|
"@cc": locations.name(),
|
|
"coords": rectEmea3.coordinates
|
|
},
|
|
expected: emeaKeys.slice(0, 1)
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
function geoFunctionsTestSuite() {
|
|
|
|
function runQuery(query) {
|
|
var result1 = getQueryResults(query.string, query.bindVars || {}, false);
|
|
var result2 = getQueryResults(query.string, query.bindVars || {}, false,
|
|
{ optimizer: { rules: ["-all"] } });
|
|
let expected = query.expected.slice().sort();
|
|
|
|
assertEqual(expected, result1.sort(), query.string);
|
|
assertEqual(expected, result2.sort(), query.string);
|
|
}
|
|
|
|
let locations;
|
|
return {
|
|
|
|
testContainsPolygon1: function () {
|
|
// simple triangle
|
|
let triangle = {type:"Polygon", coordinates:[[[0,0], [1,0], [0,1], [0,0]]]};
|
|
let valid = [[0.01, 0.01], [0.01, 0.99], [0.99, 0.01], [0.49, 0.49]];
|
|
|
|
const query = "RETURN GEO_CONTAINS(@polygon, @cc)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": triangle, "cc": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": triangle, "cc": [1.0, 1.0]}), [false]);
|
|
},
|
|
|
|
testContainsPolygon2: function () {
|
|
// polygon with nested rings
|
|
let polygon = {type:"Polygon", coordinates:[[[-1,-1], [2,-1], [2,2], [-1,2], [-1,-1]],
|
|
[[0,0], [1,0], [1,1], [0,1], [0,0]]]};
|
|
let valid = [[-0.99, -0.99], [1.99, -0.99], [1.99, 1.99], [-0.99, 1.99],
|
|
[-0.5, 0.5], [0.5, 1.5], [1.5, -0.5], [0.5, -0.5]];
|
|
|
|
const query = "RETURN GEO_CONTAINS(@polygon, @cc)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": [0.5, 0.5]}), [false]);
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": [3.0, 3.0]}), [false]);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testContainsMultiPolygon: function () {
|
|
let multipoly = {"type": "MultiPolygon", "coordinates": [[[[0,0], [1,0], [0,1], [0,0]]],[[[2,2], [3,2], [2,3], [2,2]]]] };
|
|
let valid = [[0.01, 0.01], [0.01, 0.99], [0.99, 0.01], [0.49, 0.49],
|
|
[2.01, 2.01], [2.01, 2.99], [2.99, 2.01], [2.49, 2.49]];
|
|
|
|
const query = "RETURN GEO_CONTAINS(@polygon, @cc)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": [1.0, 1.0]}), [false]);
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": [3.0, 3.0]}), [false]);
|
|
},
|
|
|
|
testIntersectsPolygon1: function () {
|
|
// polygon with nested rings
|
|
let polygon = {type:"Polygon", coordinates:[[[-1,-1], [2,-1], [2,2], [-1,2], [-1,-1]],
|
|
[[0,0], [1,0], [1,1], [0,1], [0,0]]]};
|
|
let valid = [[-0.99, -0.99], [1.99, -0.99], [1.99, 1.99], [-0.99, 1.99],
|
|
[-0.5, 0.5], [0.5, 1.5], [1.5, -0.5], [0.5, -0.5]];
|
|
|
|
const query = "RETURN GEO_INTERSECTS(@polygon, @cc)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": [0.5, 0.5]}), [false]);
|
|
assertEqual(getQueryResults(query, {"polygon": polygon, "cc": [3.0, 3.0]}), [false]);
|
|
},
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testIntersectsMultiPolygon1: function () {
|
|
let multipoly = {"type": "MultiPolygon", "coordinates": [[[[0,0], [1,0], [0,1], [0,0]]],[[[2,2], [3,2], [2,3], [2,2]]]] };
|
|
let valid = [[0.01, 0.01], [0.01, 0.99], [0.99, 0.01], [0.49, 0.49],
|
|
[2.01, 2.01], [2.01, 2.99], [2.99, 2.01], [2.49, 2.49]];
|
|
|
|
const query = "RETURN GEO_INTERSECTS(@polygon, @cc)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": [1.0, 1.0]}), [false]);
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "cc": [3.0, 3.0]}), [false]);
|
|
},
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief set up
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
testIntersectsMultiPolygon2: function () {
|
|
let multipoly = {"type": "MultiPolygon", "coordinates": [[[[0,0], [1,0], [0,1], [0,0]]],[[[2,2], [3,2], [2,3], [2,2]]]] };
|
|
let valid = [{"type": "Polygon","coordinates": [[[2.5,2],[3,2.0],[3,2.5],[2.5,2.5],[2.5,2]]]},
|
|
{"type": "LineString","coordinates": [[2,2.5],[2.5,3.0]]},
|
|
{"type": "LineString","coordinates": [[-0,0.5],[0.5,1],[0.5,-0.5]]}];
|
|
|
|
const query = "RETURN GEO_INTERSECTS(@polygon, @other)";
|
|
valid.forEach(c => {
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "other": c}), [true]);
|
|
});
|
|
assertEqual(getQueryResults(query, {"polygon": multipoly, "other": {"type": "LineString","coordinates": [[2.5,1],[3,0.05]]}}), [false]);
|
|
},
|
|
};
|
|
}
|
|
|
|
jsunity.run(ahuacatlLegacyGeoTestSuite);
|
|
jsunity.run(legacyGeoTestSuite);
|
|
jsunity.run(pointsTestSuite);
|
|
jsunity.run(geoJsonTestSuite);
|
|
jsunity.run(geoFunctionsTestSuite);
|
|
|
|
return jsunity.done();
|