diff options
22 files changed, 1072 insertions, 241 deletions
diff --git a/src/3rdparty/earcut/LICENSE b/src/3rdparty/earcut/LICENSE new file mode 100644 index 00000000..8bafb577 --- /dev/null +++ b/src/3rdparty/earcut/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2015, Mapbox + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/src/3rdparty/earcut/earcut.hpp b/src/3rdparty/earcut/earcut.hpp new file mode 100644 index 00000000..287be028 --- /dev/null +++ b/src/3rdparty/earcut/earcut.hpp @@ -0,0 +1,779 @@ +#pragma once +#ifndef EARCUT_HPP +#define EARCUT_HPP + +#include <array> +#include <algorithm> +#include <cassert> +#include <cmath> +#include <memory> +#include <vector> + +namespace mapbox { + +namespace util { + +template <std::size_t I, typename T> struct nth { + + inline static typename std::tuple_element<I, T>::type + get(const T& t) { return std::get<I>(t); } +}; + + +} + +namespace detail { + +template <typename N = uint32_t> +class Earcut { +public: + std::vector<N> indices; + N vertices = 0; + + template <typename Polygon> + void operator()(const Polygon& points); + +private: + struct Node { + Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {} + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + const N i; + const double x; + const double y; + + // previous and next vertice nodes in a polygon ring + Node* prev = nullptr; + Node* next = nullptr; + + // z-order curve value + int32_t z = 0; + + // previous and next nodes in z-order + Node* prevZ = nullptr; + Node* nextZ = nullptr; + + // indicates whether this is a steiner point + bool steiner = false; + }; + + template <typename Ring> Node* linkedList(const Ring& points, const bool clockwise); + Node* filterPoints(Node* start, Node* end = nullptr); + void earcutLinked(Node* ear, int pass = 0); + bool isEar(Node* ear); + bool isEarHashed(Node* ear); + Node* cureLocalIntersections(Node* start); + void splitEarcut(Node* start); + template <typename Polygon> Node* eliminateHoles(const Polygon& points, Node* outerNode); + void eliminateHole(Node* hole, Node* outerNode); + Node* findHoleBridge(Node* hole, Node* outerNode); + void indexCurve(Node* start); + Node* sortLinked(Node* list); + int32_t zOrder(const double x_, const double y_); + Node* getLeftmost(Node* start); + bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const; + bool isValidDiagonal(Node* a, Node* b); + double area(const Node* p, const Node* q, const Node* r) const; + bool equals(const Node* p1, const Node* p2); + bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2); + bool intersectsPolygon(const Node* a, const Node* b); + bool locallyInside(const Node* a, const Node* b); + bool middleInside(const Node* a, const Node* b); + Node* splitPolygon(Node* a, Node* b); + template <typename Point> Node* insertNode(N i, const Point& p, Node* last); + void removeNode(Node* p); + + bool hashing; + double minX, maxX; + double minY, maxY; + double size; + + template <typename T, typename Alloc = std::allocator<T>> + class ObjectPool { + public: + ObjectPool() { } + ObjectPool(std::size_t blockSize_) { + reset(blockSize_); + } + ~ObjectPool() { + clear(); + } + template <typename... Args> + T* construct(Args&&... args) { + if (currentIndex >= blockSize) { + currentBlock = alloc.allocate(blockSize); + allocations.emplace_back(currentBlock); + currentIndex = 0; + } + T* object = ¤tBlock[currentIndex++]; + alloc.construct(object, std::forward<Args>(args)...); + return object; + } + void reset(std::size_t newBlockSize) { + for (auto allocation : allocations) alloc.deallocate(allocation, blockSize); + allocations.clear(); + blockSize = std::max<std::size_t>(1, newBlockSize); + currentBlock = nullptr; + currentIndex = blockSize; + } + void clear() { reset(blockSize); } + private: + T* currentBlock = nullptr; + std::size_t currentIndex = 1; + std::size_t blockSize = 1; + std::vector<T*> allocations; + Alloc alloc; + }; + ObjectPool<Node> nodes; +}; + +template <typename N> template <typename Polygon> +void Earcut<N>::operator()(const Polygon& points) { + // reset + indices.clear(); + vertices = 0; + + if (points.empty()) return; + + double x; + double y; + size = 0; + int threshold = 80; + std::size_t len = 0; + + for (size_t i = 0; threshold >= 0 && i < points.size(); i++) { + threshold -= static_cast<int>(points[i].size()); + len += points[i].size(); + } + + //estimate size of nodes and indices + nodes.reset(len * 3 / 2); + indices.reserve(len + points[0].size()); + + Node* outerNode = linkedList(points[0], true); + if (!outerNode) return; + + if (points.size() > 1) outerNode = eliminateHoles(points, outerNode); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + hashing = threshold < 0; + if (hashing) { + Node* p = outerNode->next; + minX = maxX = p->x; + minY = maxY = p->y; + do { + x = p->x; + y = p->y; + minX = (std::min)(minX, x); + minY = (std::min)(minY, y); + maxX = (std::max)(maxX, x); + maxY = (std::max)(maxY, y); + p = p->next; + } while (p != outerNode); + + // minX, minY and size are later used to transform coords into integers for z-order calculation + size = (std::max)(maxX - minX, maxY - minY); + } + + earcutLinked(outerNode); + + nodes.clear(); +} + +// create a circular doubly linked list from polygon points in the specified winding order +template <typename N> template <typename Ring> +typename Earcut<N>::Node* +Earcut<N>::linkedList(const Ring& points, const bool clockwise) { + using Point = typename Ring::value_type; + double sum = 0; + const int len = static_cast<int>(points.size()); + int i, j; + Point p1, p2; + Node* last = nullptr; + + // calculate original winding order of a polygon ring + for (i = 0, j = len - 1; i < len; j = i++) { + p1 = points[i]; + p2 = points[j]; + const double p20 = util::nth<0, Point>::get(p2); + const double p10 = util::nth<0, Point>::get(p1); + const double p11 = util::nth<1, Point>::get(p1); + const double p21 = util::nth<1, Point>::get(p2); + sum += (p20 - p10) * (p11 + p21); + } + + // link points into circular doubly-linked list in the specified winding order + if (clockwise == (sum > 0)) { + for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last); + } else { + for (i = len - 1; i >= 0; i--) last = insertNode(vertices + i, points[i], last); + } + + if (last && equals(last, last->next)) { + removeNode(last); + last = last->next; + } + + vertices += len; + + return last; +} + +// eliminate colinear or duplicate points +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::filterPoints(Node* start, Node* end) { + if (!end) end = start; + + Node* p = start; + bool again; + do { + again = false; + + if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) { + removeNode(p); + p = end = p->prev; + + if (p == p->next) return nullptr; + again = true; + + } else { + p = p->next; + } + } while (again || p != end); + + return end; +} + +// main ear slicing loop which triangulates a polygon (given as a linked list) +template <typename N> +void Earcut<N>::earcutLinked(Node* ear, int pass) { + if (!ear) return; + + // interlink polygon nodes in z-order + if (!pass && hashing) indexCurve(ear); + + Node* stop = ear; + Node* prev; + Node* next; + + int iterations = 0; + + // iterate through ears, slicing them one by one + while (ear->prev != ear->next) { + iterations++; + prev = ear->prev; + next = ear->next; + + if (hashing ? isEarHashed(ear) : isEar(ear)) { + // cut off the triangle + indices.emplace_back(prev->i); + indices.emplace_back(ear->i); + indices.emplace_back(next->i); + + removeNode(ear); + + // skipping the next vertice leads to less sliver triangles + ear = next->next; + stop = next->next; + + continue; + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + if (ear == stop) { + // try filtering points and slicing again + if (!pass) earcutLinked(filterPoints(ear), 1); + + // if this didn't work, try curing all small self-intersections locally + else if (pass == 1) { + ear = cureLocalIntersections(ear); + earcutLinked(ear, 2); + + // as a last resort, try splitting the remaining polygon into two + } else if (pass == 2) splitEarcut(ear); + + break; + } + } +} + +// check whether a polygon node forms a valid ear with adjacent nodes +template <typename N> +bool Earcut<N>::isEar(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + Node* p = ear->next->next; + + while (p != ear->prev) { + if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->next; + } + + return true; +} + +template <typename N> +bool Earcut<N>::isEarHashed(Node* ear) { + const Node* a = ear->prev; + const Node* b = ear; + const Node* c = ear->next; + + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + const double minTX = (std::min)(a->x, (std::min)(b->x, c->x)); + const double minTY = (std::min)(a->y, (std::min)(b->y, c->y)); + const double maxTX = (std::max)(a->x, (std::max)(b->x, c->x)); + const double maxTY = (std::max)(a->y, (std::max)(b->y, c->y)); + + // z-order range for the current triangle bbox; + const int32_t minZ = zOrder(minTX, minTY); + const int32_t maxZ = zOrder(maxTX, maxTY); + + // first look for points inside the triangle in increasing z-order + Node* p = ear->nextZ; + + while (p && p->z <= maxZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->nextZ; + } + + // then look for points in decreasing z-order + p = ear->prevZ; + + while (p && p->z >= minZ) { + if (p != ear->prev && p != ear->next && + pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) && + area(p->prev, p, p->next) >= 0) return false; + p = p->prevZ; + } + + return true; +} + +// go through all polygon nodes and cure small local self-intersections +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::cureLocalIntersections(Node* start) { + Node* p = start; + do { + Node* a = p->prev; + Node* b = p->next->next; + + // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) + if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) { + indices.emplace_back(a->i); + indices.emplace_back(p->i); + indices.emplace_back(b->i); + + // remove two nodes involved + removeNode(p); + removeNode(p->next); + + p = start = b; + } + p = p->next; + } while (p != start); + + return p; +} + +// try splitting polygon into two and triangulate them independently +template <typename N> +void Earcut<N>::splitEarcut(Node* start) { + // look for a valid diagonal that divides the polygon into two + Node* a = start; + do { + Node* b = a->next->next; + while (b != a->prev) { + if (a->i != b->i && isValidDiagonal(a, b)) { + // split the polygon in two by the diagonal + Node* c = splitPolygon(a, b); + + // filter colinear points around the cuts + a = filterPoints(a, a->next); + c = filterPoints(c, c->next); + + // run earcut on each half + earcutLinked(a); + earcutLinked(c); + return; + } + b = b->next; + } + a = a->next; + } while (a != start); +} + +// link every hole into the outer loop, producing a single-ring polygon without holes +template <typename N> template <typename Polygon> +typename Earcut<N>::Node* +Earcut<N>::eliminateHoles(const Polygon& points, Node* outerNode) { + const size_t len = points.size(); + + std::vector<Node*> queue; + for (size_t i = 1; i < len; i++) { + Node* list = linkedList(points[i], false); + if (list) { + if (list == list->next) list->steiner = true; + queue.push_back(getLeftmost(list)); + } + } + std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) { + return a->x < b->x; + }); + + // process holes from left to right + for (size_t i = 0; i < queue.size(); i++) { + eliminateHole(queue[i], outerNode); + outerNode = filterPoints(outerNode, outerNode->next); + } + + return outerNode; +} + +// find a bridge between vertices that connects hole with an outer ring and and link it +template <typename N> +void Earcut<N>::eliminateHole(Node* hole, Node* outerNode) { + outerNode = findHoleBridge(hole, outerNode); + if (outerNode) { + Node* b = splitPolygon(outerNode, hole); + filterPoints(b, b->next); + } +} + +// David Eberly's algorithm for finding a bridge between hole and outer polygon +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) { + Node* p = outerNode; + double hx = hole->x; + double hy = hole->y; + double qx = -std::numeric_limits<double>::infinity(); + Node* m = nullptr; + + // find a segment intersected by a ray from the hole's leftmost Vertex to the left; + // segment's endpoint with lesser x will be potential connection Vertex + do { + if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) { + double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y); + if (x <= hx && x > qx) { + qx = x; + if (x == hx) { + if (hy == p->y) return p; + if (hy == p->next->y) return p->next; + } + m = p->x < p->next->x ? p : p->next; + } + } + p = p->next; + } while (p != outerNode); + + if (!m) return 0; + + if (hx == qx) return m->prev; + + // look for points inside the triangle of hole Vertex, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the Vertex of the minimum angle with the ray as connection Vertex + + const Node* stop = m; + double tanMin = std::numeric_limits<double>::infinity(); + double tanCur = 0; + + p = m->next; + double mx = m->x; + double my = m->y; + + while (p != stop) { + if (hx >= p->x && p->x >= mx && hx != p->x && + pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) { + + tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential + + if ((tanCur < tanMin || (tanCur == tanMin && p->x > m->x)) && locallyInside(p, hole)) { + m = p; + tanMin = tanCur; + } + } + + p = p->next; + } + + return m; +} + +// interlink polygon nodes in z-order +template <typename N> +void Earcut<N>::indexCurve(Node* start) { + assert(start); + Node* p = start; + + do { + p->z = p->z ? p->z : zOrder(p->x, p->y); + p->prevZ = p->prev; + p->nextZ = p->next; + p = p->next; + } while (p != start); + + p->prevZ->nextZ = nullptr; + p->prevZ = nullptr; + + sortLinked(p); +} + +// Simon Tatham's linked list merge sort algorithm +// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::sortLinked(Node* list) { + assert(list); + Node* p; + Node* q; + Node* e; + Node* tail; + int i, numMerges, pSize, qSize; + int inSize = 1; + + while (true) { + p = list; + list = nullptr; + tail = nullptr; + numMerges = 0; + + while (p) { + numMerges++; + q = p; + pSize = 0; + for (i = 0; i < inSize; i++) { + pSize++; + q = q->nextZ; + if (!q) break; + } + + qSize = inSize; + + while (pSize > 0 || (qSize > 0 && q)) { + + if (pSize == 0) { + e = q; + q = q->nextZ; + qSize--; + } else if (qSize == 0 || !q) { + e = p; + p = p->nextZ; + pSize--; + } else if (p->z <= q->z) { + e = p; + p = p->nextZ; + pSize--; + } else { + e = q; + q = q->nextZ; + qSize--; + } + + if (tail) tail->nextZ = e; + else list = e; + + e->prevZ = tail; + tail = e; + } + + p = q; + } + + tail->nextZ = nullptr; + + if (numMerges <= 1) return list; + + inSize *= 2; + } +} + +// z-order of a Vertex given coords and size of the data bounding box +template <typename N> +int32_t Earcut<N>::zOrder(const double x_, const double y_) { + // coords are transformed into non-negative 15-bit integer range + int32_t x = static_cast<int32_t>(32767.0 * (x_ - minX) / size); + int32_t y = static_cast<int32_t>(32767.0 * (y_ - minY) / size); + + x = (x | (x << 8)) & 0x00FF00FF; + x = (x | (x << 4)) & 0x0F0F0F0F; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + + return x | (y << 1); +} + +// find the leftmost node of a polygon ring +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::getLeftmost(Node* start) { + Node* p = start; + Node* leftmost = start; + do { + if (p->x < leftmost->x) leftmost = p; + p = p->next; + } while (p != start); + + return leftmost; +} + +// check if a point lies within a convex triangle +template <typename N> +bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const { + return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 && + (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 && + (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0; +} + +// check if a diagonal between two polygon nodes is valid (lies in polygon interior) +template <typename N> +bool Earcut<N>::isValidDiagonal(Node* a, Node* b) { + return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && + locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); +} + +// signed area of a triangle +template <typename N> +double Earcut<N>::area(const Node* p, const Node* q, const Node* r) const { + return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); +} + +// check if two points are equal +template <typename N> +bool Earcut<N>::equals(const Node* p1, const Node* p2) { + return p1->x == p2->x && p1->y == p2->y; +} + +// check if two segments intersect +template <typename N> +bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) { + if ((equals(p1, q1) && equals(p2, q2)) || + (equals(p1, q2) && equals(p2, q1))) return true; + return (area(p1, q1, p2) > 0) != (area(p1, q1, q2) > 0) && + (area(p2, q2, p1) > 0) != (area(p2, q2, q1) > 0); +} + +// check if a polygon diagonal intersects any polygon segments +template <typename N> +bool Earcut<N>::intersectsPolygon(const Node* a, const Node* b) { + const Node* p = a; + do { + if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i && + intersects(p, p->next, a, b)) return true; + p = p->next; + } while (p != a); + + return false; +} + +// check if a polygon diagonal is locally inside the polygon +template <typename N> +bool Earcut<N>::locallyInside(const Node* a, const Node* b) { + return area(a->prev, a, a->next) < 0 ? + area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 : + area(a, b, a->prev) < 0 || area(a, a->next, b) < 0; +} + +// check if the middle Vertex of a polygon diagonal is inside the polygon +template <typename N> +bool Earcut<N>::middleInside(const Node* a, const Node* b) { + const Node* p = a; + bool inside = false; + double px = (a->x + b->x) / 2; + double py = (a->y + b->y) / 2; + do { + if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y && + (px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x)) + inside = !inside; + p = p->next; + } while (p != a); + + return inside; +} + +// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits +// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a +// single ring +template <typename N> +typename Earcut<N>::Node* +Earcut<N>::splitPolygon(Node* a, Node* b) { + Node* a2 = nodes.construct(a->i, a->x, a->y); + Node* b2 = nodes.construct(b->i, b->x, b->y); + Node* an = a->next; + Node* bp = b->prev; + + a->next = b; + b->prev = a; + + a2->next = an; + an->prev = a2; + + b2->next = a2; + a2->prev = b2; + + bp->next = b2; + b2->prev = bp; + + return b2; +} + +// create a node and util::optionally link it with previous one (in a circular doubly linked list) +template <typename N> template <typename Point> +typename Earcut<N>::Node* +Earcut<N>::insertNode(N i, const Point& pt, Node* last) { + Node* p = nodes.construct(i, util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt)); + + if (!last) { + p->prev = p; + p->next = p; + + } else { + assert(last); + p->next = last->next; + p->prev = last; + last->next->prev = p; + last->next = p; + } + return p; +} + +template <typename N> +void Earcut<N>::removeNode(Node* p) { + p->next->prev = p->prev; + p->prev->next = p->next; + + if (p->prevZ) p->prevZ->nextZ = p->nextZ; + if (p->nextZ) p->nextZ->prevZ = p->prevZ; +} +} + +template <typename N = uint32_t, typename Polygon> +std::vector<N> earcut(const Polygon& poly) { + mapbox::detail::Earcut<N> earcut; + earcut(poly); + return earcut.indices; +} +} +#endif //EARCUT_HPP diff --git a/src/3rdparty/earcut/qt_attribution.json b/src/3rdparty/earcut/qt_attribution.json new file mode 100644 index 00000000..0e52989c --- /dev/null +++ b/src/3rdparty/earcut/qt_attribution.json @@ -0,0 +1,13 @@ +{ + "Id": "earcut", + "Name": "Earcut Polygon Triangulation Library", + "QDocModule": "qtlocation", + "QtUsage": "Used in the QML plugin of Qt Location.", + + "Description": "A C++ port of earcut.js, a fast, header-only polygon triangulation library.", + "Homepage": "https://github.com/mapbox/earcut.hpp", + "LicenseId": "ISC", + "License": "ISC License", + "LicenseFile": "LICENSE", + "Copyright": "Copyright (c) 2015 Mapbox" +} diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp index 274225c0..5f002bf9 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativecirclemapitem.cpp @@ -278,7 +278,8 @@ static bool crossEarthPole(const QGeoCoordinate ¢er, qreal distance) static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, const QGeoCoordinate ¢er, qreal distance, - int steps) + int steps, + QGeoCoordinate &leftBound) { // Calculate points based on great-circle distance // Calculation is the same as GeoCoordinate's atDistanceAndAzimuth function @@ -287,15 +288,17 @@ static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, // pre-calculations steps = qMax(steps, 3); qreal centerLon = center.longitude(); + qreal minLon = centerLon; qreal latRad = QLocationUtils::radians(center.latitude()); qreal lonRad = QLocationUtils::radians(centerLon); qreal cosLatRad = std::cos(latRad); qreal sinLatRad = std::sin(latRad); - qreal ratio = (distance / (QLocationUtils::earthMeanRadius())); + qreal ratio = (distance / QLocationUtils::earthMeanRadius()); qreal cosRatio = std::cos(ratio); qreal sinRatio = std::sin(ratio); qreal sinLatRad_x_cosRatio = sinLatRad * cosRatio; qreal cosLatRad_x_sinRatio = cosLatRad * sinRatio; + int idx = 0; for (int i = 0; i < steps; ++i) { qreal azimuthRad = 2 * M_PI * i / steps; qreal resultLatRad = std::asin(sinLatRad_x_cosRatio @@ -306,7 +309,17 @@ static void calculatePeripheralPoints(QList<QGeoCoordinate> &path, qreal lon2 = QLocationUtils::wrapLong(QLocationUtils::degrees(resultLonRad)); path << QGeoCoordinate(lat2, lon2, center.altitude()); + // Consider only points in the left half of the circle for the left bound. + if (azimuthRad > M_PI) { + if (lon2 > centerLon) // if point and center are on different hemispheres + lon2 -= 360; + if (lon2 < minLon) { + minLon = lon2; + idx = i; + } + } } + leftBound = path.at(idx); } QDeclarativeCircleMapItem::QDeclarativeCircleMapItem(QQuickItem *parent) @@ -480,8 +493,10 @@ void QDeclarativeCircleMapItem::updatePolish() int pathCount = circlePath.size(); bool preserve = preserveCircleGeometry(circlePath, circle_.center(), circle_.radius()); - geometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_ - geometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft()); + // using leftBound_ instead of the analytically calculated circle_.boundingGeoRectangle().topLeft()); + // to fix QTBUG-62154 + geometry_.setPreserveGeometry(true, leftBound_); // to set the geoLeftBound_ + geometry_.setPreserveGeometry(preserve, leftBound_); bool invertedCircle = false; if (crossEarthPole(circle_.center(), circle_.radius()) && circlePath.size() == pathCount) { @@ -506,8 +521,8 @@ void QDeclarativeCircleMapItem::updatePolish() std::reverse(closedPath.begin(), closedPath.end()); } - borderGeometry_.setPreserveGeometry(true, circle_.boundingGeoRectangle().topLeft()); // to set the geoLeftBound_ - borderGeometry_.setPreserveGeometry(preserve, circle_.boundingGeoRectangle().topLeft()); + borderGeometry_.setPreserveGeometry(true, leftBound_); + borderGeometry_.setPreserveGeometry(preserve, leftBound_); // Use srcOrigin_ from fill geometry after clipping to ensure that translateToCommonOrigin won't fail. const QGeoCoordinate &geometryOrigin = geometry_.origin(); @@ -553,7 +568,7 @@ void QDeclarativeCircleMapItem::updateCirclePath() if (!map()) return; QList<QGeoCoordinate> path; - calculatePeripheralPoints(path, circle_.center(), circle_.radius(), CircleSamples); + calculatePeripheralPoints(path, circle_.center(), circle_.radius(), CircleSamples, leftBound_); circlePath_.clear(); for (const QGeoCoordinate &c : path) circlePath_ << map()->geoProjection().geoToMapProjection(c); diff --git a/src/location/declarativemaps/qdeclarativecirclemapitem_p.h b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h index 511e3b17..15774427 100644 --- a/src/location/declarativemaps/qdeclarativecirclemapitem_p.h +++ b/src/location/declarativemaps/qdeclarativecirclemapitem_p.h @@ -121,6 +121,7 @@ private: QDeclarativeMapLineProperties border_; QColor color_; QList<QDoubleVector2D> circlePath_; + QGeoCoordinate leftBound_; bool dirtyMaterial_; QGeoMapCircleGeometry geometry_; QGeoMapPolylineGeometry borderGeometry_; diff --git a/src/location/declarativemaps/qdeclarativegeomap.cpp b/src/location/declarativemaps/qdeclarativegeomap.cpp index 19bc57ce..37cfc303 100644 --- a/src/location/declarativemaps/qdeclarativegeomap.cpp +++ b/src/location/declarativemaps/qdeclarativegeomap.cpp @@ -255,6 +255,8 @@ QDeclarativeGeoMap::~QDeclarativeGeoMap() delete m_copyrights.data(); m_copyrights.clear(); + + delete m_map; } /*! @@ -293,17 +295,17 @@ void QDeclarativeGeoMap::onMapChildrenChanged() copyrights = m_copyrights.data(); - connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + connect(m_map, SIGNAL(copyrightsChanged(QImage)), copyrights, SLOT(copyrightsChanged(QImage))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + connect(m_map, SIGNAL(copyrightsChanged(QImage)), this, SIGNAL(copyrightsChanged(QImage))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + connect(m_map, SIGNAL(copyrightsChanged(QString)), copyrights, SLOT(copyrightsChanged(QString))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + connect(m_map, SIGNAL(copyrightsChanged(QString)), this, SIGNAL(copyrightsChanged(QString))); - connect(m_map.data(), SIGNAL(copyrightsStyleSheetChanged(QString)), + connect(m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), copyrights, SLOT(onCopyrightsStyleSheetChanged(QString))); connect(copyrights, SIGNAL(linkActivated(QString)), @@ -739,7 +741,7 @@ void QDeclarativeGeoMap::onCameraCapabilitiesChanged(const QGeoCameraCapabilitie */ void QDeclarativeGeoMap::mappingManagerInitialized() { - m_map = QPointer<QGeoMap>(m_mappingManager->createMap(this)); + m_map = m_mappingManager->createMap(this); if (!m_map) return; @@ -784,11 +786,11 @@ void QDeclarativeGeoMap::mappingManagerInitialized() QImage copyrightImage; if (!m_initialized && width() > 0 && height() > 0) { QMetaObject::Connection copyrightStringCatcherConnection = - connect(m_map.data(), + connect(m_map, QOverload<const QString &>::of(&QGeoMap::copyrightsChanged), [©rightString](const QString ©){ copyrightString = copy; }); QMetaObject::Connection copyrightImageCatcherConnection = - connect(m_map.data(), + connect(m_map, QOverload<const QImage &>::of(&QGeoMap::copyrightsChanged), [©rightImage](const QImage ©){ copyrightImage = copy; }); m_map->setViewportSize(QSize(width(), height())); @@ -800,28 +802,28 @@ void QDeclarativeGeoMap::mappingManagerInitialized() m_copyrights = new QDeclarativeGeoMapCopyrightNotice(this); m_copyrights->onCopyrightsStyleSheetChanged(m_map->copyrightsStyleSheet()); - connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + connect(m_map, SIGNAL(copyrightsChanged(QImage)), m_copyrights.data(), SLOT(copyrightsChanged(QImage))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QImage)), + connect(m_map, SIGNAL(copyrightsChanged(QImage)), this, SIGNAL(copyrightsChanged(QImage))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + connect(m_map, SIGNAL(copyrightsChanged(QString)), m_copyrights.data(), SLOT(copyrightsChanged(QString))); - connect(m_map.data(), SIGNAL(copyrightsChanged(QString)), + connect(m_map, SIGNAL(copyrightsChanged(QString)), this, SIGNAL(copyrightsChanged(QString))); if (!copyrightString.isEmpty()) - emit m_map.data()->copyrightsChanged(copyrightString); + emit m_map->copyrightsChanged(copyrightString); else if (!copyrightImage.isNull()) - emit m_map.data()->copyrightsChanged(copyrightImage); + emit m_map->copyrightsChanged(copyrightImage); - connect(m_map.data(), SIGNAL(copyrightsStyleSheetChanged(QString)), + connect(m_map, SIGNAL(copyrightsStyleSheetChanged(QString)), m_copyrights.data(), SLOT(onCopyrightsStyleSheetChanged(QString))); connect(m_copyrights.data(), SIGNAL(linkActivated(QString)), this, SIGNAL(copyrightLinkActivated(QString))); - connect(m_map.data(), &QGeoMap::sgNodeChanged, this, &QQuickItem::update); - connect(m_map.data(), &QGeoMap::cameraCapabilitiesChanged, this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged); + connect(m_map, &QGeoMap::sgNodeChanged, this, &QQuickItem::update); + connect(m_map, &QGeoMap::cameraCapabilitiesChanged, this, &QDeclarativeGeoMap::onCameraCapabilitiesChanged); // set visibility of copyright notice m_copyrights->setCopyrightsVisible(m_copyrightsVisible); @@ -1340,20 +1342,21 @@ QGeoShape QDeclarativeGeoMap::visibleRegion() const const QList<QDoubleVector2D> &visibleRegion = m_map->geoProjection().visibleRegion(); QGeoPath path; - for (const QDoubleVector2D &c: visibleRegion) + for (int i = 0; i < visibleRegion.size(); ++i) { + const QDoubleVector2D &c = visibleRegion.at(i); + // If a segment spans more than half of the map longitudinally, split in 2. + if (i && qAbs(visibleRegion.at(i-1).x() - c.x()) >= 0.5) { // This assumes a segment is never >= 1.0 (whole map span) + QDoubleVector2D extraPoint = (visibleRegion.at(i-1) + c) * 0.5; + path.addCoordinate(m_map->geoProjection().wrappedMapProjectionToGeo(extraPoint)); + } path.addCoordinate(m_map->geoProjection().wrappedMapProjectionToGeo(c)); - - QGeoRectangle vr = path.boundingGeoRectangle(); - - bool empty = vr.topLeft().latitude() == vr.bottomRight().latitude() || - qFuzzyCompare(vr.topLeft().longitude(), vr.bottomRight().longitude()); // QTBUG-57690 - - if (empty) { - vr.setTopLeft(QGeoCoordinate(vr.topLeft().latitude(), -180)); - vr.setBottomRight(QGeoCoordinate(vr.bottomRight().latitude(), 180)); + } + if (visibleRegion.size() >= 2 && qAbs(visibleRegion.last().x() - visibleRegion.first().x()) >= 0.5) { + QDoubleVector2D extraPoint = (visibleRegion.last() + visibleRegion.first()) * 0.5; + path.addCoordinate(m_map->geoProjection().wrappedMapProjectionToGeo(extraPoint)); } - return vr; + return path.boundingGeoRectangle(); } /*! @@ -1440,7 +1443,7 @@ void QDeclarativeGeoMap::fitViewportToGeoShape() QGeoCoordinate centerCoordinate = m_map->geoProjection().mapProjectionToGeo(center); // position camera to the center of bounding box - setCenter(centerCoordinate); + setProperty("center", QVariant::fromValue(centerCoordinate)); // not using setCenter(centerCoordinate) to honor a possible animation set on the center property // if the shape is empty we just change center position, not zoom double bboxWidth = (bottomRightPoint.x() - topLeftPoint.x()) * m_map->mapWidth(); @@ -1453,7 +1456,7 @@ void QDeclarativeGeoMap::fitViewportToGeoShape() bboxHeight / (height() - margins)); zoomRatio = std::log(zoomRatio) / std::log(2.0); double newZoom = qMax<double>(minimumZoomLevel(), zoomLevel() - zoomRatio); - setZoomLevel(newZoom); + setProperty("zoomLevel", QVariant::fromValue(newZoom)); // not using setZoomLevel(newZoom) to honor a possible animation set on the zoomLevel property } diff --git a/src/location/declarativemaps/qdeclarativegeomap_p.h b/src/location/declarativemaps/qdeclarativegeomap_p.h index 90e73420..5c568d8f 100644 --- a/src/location/declarativemaps/qdeclarativegeomap_p.h +++ b/src/location/declarativemaps/qdeclarativegeomap_p.h @@ -258,7 +258,7 @@ private: QList<QDeclarativeGeoMapType *> m_supportedMapTypes; QList<QDeclarativeGeoMapItemView *> m_mapViews; QQuickGeoMapGestureArea *m_gestureArea; - QPointer<QGeoMap> m_map; + QGeoMap* m_map = nullptr; QPointer<QDeclarativeGeoMapCopyrightNotice> m_copyrights; QList<QPointer<QDeclarativeGeoMapItemBase> > m_mapItems; QList<QPointer<QDeclarativeGeoMapItemGroup> > m_mapItemGroups; diff --git a/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp b/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp index 86d67cf8..7c0764aa 100644 --- a/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp +++ b/src/location/declarativemaps/qdeclarativegeomapquickitem.cpp @@ -159,7 +159,7 @@ QDeclarativeGeoMapQuickItem::~QDeclarativeGeoMapQuickItem() {} */ void QDeclarativeGeoMapQuickItem::setCoordinate(const QGeoCoordinate &coordinate) { - if (coordinate_ == coordinate || !coordinate.isValid()) + if (coordinate_ == coordinate) return; coordinate_ = coordinate; @@ -378,6 +378,13 @@ void QDeclarativeGeoMapQuickItem::updatePolish() this, SLOT(polishAndUpdate())); } + if (!coordinate_.isValid()) { + opacityContainer_->setVisible(false); + return; + } else { + opacityContainer_->setVisible(true); + } + QScopedValueRollback<bool> rollback(updatingGeometry_); updatingGeometry_ = true; diff --git a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp index daa1a9fc..48f66423 100644 --- a/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolygonmapitem.cpp @@ -53,6 +53,8 @@ /* poly2tri triangulator includes */ #include <clip2tri.h> +#include <earcut.hpp> +#include <array> QT_BEGIN_NAMESPACE @@ -185,7 +187,7 @@ void QGeoMapPolygonGeometry::updateSourcePoints(const QGeoMap &map, // 2) QList<QList<QDoubleVector2D> > clippedPaths; - const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion(); + const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().projectableRegion(); if (visibleRegion.size()) { c2t::clip2tri clipper; clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), true); @@ -254,16 +256,6 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) return; } - QDoubleVector2D origin = map.geoProjection().coordinateToItemPosition(srcOrigin_, false); - - // Create the viewport rect in the same coordinate system - // as the actual points - QRectF viewport(0, 0, map.viewportWidth(), map.viewportHeight()); - viewport.translate(-1 * origin.toPointF()); - - QPainterPath vpPath; - vpPath.addRect(viewport); - // The geometry has already been clipped against the visible region projection in wrapped mercator space. QPainterPath ppi = srcPath_; clear(); @@ -272,57 +264,50 @@ void QGeoMapPolygonGeometry::updateScreenPoints(const QGeoMap &map) if (ppi.elementCount() < 3) return; - // Intersection between the viewport and a concave polygon can create multiple polygons - // joined by a line at the viewport border, and poly2tri does not triangulate this very well - // so use the full src path if the resulting polygon is concave. - if (clipToViewport_) { - int changeInX = 0; - int changeInY = 0; - QPainterPath::Element e1 = ppi.elementAt(1); - QPainterPath::Element e = ppi.elementAt(0); - QVector2D edgeA(e1.x - e.x ,e1.y - e.y); - for (int i = 2; i <= ppi.elementCount(); ++i) { - e = ppi.elementAt(i % ppi.elementCount()); - if (e.x == e1.x && e.y == e1.y) - continue; - QVector2D edgeB(e.x - e1.x, e.y - e1.y); - if ((edgeA.x() < 0) == (edgeB.x() >= 0)) - changeInX++; - if ((edgeA.y() < 0) == (edgeB.y() >= 0)) - changeInY++; - edgeA = edgeB; - e1 = e; - } - if (changeInX > 2 || changeInY > 2) // polygon is concave - ppi = srcPath_; - } - // translate the path into top-left-centric coordinates QRectF bb = ppi.boundingRect(); ppi.translate(-bb.left(), -bb.top()); firstPointOffset_ = -1 * bb.topLeft(); ppi.closeSubpath(); - screenOutline_ = ppi; - QTriangleSet ts = qTriangulate(ppi); - qreal *vx = ts.vertices.data(); - - screenIndices_.reserve(ts.indices.size()); - screenVertices_.reserve(ts.vertices.size()); + using Coord = double; + using N = uint32_t; + using Point = std::array<Coord, 2>; + + std::vector<std::vector<Point>> polygon; + polygon.push_back(std::vector<Point>()); + std::vector<Point> &poly = polygon.front(); + // ... fill polygon structure with actual data + + for (int i = 0; i < ppi.elementCount(); ++i) { + const QPainterPath::Element e = ppi.elementAt(i); + if (e.isMoveTo() || i == ppi.elementCount() - 1 + || (qAbs(e.x - poly.front()[0]) < 0.1 + && qAbs(e.y - poly.front()[1]) < 0.1)) { + Point p = { e.x, e.y }; + poly.push_back( p ); + } else if (e.isLineTo()) { + Point p = { e.x, e.y }; + poly.push_back( p ); + } else { + qWarning("Unhandled element type in polygon painterpath"); + } + } - if (ts.indices.type() == QVertexIndexVector::UnsignedInt) { - const quint32 *ix = reinterpret_cast<const quint32 *>(ts.indices.data()); - for (int i = 0; i < (ts.indices.size()/3*3); ++i) - screenIndices_ << ix[i]; - } else { - const quint16 *ix = reinterpret_cast<const quint16 *>(ts.indices.data()); - for (int i = 0; i < (ts.indices.size()/3*3); ++i) - screenIndices_ << ix[i]; + if (poly.size() > 2) { + // Run tessellation + // Returns array of indices that refer to the vertices of the input polygon. + // Three subsequent indices form a triangle. + screenVertices_.clear(); + screenIndices_.clear(); + for (const auto &p : poly) + screenVertices_ << QPointF(p[0], p[1]); + std::vector<N> indices = mapbox::earcut<N>(polygon); + for (const auto &i: indices) + screenIndices_ << quint32(i); } - for (int i = 0; i < (ts.vertices.size()/2*2); i += 2) - screenVertices_ << QPointF(vx[i], vx[i + 1]); screenBounds_ = ppi.boundingRect(); } diff --git a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp index 72c43a49..a97271aa 100644 --- a/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp +++ b/src/location/declarativemaps/qdeclarativepolylinemapitem.cpp @@ -226,7 +226,7 @@ QList<QList<QDoubleVector2D> > QGeoMapPolylineGeometry::clipPath(const QGeoMap & // 2) QList<QList<QDoubleVector2D> > clippedPaths; - const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().visibleRegion(); + const QList<QDoubleVector2D> &visibleRegion = map.geoProjection().projectableRegion(); if (visibleRegion.size()) { c2t::clip2tri clipper; clipper.addSubjectPath(QClipperUtils::qListToPath(wrappedPath), false); @@ -337,120 +337,7 @@ void QGeoMapPolylineGeometry::updateSourcePoints(const QGeoMap &map, } //////////////////////////////////////////////////////////////////////////// -#if 0 // Old polyline to viewport clipping code. Retaining it for now. -/* Polyline clip */ - -enum ClipPointType { - InsidePoint = 0x00, - LeftPoint = 0x01, - RightPoint = 0x02, - BottomPoint = 0x04, - TopPoint = 0x08 -}; - -static inline int clipPointType(qreal x, qreal y, const QRectF &rect) -{ - int type = InsidePoint; - if (x < rect.left()) - type |= LeftPoint; - else if (x > rect.right()) - type |= RightPoint; - if (y < rect.top()) - type |= TopPoint; - else if (y > rect.bottom()) - type |= BottomPoint; - return type; -} - -static void clipSegmentToRect(qreal x0, qreal y0, qreal x1, qreal y1, - const QRectF &clipRect, - QVector<qreal> &outPoints, - QVector<QPainterPath::ElementType> &outTypes) -{ - int type0 = clipPointType(x0, y0, clipRect); - int type1 = clipPointType(x1, y1, clipRect); - bool accept = false; - - while (true) { - if (!(type0 | type1)) { - accept = true; - break; - } else if (type0 & type1) { - break; - } else { - qreal x = 0.0; - qreal y = 0.0; - int outsideType = type0 ? type0 : type1; - - if (outsideType & BottomPoint) { - x = x0 + (x1 - x0) * (clipRect.bottom() - y0) / (y1 - y0); - y = clipRect.bottom() - 0.1; - } else if (outsideType & TopPoint) { - x = x0 + (x1 - x0) * (clipRect.top() - y0) / (y1 - y0); - y = clipRect.top() + 0.1; - } else if (outsideType & RightPoint) { - y = y0 + (y1 - y0) * (clipRect.right() - x0) / (x1 - x0); - x = clipRect.right() - 0.1; - } else if (outsideType & LeftPoint) { - y = y0 + (y1 - y0) * (clipRect.left() - x0) / (x1 - x0); - x = clipRect.left() + 0.1; - } - - if (outsideType == type0) { - x0 = x; - y0 = y; - type0 = clipPointType(x0, y0, clipRect); - } else { - x1 = x; - y1 = y; - type1 = clipPointType(x1, y1, clipRect); - } - } - } - - if (accept) { - if (outPoints.size() >= 2) { - qreal lastX, lastY; - lastY = outPoints.at(outPoints.size() - 1); - lastX = outPoints.at(outPoints.size() - 2); - - if (!qFuzzyCompare(lastY, y0) || !qFuzzyCompare(lastX, x0)) { - outTypes << QPainterPath::MoveToElement; - outPoints << x0 << y0; - } - } else { - outTypes << QPainterPath::MoveToElement; - outPoints << x0 << y0; - } - - outTypes << QPainterPath::LineToElement; - outPoints << x1 << y1; - } -} - -static void clipPathToRect(const QVector<qreal> &points, - const QVector<QPainterPath::ElementType> &types, - const QRectF &clipRect, - QVector<qreal> &outPoints, - QVector<QPainterPath::ElementType> &outTypes) -{ - outPoints.clear(); - outPoints.reserve(points.size()); - outTypes.clear(); - outTypes.reserve(types.size()); - qreal lastX, lastY; - for (int i = 0; i < types.size(); ++i) { - if (i > 0 && types[i] != QPainterPath::MoveToElement) { - qreal x = points[i * 2], y = points[i * 2 + 1]; - clipSegmentToRect(lastX, lastY, x, y, clipRect, outPoints, outTypes); - } - - lastX = points[i * 2]; - lastY = points[i * 2 + 1]; - } -} -#endif /*! \internal */ diff --git a/src/location/declarativemaps/qquickgeomapgesturearea.cpp b/src/location/declarativemaps/qquickgeomapgesturearea.cpp index 39fd2397..fc2debc3 100644 --- a/src/location/declarativemaps/qquickgeomapgesturearea.cpp +++ b/src/location/declarativemaps/qquickgeomapgesturearea.cpp @@ -498,7 +498,7 @@ QQuickGeoMapGestureArea::QQuickGeoMapGestureArea(QDeclarativeGeoMap *map) /*! \internal */ -void QQuickGeoMapGestureArea::setMap(QPointer<QGeoMap> map) +void QQuickGeoMapGestureArea::setMap(QGeoMap *map) { if (m_map || !map) return; diff --git a/src/location/declarativemaps/qquickgeomapgesturearea_p.h b/src/location/declarativemaps/qquickgeomapgesturearea_p.h index 5daa39cf..0bc774d3 100644 --- a/src/location/declarativemaps/qquickgeomapgesturearea_p.h +++ b/src/location/declarativemaps/qquickgeomapgesturearea_p.h @@ -178,7 +178,7 @@ public: void setMaximumZoomLevel(qreal max); qreal maximumZoomLevel() const; - void setMap(QPointer<QGeoMap> map); + void setMap(QGeoMap* map); bool preventStealing() const; void setPreventStealing(bool prevent); @@ -267,7 +267,7 @@ private: void updateFlickParameters(const QPointF &pos); private: - QPointer<QGeoMap> m_map; + QGeoMap* m_map; QDeclarativeGeoMap *m_declarativeMap; bool m_enabled; diff --git a/src/location/location.pro b/src/location/location.pro index 5dc89a63..a951ebd6 100644 --- a/src/location/location.pro +++ b/src/location/location.pro @@ -10,6 +10,7 @@ CONFIG += simd optimize_full # 3rdparty headers produce warnings with MSVC msvc: CONFIG -= warning_clean +INCLUDEPATH += ../3rdparty/earcut INCLUDEPATH += ../3rdparty/poly2tri INCLUDEPATH += ../3rdparty/clipper INCLUDEPATH += ../3rdparty/clip2tri diff --git a/src/location/maps/qgeomappingmanager.cpp b/src/location/maps/qgeomappingmanager.cpp index cf040beb..d73050f7 100644 --- a/src/location/maps/qgeomappingmanager.cpp +++ b/src/location/maps/qgeomappingmanager.cpp @@ -133,10 +133,8 @@ int QGeoMappingManager::managerVersion() const */ QGeoMap *QGeoMappingManager::createMap(QObject *parent) { - QGeoMap * map = d_ptr->engine->createMap(); - if (map) - connect(parent, &QObject::destroyed,map, &QGeoMap::deleteLater); - return map; + Q_UNUSED(parent) + return d_ptr->engine->createMap(); } QList<QGeoMapType> QGeoMappingManager::supportedMapTypes() const diff --git a/src/location/maps/qgeoprojection.cpp b/src/location/maps/qgeoprojection.cpp index 7fefba27..218d806b 100644 --- a/src/location/maps/qgeoprojection.cpp +++ b/src/location/maps/qgeoprojection.cpp @@ -212,14 +212,25 @@ QDoubleVector2D QGeoProjectionWebMercator::wrappedMapProjectionToItemPosition(co QDoubleVector2D QGeoProjectionWebMercator::itemPositionToWrappedMapProjection(const QDoubleVector2D &itemPosition) const { QDoubleVector2D pos = itemPosition; - // when the camera is tilted, picking a point above the horizon returns a coordinate behind the camera - if (pos.y() < m_minimumUnprojectableY) - pos.setY(m_minimumUnprojectableY); pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight); pos *= 2.0; pos -= QDoubleVector2D(1.0,1.0); - return viewportToWrappedMapProjection(pos); + double s; + QDoubleVector2D res = viewportToWrappedMapProjection(pos, s); + + // a positive s means a point behind the camera. So do it again, after clamping Y. See QTBUG-61813 + if (s > 0.0) { + pos = itemPosition; + // when the camera is tilted, picking a point above the horizon returns a coordinate behind the camera + pos.setY(m_minimumUnprojectableY); + pos *= QDoubleVector2D(m_1_viewportWidth, m_1_viewportHeight); + pos *= 2.0; + pos -= QDoubleVector2D(1.0,1.0); + res = viewportToWrappedMapProjection(pos, s); + } + + return res; } /* Default implementations */ @@ -317,10 +328,23 @@ QList<QDoubleVector2D> QGeoProjectionWebMercator::visibleRegion() const return m_visibleRegion; } +QList<QDoubleVector2D> QGeoProjectionWebMercator::projectableRegion() const +{ + if (m_visibleRegionDirty) + const_cast<QGeoProjectionWebMercator *>(this)->updateVisibleRegion(); + return m_projectableRegion; +} + +QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const +{ + double s; + return viewportToWrappedMapProjection(itemPosition, s); +} + /* actual implementation of itemPositionToWrappedMapProjection */ -QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const +QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition, double &s) const { QDoubleVector2D pos = itemPosition; pos *= QDoubleVector2D(m_halfWidth, m_halfHeight); @@ -332,7 +356,7 @@ QDoubleVector2D QGeoProjectionWebMercator::viewportToWrappedMapProjection(const QDoubleVector3D ray = p - m_eye; ray.normalize(); - return (xyPlane.lineIntersection(m_eye, ray) / m_sideLength).toVector2D(); + return (xyPlane.lineIntersection(m_eye, ray, s) / m_sideLength).toVector2D(); } void QGeoProjectionWebMercator::setupCamera() @@ -344,6 +368,8 @@ void QGeoProjectionWebMercator::setupCamera() int intZoomLevel = static_cast<int>(std::floor(m_cameraData.zoomLevel())); m_sideLength = (1 << intZoomLevel) * defaultTileSize; m_center = m_centerMercator * m_sideLength; + //aperture(90 / 2) = 1 + m_aperture = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5); double f = m_viewportHeight; double z = std::pow(2.0, m_cameraData.zoomLevel() - intZoomLevel) * defaultTileSize; @@ -352,15 +378,13 @@ void QGeoProjectionWebMercator::setupCamera() double z_mercator = std::pow(2.0, m_cameraData.zoomLevel()) * defaultTileSize; double altitude_mercator = f / (2.0 * z_mercator); - //aperture(90 / 2) = 1 - m_aperture = tan(QLocationUtils::radians(m_cameraData.fieldOfView()) * 0.5); // calculate eye m_eye = m_center; m_eye.setZ(altitude * defaultTileSize / m_aperture); // And in mercator space m_eyeMercator = m_centerMercator; - m_eyeMercator.setZ(altitude_mercator); + m_eyeMercator.setZ(altitude_mercator / m_aperture); m_view = m_eye - m_center; QDoubleVector3D side = QDoubleVector3D::normal(m_view, QDoubleVector3D(0.0, 1.0, 0.0)); @@ -396,7 +420,7 @@ void QGeoProjectionWebMercator::setupCamera() m_eyeMercator = mTiltMercator * m_viewMercator + m_centerMercator; } - m_view = m_eye - m_center; + m_view = m_eye - m_center; // ToDo: this should be inverted (center - eye), and the rest should follow m_viewNormalized = m_view.normalized(); m_up = QDoubleVector3D::normal(m_view, m_side); @@ -449,7 +473,7 @@ void QGeoProjectionWebMercator::setupCamera() m_transformation.scale(m_sideLength, m_sideLength, 1.0); m_centerNearPlane = m_eye + m_viewNormalized; - m_centerNearPlaneMercator = m_eyeMercator + m_viewNormalized * m_nearPlaneMercator; + m_centerNearPlaneMercator = m_eyeMercator - m_viewNormalized * m_nearPlaneMercator; // The method does not support tilting angles >= 90.0 or < 0. @@ -478,11 +502,17 @@ void QGeoProjectionWebMercator::updateVisibleRegion() QDoubleVector2D bl = viewportToWrappedMapProjection(QDoubleVector2D(-1, 1 )); QDoubleVector2D br = viewportToWrappedMapProjection(QDoubleVector2D( 1, 1 )); + // To make sure that what is returned can be safely converted back to lat/lon without risking overlaps + double mapLeftLongitude = QLocationUtils::mapLeftLongitude(m_cameraData.center().longitude()); + double mapRightLongitude = QLocationUtils::mapRightLongitude(m_cameraData.center().longitude()); + double leftX = geoToWrappedMapProjection(QGeoCoordinate(0, mapLeftLongitude)).x(); + double rightX = geoToWrappedMapProjection(QGeoCoordinate(0, mapRightLongitude)).x(); + QList<QDoubleVector2D> mapRect; - mapRect.push_back(QDoubleVector2D(-1.0, 1.0)); - mapRect.push_back(QDoubleVector2D( 2.0, 1.0)); - mapRect.push_back(QDoubleVector2D( 2.0, 0.0)); - mapRect.push_back(QDoubleVector2D(-1.0, 0.0)); + mapRect.push_back(QDoubleVector2D(leftX, 1.0)); + mapRect.push_back(QDoubleVector2D(rightX, 1.0)); + mapRect.push_back(QDoubleVector2D(rightX, 0.0)); + mapRect.push_back(QDoubleVector2D(leftX, 0.0)); QList<QDoubleVector2D> viewportRect; viewportRect.push_back(bl); @@ -499,6 +529,52 @@ void QGeoProjectionWebMercator::updateVisibleRegion() m_visibleRegion.clear(); if (res.size()) m_visibleRegion = QClipperUtils::pathToQList(res[0]); // Intersection between two convex quadrilaterals should always be a single polygon + + m_projectableRegion.clear(); + mapRect.clear(); + // The full map rectangle in extended mercator space + mapRect.push_back(QDoubleVector2D(-1.0, 1.0)); + mapRect.push_back(QDoubleVector2D( 2.0, 1.0)); + mapRect.push_back(QDoubleVector2D( 2.0, 0.0)); + mapRect.push_back(QDoubleVector2D(-1.0, 0.0)); + if (m_cameraData.tilt() == 0) { + m_projectableRegion = mapRect; + } else { + QGeoProjectionWebMercator::Plane nearPlane(m_centerNearPlaneMercator, m_viewNormalized); + Line2D nearPlaneXYIntersection = nearPlane.planeXYIntersection(); + double squareHalfSide = qMax(5.0, nearPlaneXYIntersection.m_point.length()); + QDoubleVector2D viewDirectionProjected = -m_viewNormalized.toVector2D().normalized(); + + + QDoubleVector2D tl = nearPlaneXYIntersection.m_point + - squareHalfSide * nearPlaneXYIntersection.m_direction + + 2 * squareHalfSide * viewDirectionProjected; + QDoubleVector2D tr = nearPlaneXYIntersection.m_point + + squareHalfSide * nearPlaneXYIntersection.m_direction + + 2 * squareHalfSide * viewDirectionProjected; + QDoubleVector2D bl = nearPlaneXYIntersection.m_point + - squareHalfSide * nearPlaneXYIntersection.m_direction; + QDoubleVector2D br = nearPlaneXYIntersection.m_point + + squareHalfSide * nearPlaneXYIntersection.m_direction; + + QList<QDoubleVector2D> projectableRect; + projectableRect.push_back(bl); + projectableRect.push_back(br); + projectableRect.push_back(tr); + projectableRect.push_back(tl); + + + c2t::clip2tri clipperProjectable; + clipperProjectable.clearClipper(); + clipperProjectable.addSubjectPath(QClipperUtils::qListToPath(mapRect), true); + clipperProjectable.addClipPolygon(QClipperUtils::qListToPath(projectableRect)); + + Paths resProjectable = clipperProjectable.execute(c2t::clip2tri::Intersection); + if (resProjectable.size()) + m_projectableRegion = QClipperUtils::pathToQList(resProjectable[0]); // Intersection between two convex quadrilaterals should always be a single polygon + else + m_projectableRegion = viewportRect; + } } QGeoCameraData QGeoProjectionWebMercator::cameraData() const @@ -544,9 +620,15 @@ QGeoProjectionWebMercator::Plane::Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const { + double s; + return lineIntersection(linePoint, lineDirection, s); +} + +QDoubleVector3D QGeoProjectionWebMercator::Plane::lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection, double &s) const +{ QDoubleVector3D w = linePoint - m_point; // s = -n.dot(w) / n.dot(u). p = p0 + su; u is lineDirection - double s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection); + s = QDoubleVector3D::dotProduct(-m_normal, w) / QDoubleVector3D::dotProduct(m_normal, lineDirection); return linePoint + lineDirection * s; } diff --git a/src/location/maps/qgeoprojection_p.h b/src/location/maps/qgeoprojection_p.h index d3be1177..ca81df3a 100644 --- a/src/location/maps/qgeoprojection_p.h +++ b/src/location/maps/qgeoprojection_p.h @@ -73,6 +73,7 @@ public: virtual bool isProjectable(const QDoubleVector2D &wrappedProjection) const = 0; virtual QList<QDoubleVector2D> visibleRegion() const = 0; + virtual QList<QDoubleVector2D> projectableRegion() const = 0; // Conversion methods for QGeoCoordinate <-> screen. // This currently assumes that the "MapProjection" space is [0, 1][0, 1] for every type of possibly supported map projection @@ -132,9 +133,9 @@ public: bool isProjectable(const QDoubleVector2D &wrappedProjection) const Q_DECL_OVERRIDE; QList<QDoubleVector2D> visibleRegion() const Q_DECL_OVERRIDE; - + QList<QDoubleVector2D> projectableRegion() const Q_DECL_OVERRIDE; inline QDoubleVector2D viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition) const; - + inline QDoubleVector2D viewportToWrappedMapProjection(const QDoubleVector2D &itemPosition, double &s) const; private: void setupCamera(); void updateVisibleRegion(); @@ -158,6 +159,7 @@ public: Plane(const QDoubleVector3D &planePoint, const QDoubleVector3D &planeNormal); QDoubleVector3D lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection) const; + inline QDoubleVector3D lineIntersection(const QDoubleVector3D &linePoint, const QDoubleVector3D &lineDirection, double &s) const; Line2D planeXYIntersection() const; bool isValid() const; @@ -208,6 +210,7 @@ private: Line2D m_nearPlaneMapIntersection; QList<QDoubleVector2D> m_visibleRegion; + QList<QDoubleVector2D> m_projectableRegion; bool m_visibleRegionDirty; Q_DISABLE_COPY(QGeoProjectionWebMercator) diff --git a/src/location/maps/qgeoroute.cpp b/src/location/maps/qgeoroute.cpp index 52fa4a5e..facf1949 100644 --- a/src/location/maps/qgeoroute.cpp +++ b/src/location/maps/qgeoroute.cpp @@ -104,7 +104,7 @@ QGeoRoute &QGeoRoute::operator= (const QGeoRoute & other) */ bool QGeoRoute::operator ==(const QGeoRoute &other) const { - return (d_ptr.constData() == other.d_ptr.constData()); + return (*d_ptr.constData() == *other.d_ptr.constData()); } /*! @@ -112,7 +112,7 @@ bool QGeoRoute::operator ==(const QGeoRoute &other) const */ bool QGeoRoute::operator !=(const QGeoRoute &other) const { - return (d_ptr.constData() != other.d_ptr.constData()); + return !(*d_ptr.constData() == *other.d_ptr.constData()); } /*! diff --git a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp index e736f6b7..95e5d44b 100644 --- a/src/plugins/geoservices/osm/qgeotileproviderosm.cpp +++ b/src/plugins/geoservices/osm/qgeotileproviderosm.cpp @@ -173,6 +173,8 @@ void QGeoTileProviderOsm::disableRedirection() for (TileProvider *p: m_providerList) { if (p->isValid() && !found) { m_provider = p; + m_providerId = m_providerList.indexOf(p); + m_status = Resolved; found = true; } p->disconnect(this); diff --git a/src/positioning/qlocationutils_p.h b/src/positioning/qlocationutils_p.h index 75c4b7f4..69cf0fee 100644 --- a/src/positioning/qlocationutils_p.h +++ b/src/positioning/qlocationutils_p.h @@ -55,7 +55,7 @@ #include <qmath.h> #include <QtPositioning/QGeoCoordinate> -static const double offsetEpsilon = 0.0000000000001; +static const double offsetEpsilon = 1e-12; // = 0.000000000001 static const double leftOffset = -180.0 + offsetEpsilon; static const double rightOffset = 180.0 - offsetEpsilon; diff --git a/src/src.pro b/src/src.pro index d0a1ee4e..0c0bf50b 100644 --- a/src/src.pro +++ b/src/src.pro @@ -7,8 +7,6 @@ SUBDIRS += positioning positioning.depends = 3rdparty/clip2tri qtHaveModule(quick) { - plugins.depends += positioning - SUBDIRS += location location.depends += positioning 3rdparty/clip2tri @@ -18,6 +16,7 @@ qtHaveModule(quick) { imports.depends += positioning location } +plugins.depends += positioning SUBDIRS += plugins !android:contains(QT_CONFIG, private_tests) { diff --git a/tests/auto/declarative_ui/tst_map.qml b/tests/auto/declarative_ui/tst_map.qml index 755da268..a6c10c22 100644 --- a/tests/auto/declarative_ui/tst_map.qml +++ b/tests/auto/declarative_ui/tst_map.qml @@ -30,6 +30,7 @@ import QtQuick 2.0 import QtTest 1.0 import QtPositioning 5.5 import QtLocation 5.10 +import QtLocation.Test 5.6 Item { width:100 @@ -55,6 +56,10 @@ Item { property variant coordinate3: QtPositioning.coordinate(50, 50, 0) property variant coordinate4: QtPositioning.coordinate(80, 80, 0) property variant coordinate5: QtPositioning.coordinate(20, 180) + property variant coordinateCenterVisibleRegion: QtPositioning.coordinate(27, 77) + property variant coordinateVisible1: QtPositioning.coordinate(28, 77) + property variant coordinateVisible2: QtPositioning.coordinate(33, 79.1) + property variant coordinateVisible3: QtPositioning.coordinate(27, 80.5) property variant invalidCoordinate: QtPositioning.coordinate() property variant altitudelessCoordinate: QtPositioning.coordinate(50, 50) property bool allMapsReady: mapZoomOnCompleted.mapReady @@ -84,6 +89,9 @@ Item { } } + Map { id: mapVisibleRegion; width: 800; height: 600; + center: coordinateCenterVisibleRegion; plugin: testPlugin; zoomLevel: 1.0 } + Map {id: map; plugin: testPlugin; center: coordinate1; width: 100; height: 100} SignalSpy {id: mapCenterSpy; target: map; signalName: 'centerChanged'} @@ -148,6 +156,39 @@ Item { compare(map.center.latitude, 12) } + function test_map_visible_region() + { + mapVisibleRegion.zoomLevel = 1.0 + wait(50) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible1)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible2)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible3)) + + mapVisibleRegion.zoomLevel = 1.88 + verify(LocationTestHelper.waitForPolished(mapVisibleRegion)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible1)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible2)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible3)) + + mapVisibleRegion.zoomLevel = 2.12 + verify(LocationTestHelper.waitForPolished(mapVisibleRegion)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible1)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible2)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible3)) + + mapVisibleRegion.zoomLevel = 2.5 + verify(LocationTestHelper.waitForPolished(mapVisibleRegion)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible1)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible2)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible3)) + + mapVisibleRegion.zoomLevel = 2.7 + verify(LocationTestHelper.waitForPolished(mapVisibleRegion)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible1)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible2)) + verify(mapVisibleRegion.visibleRegion.contains(coordinateVisible3)) + } + function test_map_parameters() { // coordinate is set at map element declaration diff --git a/tests/auto/declarative_ui/tst_map_flick.qml b/tests/auto/declarative_ui/tst_map_flick.qml index 84ffa429..c153411f 100644 --- a/tests/auto/declarative_ui/tst_map_flick.qml +++ b/tests/auto/declarative_ui/tst_map_flick.qml @@ -105,7 +105,7 @@ Item { map.gesture.enabled = true map.gesture.panEnabled = true map.gesture.flickDeceleration = 500 - map.zoomLevel = 0 + map.zoomLevel = 9 // or flicking diagonally won't work map.disableOnPanStartedWithNoGesture = false map.disableOnFlickStartedWithNoGesture = false map.disableOnPanStartedWithDisabled = false @@ -238,12 +238,12 @@ Item { { map.center.latitude = 50 map.center.longitude = 50 - mousePress(page, 2, 2) - var pos; - for (var i = 0; i < 50; i += 5) { + var pos = 5 + mousePress(page, pos, pos) + for (var i = pos; i < 50; i += 5) { + pos = i wait(20) - mouseMove(page, i, i, 0, Qt.LeftButton); - pos = i; + mouseMove(page, pos, pos, 0, Qt.LeftButton); } mouseRelease(page, pos, pos) verify(map.center.latitude > 50) |