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 ¢er)
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 ¢er)
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 ¢er)
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 ¢er) {
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 ¶ms)
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 ¶ms)
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 ¶ms, 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 ©rightsHtml);
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