1 #include "qmapboxgl.hpp"
2 #include "qmapboxgl_p.hpp"
3 
4 #include "qmapboxgl_map_observer.hpp"
5 #include "qmapboxgl_renderer_observer.hpp"
6 
7 #include "qt_conversion.hpp"
8 #include "qt_geojson.hpp"
9 
10 #include <mbgl/annotation/annotation.hpp>
11 #include <mbgl/map/camera.hpp>
12 #include <mbgl/map/map.hpp>
13 #include <mbgl/math/log2.hpp>
14 #include <mbgl/math/minmax.hpp>
15 #include <mbgl/style/style.hpp>
16 #include <mbgl/style/conversion.hpp>
17 #include <mbgl/style/conversion/layer.hpp>
18 #include <mbgl/style/conversion/source.hpp>
19 #include <mbgl/style/conversion/filter.hpp>
20 #include <mbgl/style/conversion/geojson.hpp>
21 #include <mbgl/style/filter.hpp>
22 #include <mbgl/style/layers/custom_layer.hpp>
23 #include <mbgl/style/layers/background_layer.hpp>
24 #include <mbgl/style/layers/circle_layer.hpp>
25 #include <mbgl/style/layers/fill_layer.hpp>
26 #include <mbgl/style/layers/fill_extrusion_layer.hpp>
27 #include <mbgl/style/layers/line_layer.hpp>
28 #include <mbgl/style/layers/raster_layer.hpp>
29 #include <mbgl/style/layers/symbol_layer.hpp>
30 #include <mbgl/style/sources/geojson_source.hpp>
31 #include <mbgl/style/sources/image_source.hpp>
32 #include <mbgl/style/transition_options.hpp>
33 #include <mbgl/style/image.hpp>
34 #include <mbgl/renderer/renderer.hpp>
35 #include <mbgl/renderer/backend_scope.hpp>
36 #include <mbgl/storage/network_status.hpp>
37 #include <mbgl/util/color.hpp>
38 #include <mbgl/util/constants.hpp>
39 #include <mbgl/util/geo.hpp>
40 #include <mbgl/util/geometry.hpp>
41 #include <mbgl/util/projection.hpp>
42 #include <mbgl/util/run_loop.hpp>
43 #include <mbgl/util/shared_thread_pool.hpp>
44 #include <mbgl/util/traits.hpp>
45 #include <mbgl/actor/scheduler.hpp>
46 
47 #if QT_VERSION >= 0x050000
48 #include <QGuiApplication>
49 #else
50 #include <QCoreApplication>
51 #endif
52 
53 #include <QDebug>
54 #include <QImage>
55 #include <QMargins>
56 #include <QString>
57 #include <QStringList>
58 #include <QThreadStorage>
59 #include <QVariant>
60 #include <QVariantList>
61 #include <QVariantMap>
62 #include <QColor>
63 
64 #include <memory>
65 
66 using namespace QMapbox;
67 
68 // mbgl::GLContextMode
69 static_assert(mbgl::underlying_type(QMapboxGLSettings::UniqueGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Unique), "error");
70 static_assert(mbgl::underlying_type(QMapboxGLSettings::SharedGLContext) == mbgl::underlying_type(mbgl::GLContextMode::Shared), "error");
71 
72 // mbgl::MapMode
73 static_assert(mbgl::underlying_type(QMapboxGLSettings::Continuous) == mbgl::underlying_type(mbgl::MapMode::Continuous), "error");
74 static_assert(mbgl::underlying_type(QMapboxGLSettings::Static) == mbgl::underlying_type(mbgl::MapMode::Static), "error");
75 
76 // mbgl::ConstrainMode
77 static_assert(mbgl::underlying_type(QMapboxGLSettings::NoConstrain) == mbgl::underlying_type(mbgl::ConstrainMode::None), "error");
78 static_assert(mbgl::underlying_type(QMapboxGLSettings::ConstrainHeightOnly) == mbgl::underlying_type(mbgl::ConstrainMode::HeightOnly), "error");
79 static_assert(mbgl::underlying_type(QMapboxGLSettings::ConstrainWidthAndHeight) == mbgl::underlying_type(mbgl::ConstrainMode::WidthAndHeight), "error");
80 
81 // mbgl::ViewportMode
82 static_assert(mbgl::underlying_type(QMapboxGLSettings::DefaultViewport) == mbgl::underlying_type(mbgl::ViewportMode::Default), "error");
83 static_assert(mbgl::underlying_type(QMapboxGLSettings::FlippedYViewport) == mbgl::underlying_type(mbgl::ViewportMode::FlippedY), "error");
84 
85 // mbgl::NorthOrientation
86 static_assert(mbgl::underlying_type(QMapboxGL::NorthUpwards) == mbgl::underlying_type(mbgl::NorthOrientation::Upwards), "error");
87 static_assert(mbgl::underlying_type(QMapboxGL::NorthRightwards) == mbgl::underlying_type(mbgl::NorthOrientation::Rightwards), "error");
88 static_assert(mbgl::underlying_type(QMapboxGL::NorthDownwards) == mbgl::underlying_type(mbgl::NorthOrientation::Downwards), "error");
89 static_assert(mbgl::underlying_type(QMapboxGL::NorthLeftwards) == mbgl::underlying_type(mbgl::NorthOrientation::Leftwards), "error");
90 
91 namespace {
92 
93 QThreadStorage<std::shared_ptr<mbgl::util::RunLoop>> loop;
94 
sharedDefaultFileSource(const std::string & cachePath,const std::string & assetRoot,uint64_t maximumCacheSize)95 std::shared_ptr<mbgl::DefaultFileSource> sharedDefaultFileSource(
96         const std::string& cachePath, const std::string& assetRoot, uint64_t maximumCacheSize) {
97     static std::mutex mutex;
98     static std::unordered_map<std::string, std::weak_ptr<mbgl::DefaultFileSource>> fileSources;
99 
100     std::lock_guard<std::mutex> lock(mutex);
101 
102     // Purge entries no longer in use.
103     for (auto it = fileSources.begin(); it != fileSources.end();) {
104         if (!it->second.lock()) {
105             it = fileSources.erase(it);
106         } else {
107             ++it;
108         }
109     }
110 
111     // Return an existing FileSource if available.
112     auto sharedFileSource = fileSources.find(cachePath);
113     if (sharedFileSource != fileSources.end()) {
114         return sharedFileSource->second.lock();
115     }
116 
117     // New path, create a new FileSource.
118     auto newFileSource = std::make_shared<mbgl::DefaultFileSource>(
119         cachePath, assetRoot, maximumCacheSize);
120 
121     fileSources[cachePath] = newFileSource;
122 
123     return newFileSource;
124 }
125 
126 // Conversion helper functions.
127 
sanitizedSize(const QSize & size)128 mbgl::Size sanitizedSize(const QSize& size) {
129     return mbgl::Size {
130         mbgl::util::max(0u, static_cast<uint32_t>(size.width())),
131         mbgl::util::max(0u, static_cast<uint32_t>(size.height())),
132     };
133 };
134 
toStyleImage(const QString & id,const QImage & sprite)135 std::unique_ptr<mbgl::style::Image> toStyleImage(const QString &id, const QImage &sprite) {
136     const QImage swapped = sprite
137         .rgbSwapped()
138         .convertToFormat(QImage::Format_ARGB32_Premultiplied);
139 
140 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
141     auto img = std::make_unique<uint8_t[]>(swapped.sizeInBytes());
142     memcpy(img.get(), swapped.constBits(), swapped.sizeInBytes());
143 #else
144     auto img = std::make_unique<uint8_t[]>(swapped.byteCount());
145     memcpy(img.get(), swapped.constBits(), swapped.byteCount());
146 #endif
147 
148     return std::make_unique<mbgl::style::Image>(
149         id.toStdString(),
150         mbgl::PremultipliedImage(
151             { static_cast<uint32_t>(swapped.width()), static_cast<uint32_t>(swapped.height()) },
152             std::move(img)),
153         1.0);
154 }
155 
156 } // namespace
157 
158 /*!
159     \class QMapboxGLSettings
160     \brief The QMapboxGLSettings class stores the initial configuration for QMapboxGL.
161 
162     \inmodule Mapbox Maps SDK for Qt
163 
164     QMapboxGLSettings is used to configure QMapboxGL at the moment of its creation.
165     Once created, the QMapboxGLSettings of a QMapboxGL can no longer be changed.
166 
167     Cache-related settings are shared between all QMapboxGL instances using the same cache path.
168     The first map to configure cache properties such as size will force the configuration
169     to all newly instantiated QMapboxGL objects using the same cache in the same process.
170 
171     \since 4.7
172 */
173 
174 /*!
175     \enum QMapboxGLSettings::GLContextMode
176 
177     This enum sets the expectations for the OpenGL state.
178 
179     \value UniqueGLContext  The OpenGL context is only used by QMapboxGL, so it is not
180     reset before each rendering. Use this mode if the intention is to only draw a
181     fullscreen map.
182 
183     \value SharedGLContext  The OpenGL context is shared and the state will be
184     marked dirty - which invalidates any previously assumed GL state. The
185     embedder is responsible for clearing up the viewport prior to calling
186     QMapboxGL::render. The embedder is also responsible for resetting its own
187     GL state after QMapboxGL::render has finished, if needed.
188 
189     \sa contextMode()
190 */
191 
192 /*!
193     \enum QMapboxGLSettings::MapMode
194 
195     This enum sets the map rendering mode
196 
197     \value Continuous  The map will render as data arrives from the network and
198     react immediately to state changes.
199 
200     This is the default mode and the preferred when the map is intended to be
201     interactive.
202 
203     \value Static  The map will no longer react to state changes and will only
204     be rendered when QMapboxGL::startStaticRender is called. After all the
205     resources are loaded, the QMapboxGL::staticRenderFinished signal is emitted.
206 
207     This mode is useful for taking a snapshot of the finished rendering result
208     of the map into a QImage.
209 
210     \sa mapMode()
211 */
212 
213 /*!
214     \enum QMapboxGLSettings::ConstrainMode
215 
216     This enum determines if the map wraps.
217 
218     \value NoConstrain              The map will wrap on the horizontal axis. Since it doesn't
219     make sense to wrap on the vertical axis in a Web Mercator projection, the map will scroll
220     and show some empty space.
221 
222     \value ConstrainHeightOnly      The map will wrap around the horizontal axis, like a spinning
223     globe. This is the recommended constrain mode.
224 
225     \value ConstrainWidthAndHeight  The map won't wrap and panning is restricted to the boundaries
226     of the map.
227 
228     \sa constrainMode()
229 */
230 
231 /*!
232     \enum QMapboxGLSettings::ViewportMode
233 
234     This enum flips the map vertically.
235 
236     \value DefaultViewport  Native orientation.
237 
238     \value FlippedYViewport  Mirrored vertically.
239 
240     \sa viewportMode()
241 */
242 
243 /*!
244     Constructs a QMapboxGLSettings object with the default values. The default
245     configuration is valid for initializing a QMapboxGL.
246 */
QMapboxGLSettings()247 QMapboxGLSettings::QMapboxGLSettings()
248     : m_contextMode(QMapboxGLSettings::SharedGLContext)
249     , m_mapMode(QMapboxGLSettings::Continuous)
250     , m_constrainMode(QMapboxGLSettings::ConstrainHeightOnly)
251     , m_viewportMode(QMapboxGLSettings::DefaultViewport)
252     , m_cacheMaximumSize(mbgl::util::DEFAULT_MAX_CACHE_SIZE)
253     , m_cacheDatabasePath(":memory:")
254     , m_assetPath(QCoreApplication::applicationDirPath())
255     , m_accessToken(qgetenv("MAPBOX_ACCESS_TOKEN"))
256     , m_apiBaseUrl(mbgl::util::API_BASE_URL)
257 {
258 }
259 
260 /*!
261     Returns the OpenGL context mode. This is specially important when mixing
262     with other OpenGL draw calls.
263 
264     By default, it is set to QMapboxGLSettings::SharedGLContext.
265 */
contextMode() const266 QMapboxGLSettings::GLContextMode QMapboxGLSettings::contextMode() const
267 {
268     return m_contextMode;
269 }
270 
271 /*!
272     Sets the OpenGL context \a mode.
273 */
setContextMode(GLContextMode mode)274 void QMapboxGLSettings::setContextMode(GLContextMode mode)
275 {
276     m_contextMode = mode;
277 }
278 
279 /*!
280     Returns the map mode. Static mode will emit a signal for
281     rendering a map only when the map is fully loaded.
282     Animations like style transitions and labels fading won't
283     be seen.
284 
285     The Continuous mode will emit the signal for every new
286     change on the map and it is usually what you expect for
287     a interactive map.
288 
289     By default, it is set to QMapboxGLSettings::Continuous.
290 */
mapMode() const291 QMapboxGLSettings::MapMode QMapboxGLSettings::mapMode() const
292 {
293     return m_mapMode;
294 }
295 
296 /*!
297     Sets the map \a mode.
298 */
setMapMode(MapMode mode)299 void QMapboxGLSettings::setMapMode(MapMode mode)
300 {
301     m_mapMode = mode;
302 }
303 
304 /*!
305     Returns the constrain mode. This is used to limit the map to wrap
306     around the globe horizontally.
307 
308     By default, it is set to QMapboxGLSettings::ConstrainHeightOnly.
309 */
constrainMode() const310 QMapboxGLSettings::ConstrainMode QMapboxGLSettings::constrainMode() const
311 {
312     return m_constrainMode;
313 }
314 
315 /*!
316     Sets the map constrain \a mode.
317 */
setConstrainMode(ConstrainMode mode)318 void QMapboxGLSettings::setConstrainMode(ConstrainMode mode)
319 {
320     m_constrainMode = mode;
321 }
322 
323 /*!
324     Returns the viewport mode. This is used to flip the vertical
325     orientation of the map as some devices may use inverted orientation.
326 
327     By default, it is set to QMapboxGLSettings::DefaultViewport.
328 */
viewportMode() const329 QMapboxGLSettings::ViewportMode QMapboxGLSettings::viewportMode() const
330 {
331     return m_viewportMode;
332 }
333 
334 /*!
335     Sets the viewport \a mode.
336 */
setViewportMode(ViewportMode mode)337 void QMapboxGLSettings::setViewportMode(ViewportMode mode)
338 {
339     m_viewportMode = mode;
340 }
341 
342 /*!
343     Returns the cache database maximum hard size in bytes. The database
344     will grow until the limit is reached. Setting a maximum size smaller
345     than the current size of an existing database results in undefined
346     behavior
347 
348     By default, it is set to 50 MB.
349 */
cacheDatabaseMaximumSize() const350 unsigned QMapboxGLSettings::cacheDatabaseMaximumSize() const
351 {
352     return m_cacheMaximumSize;
353 }
354 
355 /*!
356     Returns the maximum allowed cache database \a size in bytes.
357 */
setCacheDatabaseMaximumSize(unsigned size)358 void QMapboxGLSettings::setCacheDatabaseMaximumSize(unsigned size)
359 {
360     m_cacheMaximumSize = size;
361 }
362 
363 /*!
364     Returns the cache database path. The cache is used for storing
365     recently used resources like tiles and also an offline tile database
366     pre-populated by the
367     \l {https://github.com/mapbox/mapbox-gl-native/blob/master/bin/offline.sh}
368     {Offline Tool}.
369 
370     By default, it is set to \c :memory: meaning it will create an in-memory
371     cache instead of a file on disk.
372 */
cacheDatabasePath() const373 QString QMapboxGLSettings::cacheDatabasePath() const
374 {
375     return m_cacheDatabasePath;
376 }
377 
378 /*!
379     Sets the cache database \a path.
380 
381     Setting the \a path to \c :memory: will create an in-memory cache.
382 */
setCacheDatabasePath(const QString & path)383 void QMapboxGLSettings::setCacheDatabasePath(const QString &path)
384 {
385     m_cacheDatabasePath = path;
386 }
387 
388 /*!
389     Returns the asset path, which is the root directory from where
390     the \c asset:// scheme gets resolved in a style. \c asset:// can be used
391     for loading a resource from the disk in a style rather than fetching
392     it from the network.
393 
394     By default, it is set to the value returned by QCoreApplication::applicationDirPath().
395 */
assetPath() const396 QString QMapboxGLSettings::assetPath() const
397 {
398     return m_assetPath;
399 }
400 
401 /*!
402     Sets the asset \a path.
403 */
setAssetPath(const QString & path)404 void QMapboxGLSettings::setAssetPath(const QString &path)
405 {
406     m_assetPath = path;
407 }
408 
409 /*!
410     Returns the access token.
411 
412     By default, it is taken from the environment variable \c MAPBOX_ACCESS_TOKEN
413     or empty if the variable is not set.
414 */
accessToken() const415 QString QMapboxGLSettings::accessToken() const {
416     return m_accessToken;
417 }
418 
419 /*!
420     Sets the access \a token.
421 
422     Mapbox-hosted vector tiles and styles require an API
423     \l {https://www.mapbox.com/help/define-access-token/}{access token}, which you
424     can obtain from the \l {https://www.mapbox.com/studio/account/tokens/}
425     {Mapbox account page}. Access tokens associate requests to Mapbox's vector tile
426     and style APIs with your Mapbox account. They also deter other developers from
427     using your styles without your permission.
428 */
setAccessToken(const QString & token)429 void QMapboxGLSettings::setAccessToken(const QString &token)
430 {
431     m_accessToken = token;
432 }
433 
434 /*!
435     Returns the API base URL.
436 */
apiBaseUrl() const437 QString QMapboxGLSettings::apiBaseUrl() const
438 {
439     return m_apiBaseUrl;
440 }
441 
442 /*!
443     Sets the API base \a url.
444 
445     The API base URL is the URL that the \b "mapbox://" protocol will
446     be resolved to. It defaults to "https://api.mapbox.com" but can be
447     changed, for instance, to a tile cache server address.
448 */
setApiBaseUrl(const QString & url)449 void QMapboxGLSettings::setApiBaseUrl(const QString& url)
450 {
451     m_apiBaseUrl = url;
452 }
453 
454 /*!
455     Returns resource transformation callback used to transform requested URLs.
456 */
resourceTransform() const457 std::function<std::string(const std::string &&)> QMapboxGLSettings::resourceTransform() const
458 {
459     return m_resourceTransform;
460 }
461 
462 /*!
463     Sets the resource \a transform callback.
464 
465     When given, resource transformation callback will be used to transform the
466     requested resource URLs before they are requested from internet. This can be
467     used add or remove custom parameters, or reroute certain requests to other
468     servers or endpoints.
469 */
setResourceTransform(const std::function<std::string (const std::string &&)> & transform)470 void QMapboxGLSettings::setResourceTransform(const std::function<std::string(const std::string &&)> &transform)
471 {
472     m_resourceTransform = transform;
473 }
474 
475 /*!
476     \class QMapboxGL
477     \brief The QMapboxGL class is a Qt wrapper for the Mapbox GL Native engine.
478 
479     \inmodule Mapbox Maps SDK for Qt
480 
481     QMapboxGL is a Qt friendly version the Mapbox GL Native engine using Qt types
482     and deep integration with Qt event loop. QMapboxGL relies as much as possible
483     on Qt, trying to minimize the external dependencies. For instance it will use
484     QNetworkAccessManager for HTTP requests and QString for UTF-8 manipulation.
485 
486     QMapboxGL is not thread-safe and it is assumed that it will be accessed from
487     the same thread as the thread where the OpenGL context lives.
488 
489     \since 4.7
490 */
491 
492 /*!
493     \enum QMapboxGL::MapChange
494 
495     This enum represents the last changed occurred to the map state.
496 
497     \value MapChangeRegionWillChange                      A region of the map will change, like
498     when resizing the map.
499 
500     \value MapChangeRegionWillChangeAnimated              Not in use by QMapboxGL.
501 
502     \value MapChangeRegionIsChanging                      A region of the map is changing.
503 
504     \value MapChangeRegionDidChange                       A region of the map finished changing.
505 
506     \value MapChangeRegionDidChangeAnimated               Not in use by QMapboxGL.
507 
508     \value MapChangeWillStartLoadingMap                   The map is getting loaded. This state
509     is set only once right after QMapboxGL is created and a style is set.
510 
511     \value MapChangeDidFinishLoadingMap                   All the resources were loaded and parsed
512     and the map is fully rendered. After this state the mapChanged() signal won't fire again unless
513     the is some client side interaction with the map or a tile expires, causing a new resource
514     to be requested from the network.
515 
516     \value MapChangeDidFailLoadingMap                     An error occurred when loading the map.
517 
518     \value MapChangeWillStartRenderingFrame               Just before rendering the frame. This
519     is the state of the map just after calling render() and might happened many times before
520     the map is fully loaded.
521 
522     \value MapChangeDidFinishRenderingFrame               The current frame was rendered but was
523     left in a partial state. Some parts of the map might be missing because have not arrived
524     from the network or are being parsed.
525 
526     \value MapChangeDidFinishRenderingFrameFullyRendered  The current frame was fully rendered.
527 
528     \value MapChangeWillStartRenderingMap                 Set once when the map is about to get
529     rendered for the first time.
530 
531     \value MapChangeDidFinishRenderingMap                 Not in use by QMapboxGL.
532 
533     \value MapChangeDidFinishRenderingMapFullyRendered    Map is fully loaded and rendered.
534 
535     \value MapChangeDidFinishLoadingStyle                 The style was loaded.
536 
537     \value MapChangeSourceDidChange                       A source has changed.
538 
539     \sa mapChanged()
540 */
541 
542 /*!
543     \enum QMapboxGL::MapLoadingFailure
544 
545     This enum represents map loading failure type.
546 
547     \value StyleParseFailure                             Failure to parse the style.
548     \value StyleLoadFailure                              Failure to load the style data.
549     \value NotFoundFailure                               Failure to obtain style resource file.
550     \value UnknownFailure                                Unknown map loading failure.
551 
552     \sa mapLoadingFailed()
553 */
554 
555 /*!
556     \enum QMapboxGL::NorthOrientation
557 
558     This enum sets the orientation of the north bearing. It will directly affect bearing when
559     resetting the north (i.e. setting bearing to 0).
560 
561     \value NorthUpwards     The north is pointing up in the map. This is usually how maps are oriented.
562 
563     \value NorthRightwards  The north is pointing right.
564 
565     \value NorthDownwards   The north is pointing down.
566 
567     \value NorthLeftwards   The north is pointing left.
568 
569     \sa northOrientation()
570     \sa bearing()
571 */
572 
573 /*!
574     Constructs a QMapboxGL object with \a settings and sets \a parent_ as the parent
575     object. The \a settings cannot be changed after the object is constructed. The
576     \a size represents the size of the viewport and the \a pixelRatio the initial pixel
577     density of the screen.
578 */
QMapboxGL(QObject * parent_,const QMapboxGLSettings & settings,const QSize & size,qreal pixelRatio)579 QMapboxGL::QMapboxGL(QObject *parent_, const QMapboxGLSettings &settings, const QSize& size, qreal pixelRatio)
580     : QObject(parent_)
581 {
582     assert(!size.isEmpty());
583 
584     // Multiple QMapboxGL running on the same thread
585     // will share the same mbgl::util::RunLoop
586     if (!loop.hasLocalData()) {
587         loop.setLocalData(std::make_shared<mbgl::util::RunLoop>());
588     }
589 
590     d_ptr = new QMapboxGLPrivate(this, settings, size, pixelRatio);
591 }
592 
593 /*!
594     Destroys this QMapboxGL.
595 */
~QMapboxGL()596 QMapboxGL::~QMapboxGL()
597 {
598     delete d_ptr;
599 }
600 
601 /*!
602     Cycles through several debug options like showing the tile borders,
603     tile numbers, expiration time and wireframe.
604 */
cycleDebugOptions()605 void QMapboxGL::cycleDebugOptions()
606 {
607     d_ptr->mapObj->cycleDebugOptions();
608 }
609 
610 /*!
611     \property QMapboxGL::styleJson
612     \brief the map style JSON.
613 
614     Sets a new \a style from a JSON that must conform to the
615     \l {https://www.mapbox.com/mapbox-gl-style-spec/}
616     {Mapbox style specification}.
617 
618     \note In case of a invalid style it will trigger a mapChanged
619     signal with QMapboxGL::MapChangeDidFailLoadingMap as argument.
620 */
styleJson() const621 QString QMapboxGL::styleJson() const
622 {
623     return QString::fromStdString(d_ptr->mapObj->getStyle().getJSON());
624 }
625 
setStyleJson(const QString & style)626 void QMapboxGL::setStyleJson(const QString &style)
627 {
628     d_ptr->mapObj->getStyle().loadJSON(style.toStdString());
629 }
630 
631 /*!
632     \property QMapboxGL::styleUrl
633     \brief the map style URL.
634 
635     Sets a URL for fetching a JSON that will be later fed to
636     setStyleJson. URLs using the Mapbox scheme (\a mapbox://) are
637     also accepted and translated automatically to an actual HTTPS
638     request.
639 
640     The Mapbox scheme is not enforced and a style can be fetched
641     from anything that QNetworkAccessManager can handle.
642 
643     \note In case of a invalid style it will trigger a mapChanged
644     signal with QMapboxGL::MapChangeDidFailLoadingMap as argument.
645 */
styleUrl() const646 QString QMapboxGL::styleUrl() const
647 {
648     return QString::fromStdString(d_ptr->mapObj->getStyle().getURL());
649 }
650 
setStyleUrl(const QString & url)651 void QMapboxGL::setStyleUrl(const QString &url)
652 {
653     d_ptr->mapObj->getStyle().loadURL(url.toStdString());
654 }
655 
656 /*!
657     \property QMapboxGL::latitude
658     \brief the map's current latitude in degrees.
659 
660     Setting a latitude doesn't necessarily mean it will be accepted since QMapboxGL
661     might constrain it within the limits of the Web Mercator projection.
662 */
latitude() const663 double QMapboxGL::latitude() const
664 {
665     return d_ptr->mapObj->getLatLng(d_ptr->margins).latitude();
666 }
667 
setLatitude(double latitude_)668 void QMapboxGL::setLatitude(double latitude_)
669 {
670     d_ptr->mapObj->setLatLng(mbgl::LatLng { latitude_, longitude() }, d_ptr->margins);
671 }
672 
673 /*!
674     \property QMapboxGL::longitude
675     \brief the map current longitude in degrees.
676 
677     Setting a longitude beyond the limits of the Web Mercator projection will make
678     the map wrap. As an example, setting the longitude to 360 is effectively the same
679     as setting it to 0.
680 */
longitude() const681 double QMapboxGL::longitude() const
682 {
683     return d_ptr->mapObj->getLatLng(d_ptr->margins).longitude();
684 }
685 
setLongitude(double longitude_)686 void QMapboxGL::setLongitude(double longitude_)
687 {
688     d_ptr->mapObj->setLatLng(mbgl::LatLng { latitude(), longitude_ }, d_ptr->margins);
689 }
690 
691 /*!
692     \property QMapboxGL::scale
693     \brief the map scale factor.
694 
695     This property is used to zoom the map. When \a center is defined, the map will
696     scale in the direction of the center pixel coordinates. The \a center will remain
697     at the same pixel coordinate after scaling as before calling this method.
698 
699     \note This function could be used for implementing a pinch gesture or zooming
700     by using the mouse scroll wheel.
701 
702     \sa zoom()
703 */
scale() const704 double QMapboxGL::scale() const
705 {
706     return std::pow(2.0, d_ptr->mapObj->getZoom());
707 }
708 
setScale(double scale_,const QPointF & center)709 void QMapboxGL::setScale(double scale_, const QPointF &center)
710 {
711     d_ptr->mapObj->setZoom(::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
712 }
713 
714 /*!
715     \property QMapboxGL::zoom
716     \brief the map zoom factor.
717 
718     This property is used to zoom the map. When \a center is defined, the map will
719     zoom in the direction of the center. This function could be used for implementing
720     a pinch gesture or zooming by using the mouse scroll wheel.
721 
722     \sa scale()
723 */
zoom() const724 double QMapboxGL::zoom() const
725 {
726     return d_ptr->mapObj->getZoom();
727 }
728 
setZoom(double zoom_)729 void QMapboxGL::setZoom(double zoom_)
730 {
731     d_ptr->mapObj->setZoom(zoom_, d_ptr->margins);
732 }
733 
734 /*!
735     Returns the minimum zoom level allowed for the map.
736 
737     \sa maximumZoom()
738 */
minimumZoom() const739 double QMapboxGL::minimumZoom() const
740 {
741     return d_ptr->mapObj->getMinZoom();
742 }
743 
744 /*!
745     Returns the maximum zoom level allowed for the map.
746 
747     \sa minimumZoom()
748 */
maximumZoom() const749 double QMapboxGL::maximumZoom() const
750 {
751     return d_ptr->mapObj->getMaxZoom();
752 }
753 
754 /*!
755     \property QMapboxGL::coordinate
756     \brief the map center \a coordinate.
757 
758     Centers the map at a geographic coordinate respecting the margins, if set.
759 
760     \sa margins()
761 */
coordinate() const762 Coordinate QMapboxGL::coordinate() const
763 {
764     const mbgl::LatLng& latLng = d_ptr->mapObj->getLatLng(d_ptr->margins);
765     return Coordinate(latLng.latitude(), latLng.longitude());
766 }
767 
setCoordinate(const QMapbox::Coordinate & coordinate_)768 void QMapboxGL::setCoordinate(const QMapbox::Coordinate &coordinate_)
769 {
770     d_ptr->mapObj->setLatLng(mbgl::LatLng { coordinate_.first, coordinate_.second }, d_ptr->margins);
771 }
772 
773 /*!
774     \fn QMapboxGL::setCoordinateZoom(const QMapbox::Coordinate &coordinate, double zoom)
775 
776     Convenience method for setting the \a coordinate and \a zoom simultaneously.
777 
778     \note Setting \a coordinate and \a zoom at once is more efficient than doing
779     it in two steps.
780 
781     \sa zoom()
782     \sa coordinate()
783 */
setCoordinateZoom(const QMapbox::Coordinate & coordinate_,double zoom_)784 void QMapboxGL::setCoordinateZoom(const QMapbox::Coordinate &coordinate_, double zoom_)
785 {
786     d_ptr->mapObj->setLatLngZoom(
787             mbgl::LatLng { coordinate_.first, coordinate_.second }, zoom_, d_ptr->margins);
788 }
789 
790 /*!
791     Atomically jumps to the \a camera options.
792 */
jumpTo(const QMapboxGLCameraOptions & camera)793 void QMapboxGL::jumpTo(const QMapboxGLCameraOptions& camera)
794 {
795     mbgl::CameraOptions mbglCamera;
796     if (camera.center.isValid()) {
797         const Coordinate center = camera.center.value<Coordinate>();
798         mbglCamera.center = mbgl::LatLng { center.first, center.second };
799     }
800     if (camera.anchor.isValid()) {
801         const QPointF anchor = camera.anchor.value<QPointF>();
802         mbglCamera.anchor = mbgl::ScreenCoordinate { anchor.x(), anchor.y() };
803     }
804     if (camera.zoom.isValid()) {
805         mbglCamera.zoom = camera.zoom.value<double>();
806     }
807     if (camera.angle.isValid()) {
808         mbglCamera.angle = -camera.angle.value<double>() * mbgl::util::DEG2RAD;
809     }
810     if (camera.pitch.isValid()) {
811         mbglCamera.pitch = camera.pitch.value<double>() * mbgl::util::DEG2RAD;
812     }
813 
814     mbglCamera.padding = d_ptr->margins;
815 
816     d_ptr->mapObj->jumpTo(mbglCamera);
817 }
818 
819 /*!
820     \property QMapboxGL::bearing
821     \brief the map bearing in degrees.
822 
823     Set the angle in degrees. Negative values and values over 360 are
824     valid and will wrap. The direction of the rotation is counterclockwise.
825 
826     When \a center is defined, the map will rotate around the center pixel coordinate
827     respecting the margins if defined.
828 
829     \sa margins()
830 */
bearing() const831 double QMapboxGL::bearing() const
832 {
833     return d_ptr->mapObj->getBearing();
834 }
835 
setBearing(double degrees)836 void QMapboxGL::setBearing(double degrees)
837 {
838     d_ptr->mapObj->setBearing(degrees, d_ptr->margins);
839 }
840 
setBearing(double degrees,const QPointF & center)841 void QMapboxGL::setBearing(double degrees, const QPointF &center)
842 {
843     d_ptr->mapObj->setBearing(degrees, mbgl::ScreenCoordinate { center.x(), center.y() });
844 }
845 
846 /*!
847     \property QMapboxGL::pitch
848     \brief the map pitch in degrees.
849 
850     Pitch toward the horizon measured in degrees, with 0 resulting in a
851     two-dimensional map. It will be constrained at 60 degrees.
852 
853     \sa margins()
854 */
pitch() const855 double QMapboxGL::pitch() const
856 {
857     return d_ptr->mapObj->getPitch();
858 }
859 
setPitch(double pitch_)860 void QMapboxGL::setPitch(double pitch_)
861 {
862     d_ptr->mapObj->setPitch(pitch_);
863 }
864 
865 /*!
866     Returns the north orientation mode.
867 */
northOrientation() const868 QMapboxGL::NorthOrientation QMapboxGL::northOrientation() const
869 {
870     return static_cast<QMapboxGL::NorthOrientation>(d_ptr->mapObj->getNorthOrientation());
871 }
872 
873 /*!
874     Sets the north orientation mode to \a orientation.
875 */
setNorthOrientation(NorthOrientation orientation)876 void QMapboxGL::setNorthOrientation(NorthOrientation orientation)
877 {
878     d_ptr->mapObj->setNorthOrientation(static_cast<mbgl::NorthOrientation>(orientation));
879 }
880 
881 /*!
882     Tells the map rendering engine that there is currently a gesture in \a progress. This
883     affects how the map renders labels, as it will use different texture filters if a gesture
884     is ongoing.
885 */
setGestureInProgress(bool progress)886 void QMapboxGL::setGestureInProgress(bool progress)
887 {
888     d_ptr->mapObj->setGestureInProgress(progress);
889 }
890 
891 /*!
892     Sets the \a duration and \a delay of style transitions. Style paint property
893     values transition to new values with animation when they are updated.
894 */
setTransitionOptions(qint64 duration,qint64 delay)895 void QMapboxGL::setTransitionOptions(qint64 duration, qint64 delay) {
896     static auto convert = [](qint64 value) -> mbgl::optional<mbgl::Duration> {
897         return std::chrono::duration_cast<mbgl::Duration>(mbgl::Milliseconds(value));
898     };
899 
900     d_ptr->mapObj->getStyle().setTransitionOptions(mbgl::style::TransitionOptions{ convert(duration), convert(delay) });
901 }
902 
asMapboxGLAnnotation(const QMapbox::Annotation & annotation)903 mbgl::optional<mbgl::Annotation> asMapboxGLAnnotation(const QMapbox::Annotation & annotation) {
904     auto asMapboxGLGeometry = [](const QMapbox::ShapeAnnotationGeometry &geometry) {
905         mbgl::ShapeAnnotationGeometry result;
906         switch (geometry.type) {
907         case QMapbox::ShapeAnnotationGeometry::LineStringType:
908             result = asMapboxGLLineString(geometry.geometry.first().first());
909             break;
910         case QMapbox::ShapeAnnotationGeometry::PolygonType:
911             result = asMapboxGLPolygon(geometry.geometry.first());
912             break;
913         case QMapbox::ShapeAnnotationGeometry::MultiLineStringType:
914             result = asMapboxGLMultiLineString(geometry.geometry.first());
915             break;
916         case QMapbox::ShapeAnnotationGeometry::MultiPolygonType:
917             result = asMapboxGLMultiPolygon(geometry.geometry);
918             break;
919         }
920         return result;
921     };
922 
923     if (annotation.canConvert<QMapbox::SymbolAnnotation>()) {
924         QMapbox::SymbolAnnotation symbolAnnotation = annotation.value<QMapbox::SymbolAnnotation>();
925         QMapbox::Coordinate& pair = symbolAnnotation.geometry;
926         return { mbgl::SymbolAnnotation(mbgl::Point<double> { pair.second, pair.first }, symbolAnnotation.icon.toStdString()) };
927     } else if (annotation.canConvert<QMapbox::LineAnnotation>()) {
928         QMapbox::LineAnnotation lineAnnotation = annotation.value<QMapbox::LineAnnotation>();
929         auto color = mbgl::Color::parse(lineAnnotation.color.name().toStdString());
930         return { mbgl::LineAnnotation(asMapboxGLGeometry(lineAnnotation.geometry), lineAnnotation.opacity, lineAnnotation.width, { *color }) };
931     } else if (annotation.canConvert<QMapbox::FillAnnotation>()) {
932         QMapbox::FillAnnotation fillAnnotation = annotation.value<QMapbox::FillAnnotation>();
933         auto color = mbgl::Color::parse(fillAnnotation.color.name().toStdString());
934         if (fillAnnotation.outlineColor.canConvert<QColor>()) {
935             auto outlineColor = mbgl::Color::parse(fillAnnotation.outlineColor.value<QColor>().name().toStdString());
936             return { mbgl::FillAnnotation(asMapboxGLGeometry(fillAnnotation.geometry), fillAnnotation.opacity, { *color }, { *outlineColor }) };
937         } else {
938             return { mbgl::FillAnnotation(asMapboxGLGeometry(fillAnnotation.geometry), fillAnnotation.opacity, { *color }, {}) };
939         }
940     }
941 
942     qWarning() << "Unable to convert annotation:" << annotation;
943     return {};
944 }
945 
946 /*!
947     Adds an \a annotation to the map.
948 
949     Returns the unique identifier for the new annotation.
950 
951     \sa addAnnotationIcon()
952 */
addAnnotation(const QMapbox::Annotation & annotation)953 QMapbox::AnnotationID QMapboxGL::addAnnotation(const QMapbox::Annotation &annotation)
954 {
955     return d_ptr->mapObj->addAnnotation(*asMapboxGLAnnotation(annotation));
956 }
957 
958 /*!
959     Updates an existing \a annotation referred by \a id.
960 
961     \sa addAnnotationIcon()
962 */
updateAnnotation(QMapbox::AnnotationID id,const QMapbox::Annotation & annotation)963 void QMapboxGL::updateAnnotation(QMapbox::AnnotationID id, const QMapbox::Annotation &annotation)
964 {
965     d_ptr->mapObj->updateAnnotation(id, *asMapboxGLAnnotation(annotation));
966 }
967 
968 /*!
969     Removes an existing annotation referred by \a id.
970 */
removeAnnotation(QMapbox::AnnotationID id)971 void QMapboxGL::removeAnnotation(QMapbox::AnnotationID id)
972 {
973     d_ptr->mapObj->removeAnnotation(id);
974 }
975 
976 /*!
977     Sets a layout \a property_ \a value to an existing \a layer. The \a property_ string can be any
978     as defined by the \l {https://www.mapbox.com/mapbox-gl-style-spec/} {Mapbox style specification}
979     for layout properties.
980 
981     This example hides the layer \c route:
982 
983     \code
984         map->setLayoutProperty("route", "visibility", "none");
985     \endcode
986 
987     This table describes the mapping between \l {https://www.mapbox.com/mapbox-gl-style-spec/#types}
988     {style types} and Qt types accepted by setLayoutProperty():
989 
990     \table
991     \header
992         \li Mapbox style type
993         \li Qt type
994     \row
995         \li Enum
996         \li QString
997     \row
998         \li String
999         \li QString
1000     \row
1001         \li Boolean
1002         \li \c bool
1003     \row
1004         \li Number
1005         \li \c int, \c double or \c float
1006     \row
1007         \li Array
1008         \li QVariantList
1009     \endtable
1010 */
setLayoutProperty(const QString & layer,const QString & property_,const QVariant & value)1011 void QMapboxGL::setLayoutProperty(const QString& layer, const QString& property_, const QVariant& value)
1012 {
1013     using namespace mbgl::style;
1014 
1015     Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString());
1016     if (!layer_) {
1017         qWarning() << "Layer not found:" << layer;
1018         return;
1019     }
1020 
1021     if (conversion::setLayoutProperty(*layer_, property_.toStdString(), value)) {
1022         qWarning() << "Error setting layout property:" << layer << "-" << property_;
1023         return;
1024     }
1025 }
1026 
1027 /*!
1028     Sets a paint \a property_ \a value to an existing \a layer. The \a property_ string can be any
1029     as defined by the \l {https://www.mapbox.com/mapbox-gl-style-spec/} {Mapbox style specification}
1030     for paint properties.
1031 
1032     For paint properties that take a color as \a value, such as \c fill-color, a string such as
1033     \c blue can be passed or a QColor.
1034 
1035     \code
1036         map->setPaintProperty("route", "line-color", QColor("blue"));
1037     \endcode
1038 
1039     This table describes the mapping between \l {https://www.mapbox.com/mapbox-gl-style-spec/#types}
1040     {style types} and Qt types accepted by setPaintProperty():
1041 
1042     \table
1043     \header
1044         \li Mapbox style type
1045         \li Qt type
1046     \row
1047         \li Color
1048         \li QString or QColor
1049     \row
1050         \li Enum
1051         \li QString
1052     \row
1053         \li String
1054         \li QString
1055     \row
1056         \li Boolean
1057         \li \c bool
1058     \row
1059         \li Number
1060         \li \c int, \c double or \c float
1061     \row
1062         \li Array
1063         \li QVariantList
1064     \endtable
1065 
1066     If the style specification defines the property's type as \b Array, use a QVariantList. For
1067     example, the following code sets a \c route layer's \c line-dasharray property:
1068 
1069     \code
1070         QVariantList lineDashArray;
1071         lineDashArray.append(1);
1072         lineDashArray.append(2);
1073 
1074         map->setPaintProperty("route","line-dasharray", lineDashArray);
1075     \endcode
1076 */
setPaintProperty(const QString & layer,const QString & property_,const QVariant & value)1077 void QMapboxGL::setPaintProperty(const QString& layer, const QString& property_, const QVariant& value)
1078 {
1079     using namespace mbgl::style;
1080 
1081     Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString());
1082     if (!layer_) {
1083         qWarning() << "Layer not found:" << layer;
1084         return;
1085     }
1086 
1087     if (conversion::setPaintProperty(*layer_, property_.toStdString(), value)) {
1088         qWarning() << "Error setting paint property:" << layer << "-" << property_;
1089         return;
1090     }
1091 }
1092 
1093 /*!
1094     Returns true when the map is completely rendered, false otherwise. A partially
1095     rendered map ranges from nothing rendered at all to only labels missing.
1096 */
isFullyLoaded() const1097 bool QMapboxGL::isFullyLoaded() const
1098 {
1099     return d_ptr->mapObj->isFullyLoaded();
1100 }
1101 
1102 /*!
1103     Pan the map by \a offset in pixels.
1104 
1105     The pixel coordinate origin is located at the upper left corner of the map.
1106 */
moveBy(const QPointF & offset)1107 void QMapboxGL::moveBy(const QPointF &offset)
1108 {
1109     d_ptr->mapObj->moveBy(mbgl::ScreenCoordinate { offset.x(), offset.y() });
1110 }
1111 
1112 /*!
1113     \fn QMapboxGL::scaleBy(double scale, const QPointF &center)
1114 
1115     Scale the map by \a scale in the direction of the \a center. This function
1116     can be used for implementing a pinch gesture.
1117 */
scaleBy(double scale_,const QPointF & center)1118 void QMapboxGL::scaleBy(double scale_, const QPointF &center) {
1119     d_ptr->mapObj->setZoom(d_ptr->mapObj->getZoom() + ::log2(scale_), mbgl::ScreenCoordinate { center.x(), center.y() });
1120 }
1121 
1122 /*!
1123     Rotate the map from the \a first screen coordinate to the \a second screen coordinate.
1124     This method can be used for implementing rotating the map by clicking and dragging,
1125     being \a first the cursor coordinate at the last frame and \a second the cursor coordinate
1126     at the current frame.
1127 */
rotateBy(const QPointF & first,const QPointF & second)1128 void QMapboxGL::rotateBy(const QPointF &first, const QPointF &second)
1129 {
1130     d_ptr->mapObj->rotateBy(
1131             mbgl::ScreenCoordinate { first.x(), first.y() },
1132             mbgl::ScreenCoordinate { second.x(), second.y() });
1133 }
1134 
1135 /*!
1136     Resize the map to \a size_ and scale to fit at the framebuffer. For
1137     high DPI screens, the size will be smaller than the framebuffer.
1138 */
resize(const QSize & size_)1139 void QMapboxGL::resize(const QSize& size_)
1140 {
1141     auto size = sanitizedSize(size_);
1142 
1143     if (d_ptr->mapObj->getSize() == size)
1144         return;
1145 
1146     d_ptr->mapObj->setSize(size);
1147 }
1148 
1149 /*!
1150     Adds an \a icon to the annotation icon pool. This can be later used by the annotation
1151     functions to shown any drawing on the map by referencing its \a name.
1152 
1153     Unlike using addIcon() for runtime styling, annotations added with addAnnotation()
1154     will survive style changes.
1155 
1156     \sa addAnnotation()
1157 */
addAnnotationIcon(const QString & name,const QImage & icon)1158 void QMapboxGL::addAnnotationIcon(const QString &name, const QImage &icon)
1159 {
1160     if (icon.isNull()) return;
1161 
1162     d_ptr->mapObj->addAnnotationImage(toStyleImage(name, icon));
1163 }
1164 
1165 /*!
1166     Returns the amount of meters per pixel from a given \a latitude_ and \a zoom_.
1167 */
metersPerPixelAtLatitude(double latitude_,double zoom_) const1168 double QMapboxGL::metersPerPixelAtLatitude(double latitude_, double zoom_) const
1169 {
1170     return mbgl::Projection::getMetersPerPixelAtLatitude(latitude_, zoom_);
1171 }
1172 
1173 /*!
1174     Return the projected meters for a given \a coordinate_ object.
1175 */
projectedMetersForCoordinate(const QMapbox::Coordinate & coordinate_) const1176 QMapbox::ProjectedMeters QMapboxGL::projectedMetersForCoordinate(const QMapbox::Coordinate &coordinate_) const
1177 {
1178     auto projectedMeters = mbgl::Projection::projectedMetersForLatLng(mbgl::LatLng { coordinate_.first, coordinate_.second });
1179     return QMapbox::ProjectedMeters(projectedMeters.northing(), projectedMeters.easting());
1180 }
1181 
1182 /*!
1183     Returns the coordinate for a given \a projectedMeters object.
1184 */
coordinateForProjectedMeters(const QMapbox::ProjectedMeters & projectedMeters) const1185 QMapbox::Coordinate QMapboxGL::coordinateForProjectedMeters(const QMapbox::ProjectedMeters &projectedMeters) const
1186 {
1187     auto latLng = mbgl::Projection::latLngForProjectedMeters(mbgl::ProjectedMeters { projectedMeters.first, projectedMeters.second });
1188     return QMapbox::Coordinate(latLng.latitude(), latLng.longitude());
1189 }
1190 
1191 /*!
1192     \fn QMapboxGL::pixelForCoordinate(const QMapbox::Coordinate &coordinate) const
1193 
1194     Returns the offset in pixels for \a coordinate. The origin pixel coordinate is
1195     located at the top left corner of the map view.
1196 
1197     This method returns the correct value for any coordinate, even if the coordinate
1198     is not currently visible on the screen.
1199 
1200     /note The return value is affected by the current zoom level, bearing and pitch.
1201 */
pixelForCoordinate(const QMapbox::Coordinate & coordinate_) const1202 QPointF QMapboxGL::pixelForCoordinate(const QMapbox::Coordinate &coordinate_) const
1203 {
1204     const mbgl::ScreenCoordinate pixel =
1205         d_ptr->mapObj->pixelForLatLng(mbgl::LatLng { coordinate_.first, coordinate_.second });
1206 
1207     return QPointF(pixel.x, pixel.y);
1208 }
1209 
1210 /*!
1211     Returns the geographic coordinate for the \a pixel coordinate.
1212 */
coordinateForPixel(const QPointF & pixel) const1213 QMapbox::Coordinate QMapboxGL::coordinateForPixel(const QPointF &pixel) const
1214 {
1215     const mbgl::LatLng latLng =
1216         d_ptr->mapObj->latLngForPixel(mbgl::ScreenCoordinate { pixel.x(), pixel.y() });
1217 
1218     return Coordinate(latLng.latitude(), latLng.longitude());
1219 }
1220 
1221 /*!
1222     Returns the coordinate and zoom combination needed in order to make the coordinate
1223     bounding box \a sw and \a ne visible.
1224 */
coordinateZoomForBounds(const QMapbox::Coordinate & sw,QMapbox::Coordinate & ne) const1225 QMapbox::CoordinateZoom QMapboxGL::coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne) const
1226 {
1227     auto bounds = mbgl::LatLngBounds::hull(mbgl::LatLng { sw.first, sw.second }, mbgl::LatLng { ne.first, ne.second });
1228     mbgl::CameraOptions camera = d_ptr->mapObj->cameraForLatLngBounds(bounds, d_ptr->margins);
1229 
1230     return {{ (*camera.center).latitude(), (*camera.center).longitude() }, *camera.zoom };
1231 }
1232 
1233 /*!
1234     Returns the coordinate and zoom combination needed in order to make the coordinate
1235     bounding box \a sw and \a ne visible taking into account \a newBearing and \a newPitch.
1236 */
coordinateZoomForBounds(const QMapbox::Coordinate & sw,QMapbox::Coordinate & ne,double newBearing,double newPitch)1237 QMapbox::CoordinateZoom QMapboxGL::coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne,
1238     double newBearing, double newPitch)
1239 
1240 {
1241     // FIXME: mbgl::Map::cameraForLatLngBounds should
1242     // take bearing and pitch as input too, so this
1243     // hack won't be needed.
1244     double currentBearing = bearing();
1245     double currentPitch = pitch();
1246 
1247     setBearing(newBearing);
1248     setPitch(newPitch);
1249 
1250     auto bounds = mbgl::LatLngBounds::hull(mbgl::LatLng { sw.first, sw.second }, mbgl::LatLng { ne.first, ne.second });
1251     mbgl::CameraOptions camera = d_ptr->mapObj->cameraForLatLngBounds(bounds, d_ptr->margins);
1252 
1253     setBearing(currentBearing);
1254     setPitch(currentPitch);
1255 
1256     return {{ (*camera.center).latitude(), (*camera.center).longitude() }, *camera.zoom };
1257 }
1258 
1259 /*!
1260     \property QMapboxGL::margins
1261     \brief the map margins in pixels from the corners of the map.
1262 
1263     This property sets a new reference center for the map.
1264 */
setMargins(const QMargins & margins_)1265 void QMapboxGL::setMargins(const QMargins &margins_)
1266 {
1267     d_ptr->margins = {
1268         static_cast<double>(margins_.top()),
1269         static_cast<double>(margins_.left()),
1270         static_cast<double>(margins_.bottom()),
1271         static_cast<double>(margins_.right())
1272     };
1273 }
1274 
margins() const1275 QMargins QMapboxGL::margins() const
1276 {
1277     return QMargins(
1278         d_ptr->margins.left(),
1279         d_ptr->margins.top(),
1280         d_ptr->margins.right(),
1281         d_ptr->margins.bottom()
1282     );
1283 }
1284 
1285 /*!
1286     Adds a source \a id to the map as specified by the \l
1287     {https://www.mapbox.com/mapbox-gl-style-spec/#root-sources}{Mapbox style specification} with
1288     \a params.
1289 
1290     This example reads a GeoJSON from the Qt resource system and adds it as source:
1291 
1292     \code
1293         QFile geojson(":source1.geojson");
1294         geojson.open(QIODevice::ReadOnly);
1295 
1296         QVariantMap routeSource;
1297         routeSource["type"] = "geojson";
1298         routeSource["data"] = geojson.readAll();
1299 
1300         map->addSource("routeSource", routeSource);
1301     \endcode
1302 */
addSource(const QString & id,const QVariantMap & params)1303 void QMapboxGL::addSource(const QString &id, const QVariantMap &params)
1304 {
1305     using namespace mbgl::style;
1306     using namespace mbgl::style::conversion;
1307 
1308     Error error;
1309     mbgl::optional<std::unique_ptr<Source>> source = convert<std::unique_ptr<Source>>(QVariant(params), error, id.toStdString());
1310     if (!source) {
1311         qWarning() << "Unable to add source:" << error.message.c_str();
1312         return;
1313     }
1314 
1315     d_ptr->mapObj->getStyle().addSource(std::move(*source));
1316 }
1317 
1318 /*!
1319     Returns true if the layer with given \a sourceID exists, false otherwise.
1320 */
sourceExists(const QString & sourceID)1321 bool QMapboxGL::sourceExists(const QString& sourceID)
1322 {
1323     return !!d_ptr->mapObj->getStyle().getSource(sourceID.toStdString());
1324 }
1325 
1326 /*!
1327     Updates the source \a id with new \a params.
1328 
1329     If the source does not exist, it will be added like in addSource(). Only
1330     image and GeoJSON sources can be updated.
1331 */
updateSource(const QString & id,const QVariantMap & params)1332 void QMapboxGL::updateSource(const QString &id, const QVariantMap &params)
1333 {
1334     using namespace mbgl::style;
1335     using namespace mbgl::style::conversion;
1336 
1337     auto source = d_ptr->mapObj->getStyle().getSource(id.toStdString());
1338     if (!source) {
1339         addSource(id, params);
1340         return;
1341     }
1342 
1343     auto sourceGeoJSON = source->as<GeoJSONSource>();
1344     auto sourceImage = source->as<ImageSource>();
1345     if (!sourceGeoJSON && !sourceImage) {
1346         qWarning() << "Unable to update source: only GeoJSON and Image sources are mutable.";
1347         return;
1348     }
1349 
1350     if (sourceImage) {
1351         if (params.contains("url")) {
1352             sourceImage->setURL(params["url"].toString().toStdString());
1353         }
1354     } else if (sourceGeoJSON && params.contains("data")) {
1355         Error error;
1356         auto result = convert<mbgl::GeoJSON>(params["data"], error);
1357         if (result) {
1358             sourceGeoJSON->setGeoJSON(*result);
1359         }
1360     }
1361 }
1362 
1363 /*!
1364     Removes the source \a id.
1365 
1366     This method has no effect if the source does not exist.
1367 */
removeSource(const QString & id)1368 void QMapboxGL::removeSource(const QString& id)
1369 {
1370     auto sourceIDStdString = id.toStdString();
1371 
1372     if (d_ptr->mapObj->getStyle().getSource(sourceIDStdString)) {
1373         d_ptr->mapObj->getStyle().removeSource(sourceIDStdString);
1374     }
1375 }
1376 
1377 /*!
1378     Adds a custom layer \a id with the initialization function \a initFn, the
1379     render function \a renderFn and the deinitialization function \a deinitFn with
1380     the user data \a context before the existing layer \a before.
1381 
1382     \warning This is used for delegating the rendering of a layer to the user of
1383     this API and is not officially supported. Use at your own risk.
1384 */
addCustomLayer(const QString & id,QScopedPointer<QMapbox::CustomLayerHostInterface> & host,const QString & before)1385 void QMapboxGL::addCustomLayer(const QString &id,
1386         QScopedPointer<QMapbox::CustomLayerHostInterface>& host,
1387         const QString& before)
1388 {
1389     class HostWrapper : public mbgl::style::CustomLayerHost {
1390         public:
1391         QScopedPointer<QMapbox::CustomLayerHostInterface> ptr;
1392         HostWrapper(QScopedPointer<QMapbox::CustomLayerHostInterface>& p)
1393          : ptr(p.take()) {
1394          }
1395 
1396         void initialize() {
1397             ptr->initialize();
1398         }
1399 
1400         void render(const mbgl::style::CustomLayerRenderParameters& params) {
1401             QMapbox::CustomLayerRenderParameters renderParams;
1402             renderParams.width = params.width;
1403             renderParams.height = params.height;
1404             renderParams.latitude = params.latitude;
1405             renderParams.longitude = params.longitude;
1406             renderParams.zoom = params.zoom;
1407             renderParams.bearing = params.bearing;
1408             renderParams.pitch = params.pitch;
1409             renderParams.fieldOfView = params.fieldOfView;
1410             ptr->render(renderParams);
1411         }
1412 
1413         void contextLost() { }
1414 
1415         void deinitialize() {
1416             ptr->deinitialize();
1417         }
1418     };
1419 
1420     d_ptr->mapObj->getStyle().addLayer(std::make_unique<mbgl::style::CustomLayer>(
1421             id.toStdString(),
1422             std::make_unique<HostWrapper>(host)),
1423             before.isEmpty() ? mbgl::optional<std::string>() : mbgl::optional<std::string>(before.toStdString()));
1424 }
1425 
1426 /*!
1427     Adds a style layer to the map as specified by the \l
1428     {https://www.mapbox.com/mapbox-gl-style-spec/#root-layers}{Mapbox style specification} with
1429     \a params. The layer will be added under the layer specified by \a before, if specified.
1430     Otherwise it will be added as the topmost layer.
1431 
1432     This example shows how to add a layer that will be used to show a route line on the map. Note
1433     that nothing will be drawn until we set paint properties using setPaintProperty().
1434 
1435     \code
1436         QVariantMap route;
1437         route["id"] = "route";
1438         route["type"] = "line";
1439         route["source"] = "routeSource";
1440 
1441         map->addLayer(route);
1442     \endcode
1443 
1444     /note The source must exist prior to adding a layer.
1445 */
addLayer(const QVariantMap & params,const QString & before)1446 void QMapboxGL::addLayer(const QVariantMap &params, const QString& before)
1447 {
1448     using namespace mbgl::style;
1449     using namespace mbgl::style::conversion;
1450 
1451     Error error;
1452     mbgl::optional<std::unique_ptr<Layer>> layer = convert<std::unique_ptr<Layer>>(QVariant(params), error);
1453     if (!layer) {
1454         qWarning() << "Unable to add layer:" << error.message.c_str();
1455         return;
1456     }
1457 
1458     d_ptr->mapObj->getStyle().addLayer(std::move(*layer),
1459         before.isEmpty() ? mbgl::optional<std::string>() : mbgl::optional<std::string>(before.toStdString()));
1460 }
1461 
1462 /*!
1463     Returns true if the layer with given \a id exists, false otherwise.
1464 */
layerExists(const QString & id)1465 bool QMapboxGL::layerExists(const QString& id)
1466 {
1467     return !!d_ptr->mapObj->getStyle().getLayer(id.toStdString());
1468 }
1469 
1470 /*!
1471     Removes the layer with given \a id.
1472 */
removeLayer(const QString & id)1473 void QMapboxGL::removeLayer(const QString& id)
1474 {
1475     d_ptr->mapObj->getStyle().removeLayer(id.toStdString());
1476 }
1477 
1478 /*!
1479     List of all existing layer ids from the current style.
1480 */
layerIds() const1481 QList<QString> QMapboxGL::layerIds() const
1482 {
1483     const auto &layers = d_ptr->mapObj->getStyle().getLayers();
1484 
1485     QList<QString> layerIds;
1486     layerIds.reserve(layers.size());
1487 
1488     for (const mbgl::style::Layer *layer : layers) {
1489         layerIds.append(QString::fromStdString(layer->getID()));
1490     }
1491 
1492     return layerIds;
1493 }
1494 
1495 /*!
1496     Adds the \a image with the identifier \a id that can be used
1497     later by a symbol layer.
1498 
1499     If the \a id was already added, it gets replaced by the new
1500     \a image only if the dimensions of the image are the same as
1501     the old image, otherwise it has no effect.
1502 
1503     \sa addLayer()
1504 */
addImage(const QString & id,const QImage & image)1505 void QMapboxGL::addImage(const QString &id, const QImage &image)
1506 {
1507     if (image.isNull()) return;
1508 
1509     d_ptr->mapObj->getStyle().addImage(toStyleImage(id, image));
1510 }
1511 
1512 /*!
1513     Removes the image \a id.
1514 */
removeImage(const QString & id)1515 void QMapboxGL::removeImage(const QString &id)
1516 {
1517     d_ptr->mapObj->getStyle().removeImage(id.toStdString());
1518 }
1519 
1520 /*!
1521     Adds a \a filter to a style \a layer using the format described in the \l
1522     {https://www.mapbox.com/mapbox-gl-js/style-spec/#other-filter}{Mapbox style specification}.
1523 
1524     Given a layer \c marker from an arbitrary GeoJSON source containing features of type \b
1525     "Point" and \b "LineString", this example shows how to make sure the layer will only tag
1526     features of type \b "Point".
1527 
1528     \code
1529         QVariantList filterExpression;
1530         filterExpression.push_back(QLatin1String("=="));
1531         filterExpression.push_back(QLatin1String("$type"));
1532         filterExpression.push_back(QLatin1String("Point"));
1533 
1534         QVariantList filter;
1535         filter.push_back(filterExpression);
1536 
1537         map->setFilter(QLatin1String("marker"), filter);
1538     \endcode
1539 */
setFilter(const QString & layer,const QVariant & filter)1540 void QMapboxGL::setFilter(const QString& layer, const QVariant& filter)
1541 {
1542     using namespace mbgl::style;
1543     using namespace mbgl::style::conversion;
1544 
1545     Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString());
1546     if (!layer_) {
1547         qWarning() << "Layer not found:" << layer;
1548         return;
1549     }
1550 
1551     Filter filter_;
1552 
1553     Error error;
1554     mbgl::optional<Filter> converted = convert<Filter>(filter, error);
1555     if (!converted) {
1556         qWarning() << "Error parsing filter:" << error.message.c_str();
1557         return;
1558     }
1559     filter_ = std::move(*converted);
1560 
1561     if (layer_->is<FillLayer>()) {
1562         layer_->as<FillLayer>()->setFilter(filter_);
1563         return;
1564     }
1565     if (layer_->is<LineLayer>()) {
1566         layer_->as<LineLayer>()->setFilter(filter_);
1567         return;
1568     }
1569     if (layer_->is<SymbolLayer>()) {
1570         layer_->as<SymbolLayer>()->setFilter(filter_);
1571         return;
1572     }
1573     if (layer_->is<CircleLayer>()) {
1574         layer_->as<CircleLayer>()->setFilter(filter_);
1575         return;
1576     }
1577     if (layer_->is<FillExtrusionLayer>()) {
1578         layer_->as<FillExtrusionLayer>()->setFilter(filter_);
1579         return;
1580     }
1581 
1582     qWarning() << "Layer doesn't support filters";
1583 }
1584 
QVariantFromValue(const mbgl::Value & value)1585 QVariant QVariantFromValue(const mbgl::Value &value) {
1586     return value.match(
1587         [](const mbgl::NullValue) {
1588             return QVariant();
1589         }, [](const bool value_) {
1590             return QVariant(value_);
1591         }, [](const float value_) {
1592             return QVariant(value_);
1593         }, [](const int64_t value_) {
1594             return QVariant(static_cast<qlonglong>(value_));
1595         }, [](const double value_) {
1596             return QVariant(value_);
1597         }, [](const std::string &value_) {
1598            return QVariant(value_.c_str());
1599         }, [](const mbgl::Color &value_) {
1600             return QColor(value_.r, value_.g, value_.b, value_.a);
1601         }, [&](const std::vector<mbgl::Value> &vector) {
1602             QVariantList list;
1603             list.reserve(vector.size());
1604             for (const auto &value_ : vector) {
1605                 list.push_back(QVariantFromValue(value_));
1606             }
1607             return list;
1608         }, [&](const std::unordered_map<std::string, mbgl::Value> &map) {
1609             QVariantMap varMap;
1610             for (auto &item : map) {
1611                 varMap.insert(item.first.c_str(), QVariantFromValue(item.second));
1612             }
1613             return varMap;
1614         }, [](const auto &) {
1615             return QVariant();
1616         });
1617 }
1618 
1619 /*!
1620     Returns the current \a expression-based filter value applied to a style
1621     \layer, if any.
1622 
1623     Filter value types are described in the {https://www.mapbox.com/mapbox-gl-js/style-spec/#types}{Mapbox style specification}.
1624 */
getFilter(const QString & layer) const1625 QVariant QMapboxGL::getFilter(const QString &layer)  const {
1626     using namespace mbgl::style;
1627     using namespace mbgl::style::conversion;
1628 
1629     Layer* layer_ = d_ptr->mapObj->getStyle().getLayer(layer.toStdString());
1630     if (!layer_) {
1631         qWarning() << "Layer not found:" << layer;
1632         return QVariant();
1633     }
1634 
1635     Filter filter_;
1636 
1637     if (layer_->is<FillLayer>()) {
1638         filter_ = layer_->as<FillLayer>()->getFilter();
1639     } else if (layer_->is<LineLayer>()) {
1640         filter_ = layer_->as<LineLayer>()->getFilter();
1641     } else if (layer_->is<SymbolLayer>()) {
1642         filter_ = layer_->as<SymbolLayer>()->getFilter();
1643     } else if (layer_->is<CircleLayer>()) {
1644         filter_ = layer_->as<CircleLayer>()->getFilter();
1645     } else if (layer_->is<FillExtrusionLayer>()) {
1646         filter_ = layer_->as<FillExtrusionLayer>()->getFilter();
1647     } else {
1648         qWarning() << "Layer doesn't support filters";
1649         return QVariant();
1650     }
1651 
1652     auto serialized = filter_.serialize();
1653     return QVariantFromValue(serialized);
1654 }
1655 
1656 /*!
1657     Creates the infrastructure needed for rendering the map. It
1658     should be called before any call to render().
1659 
1660     Must be called on the render thread.
1661 */
createRenderer()1662 void QMapboxGL::createRenderer()
1663 {
1664     d_ptr->createRenderer();
1665 }
1666 
1667 /*!
1668     Destroys the infrastructure needed for rendering the map,
1669     releasing resources.
1670 
1671     Must be called on the render thread.
1672 */
destroyRenderer()1673 void QMapboxGL::destroyRenderer()
1674 {
1675     d_ptr->destroyRenderer();
1676 }
1677 
1678 /*!
1679     Start a static rendering of the current state of the map. This
1680     should only be called when the map is initialized in static mode.
1681 
1682     \sa QMapboxGLSettings::MapMode
1683 */
startStaticRender()1684 void QMapboxGL::startStaticRender()
1685 {
1686     d_ptr->mapObj->renderStill([this](std::exception_ptr err) {
1687         QString what;
1688 
1689         try {
1690             if (err) {
1691                 std::rethrow_exception(err);
1692             }
1693         } catch(const std::exception& e) {
1694             what = e.what();
1695         }
1696 
1697         emit staticRenderFinished(what);
1698     });
1699 }
1700 
1701 /*!
1702     Renders the map using OpenGL draw calls. It will make sure to bind the
1703     framebuffer object before drawing; otherwise a valid OpenGL context is
1704     expected with an appropriate OpenGL viewport state set for the size of
1705     the canvas.
1706 
1707     This function should be called only after the signal needsRendering() is
1708     emitted at least once.
1709 
1710     Must be called on the render thread.
1711 */
render()1712 void QMapboxGL::render()
1713 {
1714     d_ptr->render();
1715 }
1716 
1717 /*!
1718     If Mapbox GL needs to rebind the default \a fbo, it will use the
1719     ID supplied here. \a size is the size of the framebuffer, which
1720     on high DPI screens is usually bigger than the map size.
1721 
1722     Must be called on the render thread.
1723 */
setFramebufferObject(quint32 fbo,const QSize & size)1724 void QMapboxGL::setFramebufferObject(quint32 fbo, const QSize& size)
1725 {
1726     d_ptr->setFramebufferObject(fbo, size);
1727 }
1728 
1729 /*!
1730     Informs the map that the network connection has been established, causing
1731     all network requests that previously timed out to be retried immediately.
1732 */
connectionEstablished()1733 void QMapboxGL::connectionEstablished()
1734 {
1735     mbgl::NetworkStatus::Reachable();
1736 }
1737 
1738 /*!
1739     \fn void QMapboxGL::needsRendering()
1740 
1741     This signal is emitted when the visual contents of the map have changed
1742     and a redraw is needed in order to keep the map visually consistent
1743     with the current state.
1744 
1745     \sa render()
1746 */
1747 
1748 /*!
1749     \fn void QMapboxGL::staticRenderFinished(const QString &error)
1750 
1751     This signal is emitted when a static map is fully drawn. Usually the next
1752     step is to extract the map from a framebuffer into a container like a
1753     QImage. \a error is set to a message when an error occurs.
1754 
1755     \sa startStaticRender()
1756 */
1757 
1758 /*!
1759     \fn void QMapboxGL::mapChanged(QMapboxGL::MapChange change)
1760 
1761     This signal is emitted when the state of the map has changed. This signal
1762     may be used for detecting errors when loading a style or detecting when
1763     a map is fully loaded by analyzing the parameter \a change.
1764 */
1765 
1766 /*!
1767     \fn void QMapboxGL::mapLoadingFailed(QMapboxGL::MapLoadingFailure type, const QString &description)
1768 
1769     This signal is emitted when a map loading failure happens. Details of the
1770     failures are provided, including its \a type and textual \a description.
1771 */
1772 
1773 /*!
1774     \fn void QMapboxGL::copyrightsChanged(const QString &copyrightsHtml);
1775 
1776     This signal is emitted when the copyrights of the current content of the map
1777     have changed. This can be caused by a style change or adding a new source.
1778 
1779     \a copyrightsHtml is a string with a HTML snippet.
1780 */
1781 
QMapboxGLPrivate(QMapboxGL * q,const QMapboxGLSettings & settings,const QSize & size,qreal pixelRatio_)1782 QMapboxGLPrivate::QMapboxGLPrivate(QMapboxGL *q, const QMapboxGLSettings &settings, const QSize &size, qreal pixelRatio_)
1783     : QObject(q)
1784     , m_fileSourceObj(sharedDefaultFileSource(
1785         settings.cacheDatabasePath().toStdString(),
1786         settings.assetPath().toStdString(),
1787         settings.cacheDatabaseMaximumSize()))
1788     , m_threadPool(mbgl::sharedThreadPool())
1789     , m_mode(settings.contextMode())
1790     , m_pixelRatio(pixelRatio_)
1791 {
1792     // Setup the FileSource
1793     m_fileSourceObj->setAccessToken(settings.accessToken().toStdString());
1794     m_fileSourceObj->setAPIBaseURL(settings.apiBaseUrl().toStdString());
1795 
1796     if (settings.resourceTransform()) {
1797         m_resourceTransform = std::make_unique<mbgl::Actor<mbgl::ResourceTransform>>(*mbgl::Scheduler::GetCurrent(),
1798             [callback = settings.resourceTransform()] (mbgl::Resource::Kind, const std::string &&url_) -> std::string {
1799                 return callback(std::move(url_));
1800             });
1801        m_fileSourceObj->setResourceTransform(m_resourceTransform->self());
1802     }
1803 
1804     // Setup MapObserver
1805     m_mapObserver = std::make_unique<QMapboxGLMapObserver>(this);
1806 
1807     qRegisterMetaType<QMapboxGL::MapChange>("QMapboxGL::MapChange");
1808 
1809     connect(m_mapObserver.get(), SIGNAL(mapChanged(QMapboxGL::MapChange)), q, SIGNAL(mapChanged(QMapboxGL::MapChange)));
1810     connect(m_mapObserver.get(), SIGNAL(mapLoadingFailed(QMapboxGL::MapLoadingFailure,QString)), q, SIGNAL(mapLoadingFailed(QMapboxGL::MapLoadingFailure,QString)));
1811     connect(m_mapObserver.get(), SIGNAL(copyrightsChanged(QString)), q, SIGNAL(copyrightsChanged(QString)));
1812 
1813     // Setup the Map object
1814     mapObj = std::make_unique<mbgl::Map>(
1815             *this, // RendererFrontend
1816             *m_mapObserver,
1817             sanitizedSize(size),
1818             m_pixelRatio, *m_fileSourceObj, *m_threadPool,
1819             static_cast<mbgl::MapMode>(settings.mapMode()),
1820             static_cast<mbgl::ConstrainMode>(settings.constrainMode()),
1821             static_cast<mbgl::ViewportMode>(settings.viewportMode()));
1822 
1823     // Needs to be Queued to give time to discard redundant draw calls via the `renderQueued` flag.
1824     connect(this, SIGNAL(needsRendering()), q, SIGNAL(needsRendering()), Qt::QueuedConnection);
1825 }
1826 
~QMapboxGLPrivate()1827 QMapboxGLPrivate::~QMapboxGLPrivate()
1828 {
1829 }
1830 
update(std::shared_ptr<mbgl::UpdateParameters> parameters)1831 void QMapboxGLPrivate::update(std::shared_ptr<mbgl::UpdateParameters> parameters)
1832 {
1833     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1834 
1835     if (!m_mapRenderer) {
1836         return;
1837     }
1838 
1839     m_mapRenderer->updateParameters(std::move(parameters));
1840 
1841     requestRendering();
1842 }
1843 
setObserver(mbgl::RendererObserver & observer)1844 void QMapboxGLPrivate::setObserver(mbgl::RendererObserver &observer)
1845 {
1846     m_rendererObserver = std::make_shared<QMapboxGLRendererObserver>(
1847             *mbgl::util::RunLoop::Get(), observer);
1848 
1849     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1850 
1851     if (m_mapRenderer) {
1852         m_mapRenderer->setObserver(m_rendererObserver);
1853     }
1854 }
1855 
createRenderer()1856 void QMapboxGLPrivate::createRenderer()
1857 {
1858     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1859 
1860     if (m_mapRenderer) {
1861         return;
1862     }
1863 
1864     m_mapRenderer = std::make_unique<QMapboxGLMapRenderer>(
1865         m_pixelRatio,
1866         *m_fileSourceObj,
1867         *m_threadPool,
1868         m_mode
1869     );
1870 
1871     connect(m_mapRenderer.get(), SIGNAL(needsRendering()), this, SLOT(requestRendering()));
1872 
1873     m_mapRenderer->setObserver(m_rendererObserver);
1874 }
1875 
destroyRenderer()1876 void QMapboxGLPrivate::destroyRenderer()
1877 {
1878     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1879 
1880     m_mapRenderer.reset();
1881 }
1882 
render()1883 void QMapboxGLPrivate::render()
1884 {
1885     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1886 
1887     if (!m_mapRenderer) {
1888         createRenderer();
1889     }
1890 
1891 #if defined(__APPLE__) && QT_VERSION < 0x050000
1892     // FIXME Qt 4.x provides an incomplete FBO at start.
1893     // See https://bugreports.qt.io/browse/QTBUG-36802 for details.
1894     if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
1895         return;
1896     }
1897 #endif
1898 
1899     m_renderQueued.clear();
1900     m_mapRenderer->render();
1901 }
1902 
setFramebufferObject(quint32 fbo,const QSize & size)1903 void QMapboxGLPrivate::setFramebufferObject(quint32 fbo, const QSize& size)
1904 {
1905     std::lock_guard<std::recursive_mutex> lock(m_mapRendererMutex);
1906 
1907     if (!m_mapRenderer) {
1908         createRenderer();
1909     }
1910 
1911     m_mapRenderer->updateFramebuffer(fbo, sanitizedSize(size));
1912 }
1913 
requestRendering()1914 void QMapboxGLPrivate::requestRendering()
1915 {
1916     if (!m_renderQueued.test_and_set()) {
1917         emit needsRendering();
1918     }
1919 }
1920