diff --git a/include/mapbox/geojsonvt.hpp b/include/mapbox/geojsonvt.hpp index af5bbc3..1c59066 100644 --- a/include/mapbox/geojsonvt.hpp +++ b/include/mapbox/geojsonvt.hpp @@ -40,6 +40,9 @@ struct TileOptions { // tile buffer on each side uint16_t buffer = 64; + + // enable line metrics tracking for LineString/MultiLineString features + bool lineMetrics = false; }; struct Options : TileOptions { @@ -72,15 +75,15 @@ inline const Tile geoJSONToTile(const geojson& geojson_, auto tolerance = (options.tolerance / options.extent) / z2; auto features = detail::convert(features_, tolerance); if (wrap) { - features = detail::wrap(features, double(options.buffer) / options.extent); + features = detail::wrap(features, double(options.buffer) / options.extent, options.lineMetrics); } - if (clip) { + if (clip || options.lineMetrics) { const double p = options.buffer / options.extent; - const auto left = detail::clip<0>(features, (x - p) / z2, (x + 1 + p) / z2, -1, 2); - features = detail::clip<1>(left, (y - p) / z2, (y + 1 + p) / z2, -1, 2); + const auto left = detail::clip<0>(features, (x - p) / z2, (x + 1 + p) / z2, -1, 2, options.lineMetrics); + features = detail::clip<1>(left, (y - p) / z2, (y + 1 + p) / z2, -1, 2, options.lineMetrics); } - return detail::InternalTile({ features, z, x, y, options.extent, tolerance }).tile; + return detail::InternalTile({ features, z, x, y, options.extent, tolerance, options.lineMetrics }).tile; } class GeoJSONVT { @@ -94,7 +97,7 @@ class GeoJSONVT { const uint32_t z2 = 1u << options.maxZoom; auto converted = detail::convert(features_, (options.tolerance / options.extent) / z2); - auto features = detail::wrap(converted, double(options.buffer) / options.extent); + auto features = detail::wrap(converted, double(options.buffer) / options.extent, options.lineMetrics); splitTile(features, 0, 0, 0); } @@ -186,7 +189,7 @@ class GeoJSONVT { it = tiles .emplace(id, - detail::InternalTile{ features, z, x, y, options.extent, tolerance }) + detail::InternalTile{ features, z, x, y, options.extent, tolerance, options.lineMetrics }) .first; stats[z] = (stats.count(z) ? stats[z] + 1 : 1); total++; @@ -230,19 +233,19 @@ class GeoJSONVT { const auto& min = tile.bbox.min; const auto& max = tile.bbox.max; - const auto left = detail::clip<0>(features, (x - p) / z2, (x + 0.5 + p) / z2, min.x, max.x); + const auto left = detail::clip<0>(features, (x - p) / z2, (x + 0.5 + p) / z2, min.x, max.x, options.lineMetrics); - splitTile(detail::clip<1>(left, (y - p) / z2, (y + 0.5 + p) / z2, min.y, max.y), z + 1, + splitTile(detail::clip<1>(left, (y - p) / z2, (y + 0.5 + p) / z2, min.y, max.y, options.lineMetrics), z + 1, x * 2, y * 2, cz, cx, cy); - splitTile(detail::clip<1>(left, (y + 0.5 - p) / z2, (y + 1 + p) / z2, min.y, max.y), z + 1, + splitTile(detail::clip<1>(left, (y + 0.5 - p) / z2, (y + 1 + p) / z2, min.y, max.y, options.lineMetrics), z + 1, x * 2, y * 2 + 1, cz, cx, cy); const auto right = - detail::clip<0>(features, (x + 0.5 - p) / z2, (x + 1 + p) / z2, min.x, max.x); + detail::clip<0>(features, (x + 0.5 - p) / z2, (x + 1 + p) / z2, min.x, max.x, options.lineMetrics); - splitTile(detail::clip<1>(right, (y - p) / z2, (y + 0.5 + p) / z2, min.y, max.y), z + 1, + splitTile(detail::clip<1>(right, (y - p) / z2, (y + 0.5 + p) / z2, min.y, max.y, options.lineMetrics), z + 1, x * 2 + 1, y * 2, cz, cx, cy); - splitTile(detail::clip<1>(right, (y + 0.5 - p) / z2, (y + 1 + p) / z2, min.y, max.y), z + 1, + splitTile(detail::clip<1>(right, (y + 0.5 - p) / z2, (y + 1 + p) / z2, min.y, max.y, options.lineMetrics), z + 1, x * 2 + 1, y * 2 + 1, cz, cx, cy); // if we sliced further down, no need to keep source geometry diff --git a/include/mapbox/geojsonvt/clip.hpp b/include/mapbox/geojsonvt/clip.hpp index e6f3059..6f9b4bb 100644 --- a/include/mapbox/geojsonvt/clip.hpp +++ b/include/mapbox/geojsonvt/clip.hpp @@ -9,8 +9,12 @@ namespace detail { template class clipper { public: + clipper(double k1_, double k2_, bool lineMetrics_ = false) + : k1(k1_), k2(k2_), lineMetrics(lineMetrics_) {} + const double k1; const double k2; + const bool lineMetrics; vt_geometry operator()(const vt_point& point) const { return point; @@ -81,23 +85,26 @@ class clipper { } private: - vt_line_string newSlice(vt_multi_line_string& parts, vt_line_string& slice, double dist) const { - if (!slice.empty()) { - slice.dist = dist; - parts.push_back(std::move(slice)); + vt_line_string newSlice(const vt_line_string& line) const { + vt_line_string slice; + slice.dist = line.dist; + if (lineMetrics) { + slice.segStart = line.segStart; + slice.segEnd = line.segEnd; } - return {}; + return slice; } void clipLine(const vt_line_string& line, vt_multi_line_string& slices) const { - - const double dist = line.dist; const size_t len = line.size(); + double lineLen = line.segStart; + double segLen = 0.0; + double t = 0.0; if (len < 2) return; - vt_line_string slice; + vt_line_string slice = newSlice(line); for (size_t i = 0; i < (len - 1); ++i) { const auto& a = line[i]; @@ -105,25 +112,47 @@ class clipper { const double ak = get(a); const double bk = get(b); + if (lineMetrics) segLen = std::hypot((b.x - a.x), (b.y - a.y)); + if (ak < k1) { if (bk > k2) { // ---|-----|--> - slice.push_back(intersect(a, b, k1)); - slice.push_back(intersect(a, b, k2)); - slice = newSlice(slices, slice, dist); + t = calc_progress(a, b, k1); + slice.push_back(intersect(a, b, k1, t)); + if (lineMetrics) slice.segStart = lineLen + segLen * t; + + t = calc_progress(a, b, k2); + slice.push_back(intersect(a, b, k2, t)); + if (lineMetrics) slice.segEnd = lineLen + segLen * t; + slices.push_back(std::move(slice)); + + slice = newSlice(line); } else if (bk >= k1) { // ---|--> | - slice.push_back(intersect(a, b, k1)); + t = calc_progress(a, b, k1); + slice.push_back(intersect(a, b, k1, t)); + if (lineMetrics) slice.segStart = lineLen + segLen * t; + if (i == len - 2) slice.push_back(b); // last point } } else if (ak >= k2) { if (bk < k1) { // <--|-----|--- - slice.push_back(intersect(a, b, k2)); - slice.push_back(intersect(a, b, k1)); - slice = newSlice(slices, slice, dist); + t = calc_progress(a, b, k2); + slice.push_back(intersect(a, b, k2, t)); + if (lineMetrics) slice.segStart = lineLen + segLen * t; + + t = calc_progress(a, b, k1); + slice.push_back(intersect(a, b, k1, t)); + if (lineMetrics) slice.segEnd = lineLen + segLen * t; + + slices.push_back(std::move(slice)); + slice = newSlice(line); } else if (bk < k2) { // | <--|--- - slice.push_back(intersect(a, b, k2)); + t = calc_progress(a, b, k2); + slice.push_back(intersect(a, b, k2, t)); + if (lineMetrics) slice.segStart = lineLen + segLen * t; + if (i == len - 2) slice.push_back(b); // last point } @@ -131,26 +160,35 @@ class clipper { slice.push_back(a); if (bk < k1) { // <--|--- | - slice.push_back(intersect(a, b, k1)); - slice = newSlice(slices, slice, dist); + t = calc_progress(a, b, k1); + slice.push_back(intersect(a, b, k1, t)); + if (lineMetrics) slice.segEnd = lineLen + segLen * t; + slices.push_back(std::move(slice)); + slice = newSlice(line); } else if (bk > k2) { // | ---|--> - slice.push_back(intersect(a, b, k2)); - slice = newSlice(slices, slice, dist); + t = calc_progress(a, b, k2); + slice.push_back(intersect(a, b, k2, t)); + if (lineMetrics) slice.segEnd = lineLen + segLen * t; + slices.push_back(std::move(slice)); + slice = newSlice(line); } else if (i == len - 2) { // | --> | slice.push_back(b); } } + + if (lineMetrics) lineLen += segLen; } - // add the final slice - newSlice(slices, slice, dist); + if (!slice.empty()) { // add the final slice + slice.segEnd = lineLen; + slices.push_back(std::move(slice)); + } } vt_linear_ring clipRing(const vt_linear_ring& ring) const { const size_t len = ring.size(); - vt_linear_ring slice; slice.area = ring.area; @@ -165,27 +203,31 @@ class clipper { if (ak < k1) { if (bk >= k1) { - slice.push_back(intersect(a, b, k1)); // ---|--> | - if (bk > k2) // ---|-----|--> - slice.push_back(intersect(a, b, k2)); + // ---|--> | + slice.push_back(intersect(a, b, k1, calc_progress(a, b, k1))); + if (bk > k2) + // ---|-----|--> + slice.push_back(intersect(a, b, k2, calc_progress(a, b, k2))); else if (i == len - 2) slice.push_back(b); // last point } } else if (ak >= k2) { if (bk < k2) { // | <--|--- - slice.push_back(intersect(a, b, k2)); + slice.push_back(intersect(a, b, k2, calc_progress(a, b, k2))); if (bk < k1) // <--|-----|--- - slice.push_back(intersect(a, b, k1)); + slice.push_back(intersect(a, b, k1, calc_progress(a, b, k1))); else if (i == len - 2) slice.push_back(b); // last point } } else { - slice.push_back(a); - if (bk < k1) // <--|--- | - slice.push_back(intersect(a, b, k1)); - else if (bk > k2) // | ---|--> - slice.push_back(intersect(a, b, k2)); // | --> | + slice.push_back(a); + if (bk < k1) + // <--|--- | + slice.push_back(intersect(a, b, k1, calc_progress(a, b, k1))); + else if (bk > k2) + // | ---|--> + slice.push_back(intersect(a, b, k2, calc_progress(a, b, k2))); } } @@ -214,7 +256,8 @@ inline vt_features clip(const vt_features& features, const double k1, const double k2, const double minAll, - const double maxAll) { + const double maxAll, + const bool lineMetrics) { if (minAll >= k1 && maxAll < k2) // trivial accept return features; @@ -239,7 +282,22 @@ inline vt_features clip(const vt_features& features, continue; } else { - clipped.emplace_back(vt_geometry::visit(geom, clipper{ k1, k2 }), props, id); + const auto& clippedGeom = vt_geometry::visit(geom, clipper{ k1, k2, lineMetrics }); + + clippedGeom.match( + [&](const auto&) { + clipped.emplace_back(clippedGeom, props, id); + }, + [&](const vt_multi_line_string& result) { + if (lineMetrics) { + for (const auto& segment : result) { + clipped.emplace_back(segment, props, id); + } + } else { + clipped.emplace_back(clippedGeom, props, id); + } + } + ); } } diff --git a/include/mapbox/geojsonvt/convert.hpp b/include/mapbox/geojsonvt/convert.hpp index 88c036f..7f60a7c 100644 --- a/include/mapbox/geojsonvt/convert.hpp +++ b/include/mapbox/geojsonvt/convert.hpp @@ -39,13 +39,14 @@ struct project { for (size_t i = 0; i < len - 1; ++i) { const auto& a = result[i]; const auto& b = result[i + 1]; - // use Manhattan distance instead of Euclidian to avoid expensive square root - // computation - result.dist += std::abs(b.x - a.x) + std::abs(b.y - a.y); + result.dist += std::hypot((b.x - a.x), (b.y - a.y)); } simplify(result, tolerance); + result.segStart = 0; + result.segEnd = result.dist; + return result; } diff --git a/include/mapbox/geojsonvt/tile.hpp b/include/mapbox/geojsonvt/tile.hpp index e382346..53f6c15 100644 --- a/include/mapbox/geojsonvt/tile.hpp +++ b/include/mapbox/geojsonvt/tile.hpp @@ -25,6 +25,7 @@ class InternalTile { const double z2; const double tolerance; const double sq_tolerance; + const bool lineMetrics; vt_features source_features; mapbox::geometry::box bbox = { { 2, 1 }, { -1, 0 } }; @@ -36,14 +37,16 @@ class InternalTile { const uint32_t x_, const uint32_t y_, const uint16_t extent_, - const double tolerance_) + const double tolerance_, + const bool lineMetrics_) : extent(extent_), z(z_), x(x_), y(y_), z2(std::pow(2, z)), tolerance(tolerance_), - sq_tolerance(tolerance_ * tolerance_) { + sq_tolerance(tolerance_ * tolerance_), + lineMetrics(lineMetrics_) { for (const auto& feature : source) { const auto& geom = feature.geometry; @@ -74,8 +77,15 @@ class InternalTile { const property_map& props, const optional& id) { const auto new_line = transform(line); - if (!new_line.empty()) - tile.features.push_back({ std::move(new_line), props, id }); + if (!new_line.empty()) { + if (lineMetrics) { + property_map newProps = props; + newProps["mapbox_clip_start"] = line.segStart / line.dist; + newProps["mapbox_clip_end"] = line.segEnd / line.dist; + tile.features.push_back({ std::move(new_line), newProps, id }); + } else + tile.features.push_back({ std::move(new_line), props, id }); + } } void addFeature(const vt_polygon& polygon, diff --git a/include/mapbox/geojsonvt/types.hpp b/include/mapbox/geojsonvt/types.hpp index b65844f..3bf7ae3 100644 --- a/include/mapbox/geojsonvt/types.hpp +++ b/include/mapbox/geojsonvt/types.hpp @@ -43,16 +43,29 @@ inline double get<1>(const mapbox::geometry::point& p) { } template -inline vt_point intersect(const vt_point&, const vt_point&, const double); +inline double calc_progress(const vt_point&, const vt_point&, const double); template <> -inline vt_point intersect<0>(const vt_point& a, const vt_point& b, const double x) { - const double y = (x - a.x) * (b.y - a.y) / (b.x - a.x) + a.y; +inline double calc_progress<0>(const vt_point& a, const vt_point& b, const double x) { + return (x - a.x) / (b.x - a.x); +} + +template <> +inline double calc_progress<1>(const vt_point& a, const vt_point& b, const double y) { + return (y - a.y) / (b.y - a.y); +} + +template +inline vt_point intersect(const vt_point&, const vt_point&, const double, const double); + +template <> +inline vt_point intersect<0>(const vt_point& a, const vt_point& b, const double x, const double t) { + const double y = (b.y - a.y) * t + a.y; return { x, y, 1.0 }; } template <> -inline vt_point intersect<1>(const vt_point& a, const vt_point& b, const double y) { - const double x = (y - a.y) * (b.x - a.x) / (b.y - a.y) + a.x; +inline vt_point intersect<1>(const vt_point& a, const vt_point& b, const double y, const double t) { + const double x = (b.x - a.x) * t + a.x; return { x, y, 1.0 }; } @@ -65,6 +78,8 @@ struct vt_line_string : std::vector { : container_type(std::move(args)) {} double dist = 0.0; // line length + double segStart = 0.0; + double segEnd = 0.0; // segStart and segEnd are distance along a line in tile units, when lineMetrics = true }; struct vt_linear_ring : std::vector { diff --git a/include/mapbox/geojsonvt/wrap.hpp b/include/mapbox/geojsonvt/wrap.hpp index 495ccc5..a4f1bb8 100644 --- a/include/mapbox/geojsonvt/wrap.hpp +++ b/include/mapbox/geojsonvt/wrap.hpp @@ -16,17 +16,17 @@ inline void shiftCoords(vt_features& features, double offset) { } } -inline vt_features wrap(const vt_features& features, double buffer) { +inline vt_features wrap(const vt_features& features, double buffer, const bool lineMetrics) { // left world copy - auto left = clip<0>(features, -1 - buffer, buffer, -1, 2); + auto left = clip<0>(features, -1 - buffer, buffer, -1, 2, lineMetrics); // right world copy - auto right = clip<0>(features, 1 - buffer, 2 + buffer, -1, 2); + auto right = clip<0>(features, 1 - buffer, 2 + buffer, -1, 2, lineMetrics); if (left.empty() && right.empty()) return features; // center world copy - auto merged = clip<0>(features, -buffer, 1 + buffer, -1, 2); + auto merged = clip<0>(features, -buffer, 1 + buffer, -1, 2, lineMetrics); if (!left.empty()) { // merge left into center diff --git a/test/fixtures/dateline-metrics-tiles.json b/test/fixtures/dateline-metrics-tiles.json new file mode 100644 index 0000000..82f85e8 --- /dev/null +++ b/test/fixtures/dateline-metrics-tiles.json @@ -0,0 +1 @@ +{"z0-0-0":[{"geometry":[[[4160,1532],[4088,1464],[3952,1456],[3984,896],[4160,1217],[4160,1532]]],"type":3,"tags":{}},{"geometry":[[3312,1168]],"type":1,"tags":{}},{"geometry":[[[3472,2792],[4160,2782]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.24216869845698458}},{"geometry":[[[4160,2953],[3800,3112],[4160,3087]]],"type":2,"tags":{"mapbox_clip_start":0.5368896071622057,"mapbox_clip_end":0.802430932087145}},{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.8816568047337279}},{"geometry":[[[3472,1488],[3992,2216],[4160,2370],[4160,2495],[3856,2384],[3616,2064],[3384,1744],[2744,1448],[2200,1736],[1672,2032],[1152,2384],[992,1904],[952,1520],[1400,1608],[1480,1800],[1840,1696],[1656,1384],[1232,1168],[616,1272],[152,1616],[-8,1464],[-64,1461],[-64,983],[112,1304],[280,1112],[680,816],[2304,816],[2792,1000],[2168,992],[1560,976],[2008,1216],[2896,1232],[3280,1168],[3472,1488]]],"type":3,"tags":{}},{"geometry":[[2248,2808]],"type":1,"tags":{}},{"geometry":[[2968,2272]],"type":1,"tags":{}},{"geometry":[[488,1736]],"type":1,"tags":{}},{"geometry":[[[3456,280],[4160,280]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.25553376666379907}},{"geometry":[[[4160,378],[3752,464],[4160,526]]],"type":2,"tags":{"mapbox_clip_start":0.5960823391063382,"mapbox_clip_end":0.8972126466121583}},{"geometry":[[[-64,2784],[464,2776],[-64,3009]]],"type":2,"tags":{"mapbox_clip_start":0.1971140568835921,"mapbox_clip_end":0.5861456563744839}},{"geometry":[[[-64,3096],[624,3048]]],"type":2,"tags":{"mapbox_clip_start":0.7572722879927781,"mapbox_clip_end":1}},{"geometry":[[[3944,1344],[4160,1311]]],"type":2,"tags":{"mapbox_clip_start":0,"mapbox_clip_end":0.27835051546391754}},{"geometry":[[[-64,2139],[224,2120]]],"type":2,"tags":{"mapbox_clip_start":0.7869822485207101,"mapbox_clip_end":1}},{"geometry":[[[-64,2253],[288,2576],[-64,2448],[-64,2253]]],"type":3,"tags":{}},{"geometry":[[368,2176]],"type":1,"tags":{}},{"geometry":[[[-64,280],[528,280],[-64,405]]],"type":2,"tags":{"mapbox_clip_start":0.20907308181583562,"mapbox_clip_end":0.6435660880356269}},{"geometry":[[[-64,506],[344,568]]],"type":2,"tags":{"mapbox_clip_start":0.8502241422062877,"mapbox_clip_end":1}},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{"mapbox_clip_start":0.1134020618556701,"mapbox_clip_end":1}}]} diff --git a/test/fixtures/dateline-tiles.json b/test/fixtures/dateline-tiles.json index eed401f..c82915f 100644 --- a/test/fixtures/dateline-tiles.json +++ b/test/fixtures/dateline-tiles.json @@ -1 +1 @@ -{"z0-0-0":[{"geometry":[[[4160,1532],[4088,1464],[3952,1456],[3984,896],[4160,1217],[4160,1532]]],"type":3,"tags":{}},{"geometry":[[3312,1168]],"type":1,"tags":{}},{"geometry":[[[3472,2792],[4160,2782]],[[4160,2953],[3800,3112],[4160,3087]]],"type":2,"tags":{}},{"geometry":[[[3472,1488],[3992,2216],[4160,2370],[4160,2495],[3856,2384],[3616,2064],[3384,1744],[2744,1448],[2200,1736],[1672,2032],[1152,2384],[992,1904],[952,1520],[1400,1608],[1480,1800],[1840,1696],[1656,1384],[1232,1168],[616,1272],[152,1616],[-8,1464],[-64,1461],[-64,983],[112,1304],[280,1112],[680,816],[2304,816],[2792,1000],[2168,992],[1560,976],[2008,1216],[2896,1232],[3280,1168],[3472,1488]]],"type":3,"tags":{}},{"geometry":[[2248,2808]],"type":1,"tags":{}},{"geometry":[[2968,2272]],"type":1,"tags":{}},{"geometry":[[488,1736]],"type":1,"tags":{}},{"geometry":[[[3456,280],[4160,280]],[[4160,378],[3752,464],[4160,526]]],"type":2,"tags":{}},{"geometry":[[[-64,2784],[464,2776],[-64,3009]],[[-64,3096],[624,3048]]],"type":2,"tags":{}},{"geometry":[[[-64,2253],[288,2576],[-64,2448],[-64,2253]]],"type":3,"tags":{}},{"geometry":[[368,2176]],"type":1,"tags":{}},{"geometry":[[[-64,280],[528,280],[-64,405]],[[-64,506],[344,568]]],"type":2,"tags":{}}]} +{"z0-0-0":[{"geometry":[[[4160,1532],[4088,1464],[3952,1456],[3984,896],[4160,1217],[4160,1532]]],"type":3,"tags":{}},{"geometry":[[3312,1168]],"type":1,"tags":{}},{"geometry":[[[3472,2792],[4160,2782]],[[4160,2953],[3800,3112],[4160,3087]]],"type":2,"tags":{}},{"geometry":[[[2968,2208],[4160,2130]]],"type":2,"tags":{}},{"geometry":[[[3472,1488],[3992,2216],[4160,2370],[4160,2495],[3856,2384],[3616,2064],[3384,1744],[2744,1448],[2200,1736],[1672,2032],[1152,2384],[992,1904],[952,1520],[1400,1608],[1480,1800],[1840,1696],[1656,1384],[1232,1168],[616,1272],[152,1616],[-8,1464],[-64,1461],[-64,983],[112,1304],[280,1112],[680,816],[2304,816],[2792,1000],[2168,992],[1560,976],[2008,1216],[2896,1232],[3280,1168],[3472,1488]]],"type":3,"tags":{}},{"geometry":[[2248,2808]],"type":1,"tags":{}},{"geometry":[[2968,2272]],"type":1,"tags":{}},{"geometry":[[488,1736]],"type":1,"tags":{}},{"geometry":[[[3456,280],[4160,280]],[[4160,378],[3752,464],[4160,526]]],"type":2,"tags":{}},{"geometry":[[[-64,2784],[464,2776],[-64,3009]],[[-64,3096],[624,3048]]],"type":2,"tags":{}},{"geometry":[[[3944,1344],[4160,1311]],[[-64,2139],[224,2120]]],"type":2,"tags":{}},{"geometry":[[[-64,2253],[288,2576],[-64,2448],[-64,2253]]],"type":3,"tags":{}},{"geometry":[[368,2176]],"type":1,"tags":{}},{"geometry":[[[-64,280],[528,280],[-64,405]],[[-64,506],[344,568]]],"type":2,"tags":{}},{"geometry":[[[-64,1330],[624,1224]]],"type":2,"tags":{}}]} diff --git a/test/fixtures/dateline.json b/test/fixtures/dateline.json index f7f0cfc..dce97b6 100644 --- a/test/fixtures/dateline.json +++ b/test/fixtures/dateline.json @@ -252,6 +252,32 @@ ] ] } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "MultiLineString", + "coordinates": [[ + [ + 166.640625, + 52.482780222078226 + ], + [ + 234.84375000000003, + 58.44773280389084 + ] + ], [ + [ + -279.140625, + -13.923403897723334 + ], + [ + -160.3125, + -6.315298538330033 + ] + ]] + } } ] } diff --git a/test/test.cpp b/test/test.cpp index 3d75f9c..d9f3204 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace mapbox::geojsonvt; @@ -107,6 +108,29 @@ TEST(Clip, Polylines) { ASSERT_EQ(expected2, clipped2); } +TEST(Clip, PolylinesLineMetrics) { + const detail::vt_line_string points1{ { 0, 0 }, { 50, 0 }, { 50, 10 }, { 20, 10 }, + { 20, 20 }, { 30, 20 }, { 30, 30 }, { 50, 30 }, + { 50, 40 }, { 25, 40 }, { 25, 50 }, { 0, 50 }, + { 0, 60 }, { 25, 60 } }; + + const auto clip = detail::clipper<0>{ 10, 40, true /*lineMetrics*/ }; + + const auto clipped = clip(points1).get(); + + ASSERT_EQ(clipped[0].segStart, 10.0); + ASSERT_EQ(clipped[0].segEnd, 40.0); + + ASSERT_EQ(clipped[1].segStart, 70.0); + ASSERT_EQ(clipped[1].segEnd, 130.0); + + ASSERT_EQ(clipped[2].segStart, 160.0); + ASSERT_EQ(clipped[2].segEnd, 200.0); + + ASSERT_EQ(clipped[3].segStart, 230.0); + ASSERT_EQ(clipped[3].segEnd, 245.0); +} + TEST(Clip, Polygons) { const detail::vt_polygon points1{ { { 0, 0 }, { 50, 0 }, @@ -278,11 +302,12 @@ TEST(GetTile, Projection) { } std::map> -genTiles(const std::string& data, uint8_t maxZoom = 0, uint32_t maxPoints = 10000) { +genTiles(const std::string& data, uint8_t maxZoom = 0, uint32_t maxPoints = 10000, bool lineMetrics = false) { Options options; options.maxZoom = 14; options.indexMaxZoom = maxZoom; options.indexMaxPoints = maxPoints; + options.lineMetrics = lineMetrics; const auto geojson = mapbox::geojson::parse(data); GeoJSONVT index{ geojson, options }; @@ -303,20 +328,23 @@ struct Arguments { Arguments(const std::string inputFile_, const std::string expectedFile_, const uint32_t maxZoom_ = 0, - const uint32_t maxPoints_ = 10000) + const uint32_t maxPoints_ = 10000, + const bool lineMetrics_ = false) : inputFile(inputFile_), expectedFile(expectedFile_), maxZoom(maxZoom_), - maxPoints(maxPoints_){}; + maxPoints(maxPoints_), + lineMetrics(lineMetrics_) {} const std::string inputFile; const std::string expectedFile; const uint32_t maxZoom; const uint32_t maxPoints; + const bool lineMetrics; }; ::std::ostream& operator<<(::std::ostream& os, const Arguments& a) { - return os << a.inputFile << " (" << a.maxZoom << ", " << a.maxPoints << ")"; + return os << a.inputFile << " (" << a.maxZoom << ", " << a.maxPoints << ", " << a.lineMetrics << ")"; } class TileTest : public ::testing::TestWithParam {}; @@ -324,7 +352,7 @@ class TileTest : public ::testing::TestWithParam {}; TEST_P(TileTest, Tiles) { const auto& params = GetParam(); - const auto actual = genTiles(loadFile(params.inputFile), params.maxZoom, params.maxPoints); + const auto actual = genTiles(loadFile(params.inputFile), params.maxZoom, params.maxPoints, params.lineMetrics); const auto expected = parseJSONTiles(loadFile(params.expectedFile)); ASSERT_EQ(expected == actual, true); @@ -359,6 +387,7 @@ INSTANTIATE_TEST_CASE_P( ::testing::ValuesIn(std::vector{ { "test/fixtures/us-states.json", "test/fixtures/us-states-tiles.json", 7, 200 }, { "test/fixtures/dateline.json", "test/fixtures/dateline-tiles.json", 7, 200 }, + { "test/fixtures/dateline.json", "test/fixtures/dateline-metrics-tiles.json", 0, 10000, true }, { "test/fixtures/feature.json", "test/fixtures/feature-tiles.json" }, { "test/fixtures/collection.json", "test/fixtures/collection-tiles.json" }, { "test/fixtures/single-geom.json", "test/fixtures/single-geom-tiles.json" } })); diff --git a/test/util.cpp b/test/util.cpp index fa2e023..709d0d6 100644 --- a/test/util.cpp +++ b/test/util.cpp @@ -35,11 +35,51 @@ std::string loadFile(const std::string& filename) { throw std::runtime_error("Error opening file"); } +namespace { + +struct ToDouble { + double operator()(double value) const { return value; } + double operator()(int64_t value) const { return double(value); } + double operator()(uint64_t value) const { return double(value); } + template + double operator()(const T&) const { return std::numeric_limits::quiet_NaN(); } +}; + +template +inline void compareValues(const T& a, const T& b) { + EXPECT_TRUE(a == b); +} + +void compareValues(const mapbox::geometry::value& a, + const mapbox::geometry::value& b) { + if (a == b) return; + + double a_as_double = mapbox::geometry::value::visit(a, ToDouble{}); + double b_as_double = mapbox::geometry::value::visit(b, ToDouble{}); + EXPECT_DOUBLE_EQ(a_as_double, b_as_double); +} + +template +void compareMaps(const MapType& a, const MapType& b) { + EXPECT_EQ(a.size(), b.size()); + for (auto it = a.begin(); it != a.end(); ++it) { + auto found = b.find(it->first); + if (found != b.end()) { + compareValues(it->second, found->second); + } else { + ADD_FAILURE(); + } + } +} + +} // namespace + bool operator==(const mapbox::geometry::feature& a, const mapbox::geometry::feature& b) { // EXPECT_EQ(a.geometry, b.geometry); EXPECT_EQ(typeid(a.geometry), typeid(b.geometry)); - EXPECT_EQ(a.properties, b.properties); + compareMaps(a.properties, b.properties); + EXPECT_EQ(a.id, b.id); return true; } @@ -149,7 +189,33 @@ parseJSONTile(const rapidjson::GenericValue, rapidjson::CrtAll feat.geometry = mapbox::geometry::point( static_cast(pt[0].GetInt()), static_cast(pt[1].GetInt())); } - // polygon geometry + // linestring geometry + } else if (geomType == 2) { + mapbox::geometry::multi_line_string multi_line; + const bool is_multi = geometry.Size() > 1; + for (rapidjson::SizeType j = 0; j < geometry.Size(); ++j) { + const auto& part = geometry[j]; + EXPECT_TRUE(part.IsArray()); + mapbox::geometry::line_string line_string; + for (rapidjson::SizeType i = 0; i < part.Size(); ++i) { + const auto& pt = part[i]; + EXPECT_TRUE(pt.IsArray()); + EXPECT_TRUE(pt.Size() >= 2); + EXPECT_TRUE(pt[0].IsNumber()); + EXPECT_TRUE(pt[1].IsNumber()); + line_string.emplace_back(static_cast(pt[0].GetInt()), + static_cast(pt[1].GetInt())); + } + if (!is_multi) { + feat.geometry = line_string; + break; + } else { + multi_line.emplace_back(line_string); + } + } + if (is_multi) + feat.geometry = multi_line; + // polygon geometry } else if (geomType == 3) { mapbox::geometry::polygon poly; for (rapidjson::SizeType j = 0; j < geometry.Size(); ++j) {