[test] Distance expression: Update unit tests

This commit is contained in:
zmiao
2020-04-28 20:03:46 +03:00
parent 0fa485092e
commit 70fbc97e58
5 changed files with 456 additions and 46 deletions

View File

@@ -270,6 +270,9 @@ struct Comparator {
// The priority queue will ensure the top element would always be the pair that has the biggest distance
using DistQueue = std::priority_queue<DistPair, std::deque<DistPair>, Comparator>;
// Divide and conqure, the time complexity is O(n*lgn), faster than Brute force O(n*n)
// Most of the time, use index for in-place processing.
double lineToPolygonDistance(const mapbox::geometry::line_string<double>& line,
const mapbox::geometry::polygon<double>& polygon,
mapbox::cheap_ruler::CheapRuler& ruler,
@@ -278,6 +281,7 @@ double lineToPolygonDistance(const mapbox::geometry::line_string<double>& line,
DistQueue distQueue;
distQueue.push(std::forward_as_tuple(0, IndexRange(0, line.size() - 1), IndexRange(0, 0)));
const auto polyBBox = getBBox(polygon);
while (!distQueue.empty()) {
auto distPair = distQueue.top();
distQueue.pop();
@@ -293,9 +297,9 @@ double lineToPolygonDistance(const mapbox::geometry::line_string<double>& line,
} else {
auto newRangesA = splitRange(range, true /*isLine*/);
const auto updateQueue =
[&distQueue, &miniDist, &ruler, &line, &polygon](mbgl::optional<IndexRange>& rangeA) {
[&distQueue, &miniDist, &ruler, &line, &polyBBox](mbgl::optional<IndexRange>& rangeA) {
if (!rangeA) return;
auto tempDist = bboxToBBoxDistance(getBBox(line, *rangeA), getBBox(polygon), ruler);
auto tempDist = bboxToBBoxDistance(getBBox(line, *rangeA), polyBBox, ruler);
// Insert new pair to the queue if the bbox distance is less or equal to miniDist,
// The pair with biggest distance will be at the top
if (tempDist <= miniDist)
@@ -316,6 +320,7 @@ double pointsToPolygonDistance(const mapbox::geometry::multi_point<double>& poin
DistQueue distQueue;
distQueue.push(std::forward_as_tuple(0, IndexRange(0, points.size() - 1), IndexRange(0, 0)));
const auto polyBBox = getBBox(polygon);
while (!distQueue.empty()) {
auto distPair = distQueue.top();
distQueue.pop();
@@ -330,11 +335,11 @@ double pointsToPolygonDistance(const mapbox::geometry::multi_point<double>& poin
if (miniDist == 0.0) return 0.0;
}
} else {
auto newRangesA = splitRange(range, true /*isLine*/);
auto newRangesA = splitRange(range, false /*isLine*/);
const auto updateQueue =
[&distQueue, &miniDist, &ruler, &points, &polygon](mbgl::optional<IndexRange>& rangeA) {
[&distQueue, &miniDist, &ruler, &points, &polyBBox](mbgl::optional<IndexRange>& rangeA) {
if (!rangeA) return;
auto tempDist = bboxToBBoxDistance(getBBox(points, *rangeA), getBBox(polygon), ruler);
auto tempDist = bboxToBBoxDistance(getBBox(points, *rangeA), polyBBox, ruler);
// Insert new pair to the queue if the bbox distance is less or equal to miniDist,
// The pair with biggest distance will be at the top
if (tempDist <= miniDist)
@@ -347,8 +352,6 @@ double pointsToPolygonDistance(const mapbox::geometry::multi_point<double>& poin
return miniDist;
}
// Divide and conqure, the time complexity is O(n*lgn), faster than Brute force O(n*n)
// Use index for in-place processing.
double lineToLineDistance(const mapbox::geometry::line_string<double>& line1,
const mapbox::geometry::multi_point<double>& line2,
mapbox::cheap_ruler::CheapRuler& ruler,
@@ -391,8 +394,6 @@ double lineToLineDistance(const mapbox::geometry::line_string<double>& line1,
return miniDist;
}
// Divide and conqure, the time complexity is O(n*lgn), faster than Brute force O(n*n)
// Use index for in-place processing.
double pointsToPointsDistance(const mapbox::geometry::multi_point<double>& pointSet1,
const mapbox::geometry::multi_point<double>& pointSet2,
mapbox::cheap_ruler::CheapRuler& ruler) {
@@ -436,8 +437,6 @@ double pointsToPointsDistance(const mapbox::geometry::multi_point<double>& point
return miniDist;
}
// Divide and conqure, the time complexity is O(n*lgn), faster than Brute force O(n*n)
// Most of the time, use index for in-place processing.
double pointsToLineDistance(const mapbox::geometry::multi_point<double>& points,
const mapbox::geometry::line_string<double>& line,
mapbox::cheap_ruler::CheapRuler& ruler) {
@@ -653,7 +652,7 @@ double calculateDistance(const GeometryTileFeature& feature,
optional<GeoJSON> parseValue(const style::conversion::Convertible& value, style::expression::ParsingContext& ctx) {
if (isArray(value)) {
// object value, quoted with ["Distance", GeoJSONObj]
// object value, quoted with ["distance", GeoJSONObj]
auto length = arrayLength(value);
if (length != 2) {
ctx.error("'distance' expression requires one argument, but found " +
@@ -672,7 +671,7 @@ optional<GeoJSON> parseValue(const style::conversion::Convertible& value, style:
ctx.error(error.message);
}
}
ctx.error("'distance' expression needs to be an array with format [\"Distance\", GeoJSONObj].");
ctx.error("'distance' expression needs to be an array with format [\"distance\", GeoJSONObj].");
return nullopt;
}

View File

@@ -0,0 +1,75 @@
{
"type": "Feature",
"properties": {
"marker-color": "#e0d96a"
},
"geometry": {
"type": "MultiPoint",
"coordinates": [
[
24.941325187683105,
60.169503396855
],
[
24.943127632141113,
60.16984495711831
],
[
24.94493007659912,
60.17101904344053
],
[
24.947762489318848,
60.16753935642882
],
[
24.9479341506958,
60.16907644153227
],
[
24.946002960205078,
60.16901239775532
],
[
24.944028854370117,
60.16824386269413
],
[
24.9459171295166,
60.16756070532545
],
[
24.940338134765625,
60.168286559558055
],
[
24.94248390197754,
60.1678168910033
],
[
24.944286346435547,
60.16696293097573
],
[
24.946260452270508,
60.16619434797435
],
[
24.941411018371582,
60.16619434797435
],
[
24.943749904632565,
60.16782756536319
],
[
24.942612648010254,
60.16908711548297
],
[
24.944608211517334,
60.16952474447549
]
]
}
}

View File

@@ -0,0 +1,132 @@
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"stroke": "#e0d96a",
"stroke-width": 2,
"stroke-opacity": 1,
"fill": "#c6ec00",
"fill-opacity": 0.5
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
24.93870735168457,
60.16429416283512
],
[
24.936389923095703,
60.16205223008669
],
[
24.940123558044434,
60.16250062887229
],
[
24.943857192993164,
60.16333335324268
],
[
24.943127632141113,
60.16465712803696
],
[
24.94123935699463,
60.16595949958244
],
[
24.936561584472656,
60.16561789892518
],
[
24.93450164794922,
60.16435821580911
],
[
24.935274124145508,
60.163568220403924
],
[
24.93870735168457,
60.16429416283512
]
]
],
[
[
[
24.95166778564453,
60.174306261926034
],
[
24.949607849121094,
60.172918839697616
],
[
24.952783584594727,
60.17144597351834
],
[
24.95595932006836,
60.17217174191747
],
[
24.956517219543457,
60.1742635728851
],
[
24.954586029052734,
60.175736312737385
],
[
24.950637817382812,
60.175245406790246
],
[
24.9514102935791,
60.174348950911465
],
[
24.95166778564453,
60.174306261926034
]
]
],
[
[
[
24.930424690246582,
60.17413550542946
],
[
24.92875099182129,
60.171830205844614
],
[
24.936904907226562,
60.1703145966823
],
[
24.937891960144043,
60.172150396016946
],
[
24.937891960144043,
60.173922058560485
],
[
24.930424690246582,
60.17413550542946
]
]
]
]
}
}
]
}

View File

@@ -0,0 +1,81 @@
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
24.94171142578125,
60.16777419352904
],
[
24.948577880859375,
60.16777419352904
],
[
24.948577880859375,
60.170122472217564
],
[
24.94171142578125,
60.170122472217564
],
[
24.94171142578125,
60.16777419352904
]
]
],
[
[
[
24.951581954956055,
60.169503396855
],
[
24.955186843872067,
60.169503396855
],
[
24.955186843872067,
60.17052806699205
],
[
24.951581954956055,
60.17052806699205
],
[
24.951581954956055,
60.169503396855
]
]
],
[
[
[
24.944243431091305,
60.16301307713581
],
[
24.94716167449951,
60.16301307713581
],
[
24.94716167449951,
60.16412335429406
],
[
24.944243431091305,
60.16412335429406
],
[
24.944243431091305,
60.16301307713581
]
]
]
]
}
}

View File

@@ -468,11 +468,12 @@ TEST(PropertyExpression, WithinExpressionAntiMeridian) {
TEST(PropertyExpression, DistanceExpression) {
static const double invalidResult = std::numeric_limits<double>::quiet_NaN();
static const CanonicalTileID canonicalTileID(15, 18653, 9484);
// Parsing error with invalid geojson source
{
// geoJSON source must be Point or LineString.
const std::string invalidGeoSource = R"({
"type": "Polygon",
"type": "Unknown",
"coordinates": [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]
})";
std::stringstream ss;
@@ -481,48 +482,18 @@ TEST(PropertyExpression, DistanceExpression) {
ASSERT_FALSE(expression);
}
// Parsing error with invalid unit
// Parsing error with extra arguments
{
const std::string invalidGeoSource = R"({
"type": "Point",
"coordinates": [0, 0],
})";
std::stringstream ss;
ss << std::string(R"(["distance", )") << invalidGeoSource << std::string(R"(, "InvalidUnits"])");
ss << std::string(R"(["distance", )") << invalidGeoSource << std::string(R"(, "ExtraArgument"])");
auto expression = createExpression(ss.str().c_str());
ASSERT_FALSE(expression);
}
// Evaluation test with non-supported geometry type
{
const std::string pointGeoSource = R"({
"type": "Point",
"coordinates":
[
24.938492774963375,
60.16980226227959
]
})";
std::stringstream ss;
ss << std::string(R"(["distance", )") << pointGeoSource << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
Polygon<double> testRing{{{24.937505722045895, 60.16815846879986},
{24.940595626831055, 60.16815846879986},
{24.940595626831055, 60.169268572114106},
{24.937505722045895, 60.169268572114106},
{24.937505722045895, 60.16815846879986}}};
auto geoPoly = convertGeometry(testRing, canonicalTileID);
StubGeometryTileFeature polyFeature(FeatureType::Polygon, geoPoly);
// return evaluation error
auto evaluatedResult =
propExpr.evaluate(EvaluationContext(&polyFeature).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_TRUE(std::isnan(evaluatedResult));
}
// Evaluation test with Point to Point distance by using different units
{
const std::string pointGeoSource = R"({
@@ -752,4 +723,156 @@ TEST(PropertyExpression, DistanceExpression) {
EvaluationContext(&multiLineFeature).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(384.109, evaluatedResult, 0.01);
}
// Evaluation test with MultiPoint to MultiPolygon distance
{
const auto multiPolygon = mbgl::util::read_file("test/fixtures/geometry_data/multi_polygon_1.geojson");
std::stringstream ss;
ss << std::string(R"(["distance", )") << multiPolygon << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
const auto multiPointFeature1 = getFeature("multi_point_3.geojson", FeatureType::Point, canonicalTileID);
auto evaluatedResult = propExpr.evaluate(
EvaluationContext(&multiPointFeature1).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(27.917, evaluatedResult, 0.01);
const auto multiPointFeature2 = getFeature("multi_point_2.geojson", FeatureType::Point, canonicalTileID);
evaluatedResult = propExpr.evaluate(
EvaluationContext(&multiPointFeature2).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
}
// Evaluation test with MultiLine to MultiPolygon distance
{
const auto multiPolygon = mbgl::util::read_file("test/fixtures/geometry_data/multi_polygon_1.geojson");
std::stringstream ss;
ss << std::string(R"(["distance", )") << multiPolygon << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
const auto multiLineFeature1 =
getFeature("multi_line_string_1.geojson", FeatureType::LineString, canonicalTileID);
auto evaluatedResult = propExpr.evaluate(
EvaluationContext(&multiLineFeature1).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(193.450, evaluatedResult, 0.01);
const auto multiLineFeature2 =
getFeature("multi_line_string_2.geojson", FeatureType::LineString, canonicalTileID);
evaluatedResult = propExpr.evaluate(EvaluationContext(&multiLineFeature2).withCanonicalTileID(&canonicalTileID),
invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
}
// Evaluation test with Polygon to MultiPolygon distance
{
const auto multiPolygon = mbgl::util::read_file("test/fixtures/geometry_data/multi_polygon_1.geojson");
std::stringstream ss;
ss << std::string(R"(["distance", )") << multiPolygon << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
Polygon<double> polygon{{{24.94171142578125, 60.16777419352904},
{24.948577880859375, 60.16777419352904},
{24.948577880859375, 60.170122472217564},
{24.94171142578125, 60.170122472217564},
{24.94171142578125, 60.16777419352904}}};
auto geoPolygon = convertGeometry(polygon, canonicalTileID);
StubGeometryTileFeature polygonFeature(FeatureType::Polygon, geoPolygon);
auto evaluatedResult =
propExpr.evaluate(EvaluationContext(&polygonFeature).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(203.876, evaluatedResult, 0.01);
}
// Evaluation test with MultiPolygon to MultiPolygon distance
{
const auto multiPolygon = mbgl::util::read_file("test/fixtures/geometry_data/multi_polygon_1.geojson");
std::stringstream ss;
ss << std::string(R"(["distance", )") << multiPolygon << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
const auto multiPolygonFeature = getFeature("multi_polygon_2.geojson", FeatureType::Polygon, canonicalTileID);
auto evaluatedResult = propExpr.evaluate(
EvaluationContext(&multiPolygonFeature).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(41.632, evaluatedResult, 0.01);
}
// Evaluation test with LineString/Polygon to Polygon distance with special cases
{
const std::string multiPolygon = R"({
"type": "Polygon",
"coordinates": [[[24.937891960144043, 60.16730451765011],
[24.94892120361328, 60.16730451765011],
[24.94892120361328, 60.17010112498546],
[24.937891960144043, 60.17010112498546],
[24.937891960144043, 60.16730451765011]]]})";
std::stringstream ss;
ss << std::string(R"(["distance", )") << multiPolygon << std::string(R"( ])");
auto expression = createExpression(ss.str().c_str());
ASSERT_TRUE(expression);
PropertyExpression<double> propExpr(std::move(expression));
// polygon1 is fully inside polygon
Polygon<double> polygon1{{{24.941797256469727, 60.16850004304534},
{24.94235515594482, 60.16850004304534},
{24.94235515594482, 60.169439353910285},
{24.941797256469727, 60.169439353910285},
{24.941797256469727, 60.16850004304534}}};
auto geoPolygon1 = convertGeometry(polygon1, canonicalTileID);
StubGeometryTileFeature polygonFeature1(FeatureType::Polygon, geoPolygon1);
auto evaluatedResult =
propExpr.evaluate(EvaluationContext(&polygonFeature1).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
// polygon2's line is intersecting polygon, but no point is inside polygon
Polygon<double> polygon2{{{24.943385124206543, 60.166066249059554},
{24.94596004486084, 60.166066249059554},
{24.94596004486084, 60.1714246271462},
{24.943385124206543, 60.1714246271462},
{24.943385124206543, 60.166066249059554}}};
auto geoPolygon2 = convertGeometry(polygon2, canonicalTileID);
StubGeometryTileFeature polygonFeature2(FeatureType::Polygon, geoPolygon2);
evaluatedResult =
propExpr.evaluate(EvaluationContext(&polygonFeature2).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
// one of polygon3's point is inside polygon
Polygon<double> polygon3{{{24.94797706604004, 60.1694607015724},
{24.952354431152344, 60.1694607015724},
{24.952354431152344, 60.17082692309542},
{24.94797706604004, 60.17082692309542},
{24.94797706604004, 60.1694607015724}}};
auto geoPolygon3 = convertGeometry(polygon3, canonicalTileID);
StubGeometryTileFeature polygonFeature3(FeatureType::Polygon, geoPolygon3);
evaluatedResult =
propExpr.evaluate(EvaluationContext(&polygonFeature3).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
// lineString1 is within polygon
LineString<double> lineString1{{24.947097301483154, 60.16921520262075}, {24.947311878204346, 60.1680197032483}};
auto geoLineString1 = convertGeometry(lineString1, canonicalTileID);
StubGeometryTileFeature lineFeature1(FeatureType::LineString, geoLineString1);
evaluatedResult =
propExpr.evaluate(EvaluationContext(&lineFeature1).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
// lineString2 is intersecting the polygon but its points are outside the polygon
LineString<double> lineString2{{24.93999481201172, 60.17116846959888}, {24.940037727355957, 60.16542574699484}};
auto geoLineString2 = convertGeometry(lineString2, canonicalTileID);
StubGeometryTileFeature lineFeature2(FeatureType::LineString, geoLineString2);
evaluatedResult =
propExpr.evaluate(EvaluationContext(&lineFeature2).withCanonicalTileID(&canonicalTileID), invalidResult);
EXPECT_NEAR(0.0, evaluatedResult, 0.01);
}
}