mapbox / mapbox-gl-native

Interactive, thoroughly customizable maps in native Android, iOS, macOS, Node.js, and Qt applications, powered by vector tiles and OpenGL
https://mapbox.com/mobile
Other
4.38k stars 1.32k forks source link

Runaway tile downloads, symbol placement when map is tilted with top padding #15163

Open 1ec5 opened 5 years ago

1ec5 commented 5 years ago

As of #14664, increasing the camera’s top padding can cause the camera’s focal point to lie lower than the center of the map. This significantly improves the perspective effect, but it also causes excessive detail to be shown toward the top of the map. Adaptive tile coverage (#9037) is still unimplemented, so tiles for the same zoom level get loaded throughout the map and displayed in smaller and smaller cells as you go toward the top of the map.

Here’s a map view displayed full-screen on an iPhone 8 simulator, with the top content inset set to 40% of the view’s height and the left content inset set to half the view’s height. The camera is rotated 258° counterclockwise from north (approximately west-southwest), tilted 60°, and raised about 200 meters above the ground. Tile boundaries are enabled. The top of the map is covered by an inordinate number of tiles:

boundaries

Sometimes, with slightly different rotation and zooming, mbgl hangs because of the sheer amount of symbol placement that needs to happen – many times the amount of symbol placement that would happen with a normal viewport:

#0  0x000000010c939d68 in std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::__map_value_compare<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::less<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > >, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > > > >::__root() const at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1076
#1  0x000000010c939c89 in std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::__map_value_compare<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::less<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > >, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > > > >::~__tree() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1831
#2  0x000000010c939c65 in std::__1::__tree<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::__map_value_compare<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > >, std::__1::less<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > >, true>, std::__1::allocator<std::__1::__value_type<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > > > >::~__tree() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1828
#3  0x000000010c939c45 in std::__1::map<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> >, std::__1::less<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > const, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > > > >::~map() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1504
#4  0x000000010c939c25 in std::__1::map<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> >, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> >, std::__1::less<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char16_t, std::__1::char_traits<char16_t>, std::__1::allocator<char16_t> > const, std::__1::vector<mbgl::IndexedSymbolInstance, std::__1::allocator<mbgl::IndexedSymbolInstance> > > > >::~map() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1504
#5  0x000000010c939c09 in mbgl::TileLayerIndex::~TileLayerIndex() at /path/to/mapbox-gl-native/src/mbgl/text/cross_tile_symbol_index.hpp:31
#6  0x000000010c939be5 in mbgl::TileLayerIndex::~TileLayerIndex() at /path/to/mapbox-gl-native/src/mbgl/text/cross_tile_symbol_index.hpp:31
#7  0x000000010c939bc9 in std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex>::~pair() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/utility:314
#8  0x000000010c939ba5 in std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex>::~pair() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/utility:314
#9  0x000000010c939b89 in void std::__1::allocator_traits<std::__1::allocator<std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*> > >::__destroy<std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex> >(std::__1::integral_constant<bool, false>, std::__1::allocator<std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*> >&, std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:1747
#10 0x000000010c939abd in void std::__1::allocator_traits<std::__1::allocator<std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*> > >::destroy<std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex> >(std::__1::allocator<std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*> >&, std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/memory:1595
#11 0x000000010c939a2f in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1843
#12 0x000000010c9399f4 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1841
#13 0x000000010c9399e3 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1840
#14 0x000000010c9399f4 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1841
#15 0x000000010c9399e3 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1840
#16 0x000000010c9399e3 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1840
#17 0x000000010c9399f4 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1841
#18 0x000000010c9399e3 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1840
#19 0x000000010c9399f4 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::destroy(std::__1::__tree_node<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, void*>*) at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1841
#20 0x000000010c9399a5 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::~__tree() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1831
#21 0x000000010c939975 in std::__1::__tree<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::__map_value_compare<mbgl::OverscaledTileID, std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex>, std::__1::less<mbgl::OverscaledTileID>, true>, std::__1::allocator<std::__1::__value_type<mbgl::OverscaledTileID, mbgl::TileLayerIndex> > >::~__tree() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1828
#22 0x000000010c939955 in std::__1::map<mbgl::OverscaledTileID, mbgl::TileLayerIndex, std::__1::less<mbgl::OverscaledTileID>, std::__1::allocator<std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex> > >::~map() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1504
#23 0x000000010c939935 in std::__1::map<mbgl::OverscaledTileID, mbgl::TileLayerIndex, std::__1::less<mbgl::OverscaledTileID>, std::__1::allocator<std::__1::pair<mbgl::OverscaledTileID const, mbgl::TileLayerIndex> > >::~map() at /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/__tree:1504
#24 0x000000010cc8d3ba in mbgl::CrossTileSymbolLayerIndex::addBucket(mbgl::OverscaledTileID const&, mbgl::SymbolBucket&, unsigned int&) at /path/to/mapbox-gl-native/src/mbgl/text/cross_tile_symbol_index.cpp:124
#25 0x000000010c7fe8af in mbgl::SymbolBucket::registerAtCrossTileIndex(mbgl::CrossTileSymbolLayerIndex&, mbgl::OverscaledTileID const&, unsigned int&) at /path/to/mapbox-gl-native/src/mbgl/renderer/buckets/symbol_bucket.cpp:248
#26 0x000000010cc8e128 in mbgl::CrossTileSymbolIndex::addLayer(mbgl::RenderLayer const&, float) at /path/to/mapbox-gl-native/src/mbgl/text/cross_tile_symbol_index.cpp:177
#27 0x000000010c9322a6 in mbgl::RenderOrchestrator::createRenderTree(mbgl::UpdateParameters const&) at /path/to/mapbox-gl-native/src/mbgl/renderer/render_orchestrator.cpp:376
#28 0x000000010c9737b0 in mbgl::Renderer::render(mbgl::UpdateParameters const&) at /path/to/mapbox-gl-native/src/mbgl/renderer/renderer.cpp:36
#29 0x000000010c3fee94 in MGLRenderFrontend::render() at /path/to/mapbox-gl-native/platform/darwin/src/MGLRendererFrontend.h:57
#30 0x000000010c3fede5 in ::-[MGLMapView renderSync]() at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:905
#31 0x000000010c324983 in MGLMapViewImpl::render() at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView+Impl.mm:14
#32 0x000000010c328158 in ::-[MGLMapViewImplDelegate glkView:drawInRect:](GLKView *, CGRect) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView+OpenGL.mm:26
#33 0x000000011301fffd in -[GLKView _display:] ()
#34 0x000000010c328b45 in MGLMapViewOpenGLImpl::display() at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView+OpenGL.mm:117
#35 0x000000010c400fbb in ::-[MGLMapView updateFromDisplayLink:](CADisplayLink *) at /path/to/mapbox-gl-native/platform/ios/src/MGLMapView.mm:1069

Increasing the top content inset to fully half the view’s height causes mbgl to not even render the top half of the map:

half half-rotated

This issue could potentially affect a variety of iOS applications. The iOS and macOS map SDKs automatically increase the top content inset by default to accommodate any translucent top bar or toolbar in the same view controller. The issue isn’t acute in iosapp because the top bar there is relatively thin, but navigation applications in particular tend to have larger elements on top, such as a search bar or visual guidance instructions.

/cc @mapbox/gl-core @mapbox/maps-ios @mapbox/navigation-ios

1ec5 commented 5 years ago

There are two potential fixes for this issue:

astojilj commented 5 years ago

There are two potential fixes for this issue:

  • Adaptive tile loading (#9037)
  • Varying the maximum pitch – but by the viewable distance rather than the zoom level, as proposed in #6908

As it is about high memory consumption, adding reference to mapbox/mapbox-gl-js#1898 and https://github.com/mapbox/mapbox-gl-native/issues/15022 - there we have memory issues with no pitch and it is expected to have the same with pitch.

chloekraw commented 5 years ago

As it is about high memory consumption, adding reference to mapbox/mapbox-gl-js#1898 and #15022 - there we have memory issues with no pitch and it is expected to have the same with pitch.

Ah okay, sounds good. Let's try to address those issues first then, in the next release.

1ec5 commented 5 years ago

As it is about high memory consumption, adding reference to mapbox/mapbox-gl-js#1898 and #15022 - there we have memory issues with no pitch and it is expected to have the same with pitch.

That’s certainly part of it, but the issue is really about wasteful tile loading, which affects not only memory usage but also time-to-render, energy usage, and bandwidth consumption. The memory-related fixes will only go so far to mitigate the underlying issue, which is that the developer can implicitly control the camera’s elevation (mapbox/mapbox-gl-js#3552) without the limits that we’d impose if we implemented that feature explicitly.

astojilj commented 5 years ago

As it is about high memory consumption, adding reference to mapbox/mapbox-gl-js#1898 and #15022 - there we have memory issues with no pitch and it is expected to have the same with pitch.

That’s certainly part of it, but the issue is really about wasteful tile loading, which affects not only memory usage but also time-to-render, energy usage, and bandwidth consumption. The memory-related fixes will only go so far to mitigate the underlying issue, which is that the developer can implicitly control the camera’s elevation (mapbox/mapbox-gl-js#3552) without the limits that we’d impose if we implemented that feature explicitly.

Yes. Working on that first.

astojilj commented 5 years ago

There are two potential fixes for this issue:

  • Adaptive tile loading (#9037)
  • Varying the maximum pitch – but by the viewable distance rather than the zoom level, as proposed in #6908

@1ec5 , to further clarify what I'm working on in https://github.com/mapbox/mapbox-gl-native/pull/15195, both are required: even with adaptive tile loading, maximum pitch for given edge insets shouldn't allow displaying area above horizon. Max pith values will likely be higher than the current 60 deg, but then it depends on edge insets.

astojilj commented 5 years ago

This is not fixed yet - work is ongoing in https://github.com/mapbox/mapbox-gl-native/pull/15230

JustinGanzer commented 5 years ago

This is a very real issue for us with a large overlay at the top. We would use the MGLMapView's contentInset to translate the maps center location which seemed to be it's purpose back in #3583.

Now the new behaviour is very pleasing, albeit different and not something I'd expect personally when reading the documentation.

Issue 1: With a top padding, as is the case in our app, it used to feel like the camera was simply moved up in terms of latitude before this change. Now it feels like the camera is moved up AND a lower angle of view is applied effectively no longer truly representing the camera pitch we had envisioned or am I wrong?

Reading this conversion gives me reason to believe that this new behaviour is intended. However how would one apply an exact pitch of 60 degrees now while contentInsets are in place? I feel like we'd need another way of translating MGLMapViews center.

Issue 2: As 1ec5 has mentioned, this hammers away at the battery life during navigation. I feel like the only solution at the moment is to change from translucent to opaque overlays and resizing the MapView or play around with CameraOptions.padding?

astojilj commented 5 years ago

@JustinGanzer Thanks. Let me take another look at it. We are working on tile LOD to show higher pitch values but that would only address Issue 2.

Now the new behaviour is very pleasing, albeit different and not something I'd expect personally when reading the documentation.

Could you please share the APIs or even better, come of the code with inset values you use to set content inset... and the part of documentation about the non expected behavior?

JustinGanzer commented 5 years ago

@astojilj

Here are comparative images and code regarding Issue 1. I'd like to have a pitch of 60 degrees and a topInset of 70% as to have the center at the bottom 15% of the mapView. Here you'll see that without insets they're both identical, but with its as though the pitch changed, or the curvature of the camera lens at the top drastically increased.

Images with Mapbox 4.10 **Without Inset:** ![loc2_NoInset](https://user-images.githubusercontent.com/15075130/68683090-7cfd4e80-0566-11ea-8fb6-fae3e99b9a08.PNG) **With Inset:** ![loc2_YesInset](https://user-images.githubusercontent.com/15075130/68682922-34de2c00-0566-11ea-93aa-e42d2b702e0e.PNG)
Images with Mapbox 5.5 **Without Inset:** ![loc2_NoInset](https://user-images.githubusercontent.com/15075130/68683527-352af700-0567-11ea-907a-e2778dacf189.PNG) **With Inset:** ![loc2_YesInset](https://user-images.githubusercontent.com/15075130/68683543-3f4cf580-0567-11ea-8618-d64a40534668.PNG)
Code used to test behaviour ``` import Mapbox import Accelerate class TestMapController: UIViewController, MGLMapViewDelegate { var mapView: MGLMapView! let btnToggleInsets: UIButton let btnFly: UIButton let btnNextLocation: UIButton var isFlying = false var defaultInsets: UIEdgeInsets? init() { btnFly = { () -> UIButton in let btn = UIButton() btn.setTitle("Fly Line", for: .normal) btn.backgroundColor = .orange btn.translatesAutoresizingMaskIntoConstraints = false return btn }() btnToggleInsets = { () -> UIButton in let btn = UIButton() btn.setTitle("Toggle Insets", for: .normal) btn.backgroundColor = .orange btn.translatesAutoresizingMaskIntoConstraints = false return btn }() btnNextLocation = { () -> UIButton in let btn = UIButton() btn.setTitle("Next", for: .normal) btn.backgroundColor = .orange btn.translatesAutoresizingMaskIntoConstraints = false return btn }() super.init(nibName: nil, bundle: nil) hidesBottomBarWhenPushed = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.streetsStyleURL) mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] mapView.tintColor = .darkGray mapView.delegate = self view.addSubview(mapView) btnFly.addTarget(self, action: #selector(fly), for: .touchUpInside) btnToggleInsets.addTarget(self, action: #selector(toggleInsets), for: .touchUpInside) btnNextLocation.addTarget(self, action: #selector(nextLocation), for: .touchUpInside) view.addSubview(btnFly) view.addSubview(btnToggleInsets) view.addSubview(btnNextLocation) btnFly.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10).isActive = true btnFly.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10).isActive = true btnNextLocation.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true btnNextLocation.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10).isActive = true btnToggleInsets.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10).isActive = true btnToggleInsets.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10).isActive = true } func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) { let source = MGLShapeSource(identifier: "pictures", features: [], options: nil) let blueBoxImage = TestMapController.singleColoredImage(of: .blue, and: CGSize(width: 50, height: 50)) style.setImage(blueBoxImage, forName: "picture") let layer = TestMapController.getLayer(for: source) style.addSource(source) style.addLayer(layer) } @objc func fly() { guard !isFlying else { return } isFlying = true DispatchQueue.global().async { self.isFlying = self.flyLine() } } var iNextCount = 0 @objc func nextLocation() { let locations = [CLLocationCoordinate2D(latitude: 52.399580, longitude: 13.048261), CLLocationCoordinate2D(latitude: 52.510361, longitude: 13.389938)] let l = locations[iNextCount % locations.count] let c = MGLMapCamera(lookingAtCenter: l, altitude: 300.0, pitch: 60.0, heading: 0.0) self.mapView.setCamera(c, animated: false) iNextCount += 1 } private func flyLine() -> Bool { let lon: Float = 13.4 let lat: Float = 52.496 let latEnd: Float = 52.55 let lonDouble = Double(lon) let fps = 60 let waitingTime = 1.0 / Double(fps - 1) ///number of frames for 10 seconds let numOfInterpolations = fps * 20 var lats = [Float](repeating: 0, count: numOfInterpolations) vDSP_vgenp([lat, latEnd], 1, [0, Float(numOfInterpolations)], 1, &lats, 1, vDSP_Length(numOfInterpolations), 2) let dg = DispatchGroup() dg.enter() for l in lats { DispatchQueue.main.async { let c = MGLMapCamera(lookingAtCenter: CLLocationCoordinate2D(latitude: Double(l), longitude: lonDouble), altitude: 300.0, pitch: 60.0, heading: 0.0) self.mapView.setCamera(c, animated: false) } usleep(UInt32(waitingTime * 1_000_000)) } dg.leave() dg.wait() return false } @objc private func toggleInsets() { let leftInset, rightInset, bottomInset: CGFloat leftInset = 40; rightInset = 40; bottomInset = 40 let topInset: CGFloat = mapView.height * 0.7 let newInsets = UIEdgeInsets(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) if defaultInsets == nil { defaultInsets = mapView.contentInset mapView.contentInset = newInsets } else { mapView.contentInset = defaultInsets! defaultInsets = nil } } static func singleColoredImage(of color: UIColor, and size: CGSize) -> UIImage { let format = UIGraphicsImageRendererFormat() return UIGraphicsImageRenderer(size: size, format: format).image { (context) in color.setFill() context.fill(CGRect(origin: .zero, size: size)) } } static func getLayer(for source: MGLShapeSource) -> MGLStyleLayer { let layer = MGLSymbolStyleLayer(identifier: "pictures", source: source) layer.iconAllowsOverlap = NSExpression(forConstantValue: true) layer.minimumZoomLevel = 6.0 let nameOfPictures = "picture" let matching = "'\(nameOfPictures)', '\(nameOfPictures)'" let formatImageName = "MGL_MATCH(highlight, \(matching), '\(nameOfPictures)')" let functionImage = NSExpression(format: formatImageName) layer.iconImageName = functionImage layer.keepsIconUpright = NSExpression(forConstantValue: 1) let formatScale = "mgl_interpolate:withCurveType:parameters:stops:($zoomLevel, 'exponential', 1, %@)" layer.iconScale = NSExpression(format: formatScale, [6: 0.3, 11: 0.6, 14: 0.88]) return layer } } ```

And here are some performance changes when navigating across the map, without insets they are almost identical with 5.5 a bit better actually. But with insets :

Performance in Mapbox 4.10 vs Mapbox 5.5 (Insets) **4.10 with insets** ![4_10_Performance](https://user-images.githubusercontent.com/15075130/68684169-52ac9080-0568-11ea-9929-eb5d7a68cb43.png) **5.5 with insets** ![5_5_Performance](https://user-images.githubusercontent.com/15075130/68684187-5a6c3500-0568-11ea-8291-cbd81305682c.png)
JustinGanzer commented 5 years ago

@astojilj

And the docs here, read:

For instance, if the only the top edge is inset, the map center is effectively shifted downward

With that in mind, my assumption would be that using setCenterCoordinate:animated: of MGLMapView to apply the very same location to two MapViews would look almost the same. The only difference would be the offset of the real camera center due to the contentInset, thus having the latitude & longitude of said real center slightly moved. Pitch, heading etc would stay the same.

Edit: I with topInsets at about 70% of the mapViews height, the camera at a pitch of 50 looks near identical to a camera with no insets with a pitch at 60.

Perhaps I'm interpreting in wrong, but I hope this makes it kind of understandable as to why this change had me a bit confused.

astojilj commented 5 years ago

@JustinGanzer Thanks, a lot, for help on this. Let's continue using your screenshots to discuss what's happening. Red rectangle is an approximation of area that becomes content area (defined by content insets):

Images with Mapbox 4.10

Screen Shot 2019-11-14 at 13 39 55

Images with Mapbox 5.5

Screen Shot 2019-11-14 at 13 37 59

With that in mind, my assumption would be that using setCenterCoordinate:animated: of MGLMapView to apply the very same location to two MapViews would look almost the same

It should look the same.

4.10: Effectively, the zoom in center (that is now offset to 15% of the hight, measured from the bottom) is not what is specified by the API: zoom is larger.

5.5: The zoom in center is according to specified, but the pitch is not.

Now it feels like the camera is moved up AND a lower angle of view is applied effectively no longer truly representing the camera pitch we had envisioned or am I wrong?

https://github.com/mapbox/mapbox-gl-native/pull/15195 limits the pitch in order to prevent horizon to be displayed. But, it doesn't cover the issue you are experiencing - it doesn't do it so that pitch takes into account effect of asymmetric viewport (wider field of view) to pitch. Even the pitch in this case is ~60, it looks on the screen as if the pitch is having much larger value. Did you mean that with lower angle of view?

I'll work on fixing this. It would likely not require tile LOD to improve performance: number of tiles processed should be the same as with no content inset.

JustinGanzer commented 5 years ago

@astojilj glad it's of any use, and thank you for the insightful description of what's going on. Yes I did refer to the increasing field of view as lower higher angle of view. Sorry about the lower/higher mistake here.

Ahh I did not see that in 4.10 the zoom was incorrectly affected by the use of contentInset. Good eye!

Just out of curiosity, why does the field of view increase in version 5.5? Is it because the camera is not moved on the latitude/longitude while at the same time trying to maintain zoom and pitch for the specified camera center? Would it still be compliant with the API if the camera was moved a bit instead?

astojilj commented 5 years ago

@JustinGanzer Please ignore my analysis from above. I have double-checked the screenshots again and it seems that there is an optical illusion :) The pitch in both cases is rendered the same on screen. Different level of detail, thin lines at the top and the position of screenshot (one on the right looks like having larger pitch value) makes it look that the pitch is different. This is going to be fixed soon, after porting https://github.com/mapbox/mapbox-gl-js/pull/8975 to gl-native.

So, the "experiment" showing that the pitch is actually correct: I have cut center + upper part of no-inset screen (1 on the picture), moved it down where it should be rendered as expected by content inset, and combined it with upper part(2) of inset screenshot. They fit perfectly (3rd image from left, if zooming you'll be able to see icons at 30% hight of the top, part of cut (1)). ![Screen Shot 2019-11-14 at 23 09 38](https://user-images.githubusercontent.com/549216/68896336-e54d5b00-0733-11ea-8aaf-a7e5b4659c74.png)

Screenshot with large top Inset now doesn't look good and has severe performance issue because upper part is too busy: tiles at the top are showing way too much details compared to central or bottom area. As work on https://github.com/mapbox/mapbox-gl-js/pull/8975 progresses, let me provide more details/screenshots, with the same center (52.510361, 13.389938) to back up this claim.

Just out of curiosity, why does the field of view increase in version 5.5? Is it because the camera is not moved on the latitude/longitude while at the same time trying to maintain zoom and pitch for the specified camera center? Would it still be compliant with the API if the camera was moved a bit instead?

Let's consider no-inset center of view field of view (fov on the image) and inset center of view (new fovon the image). Center is at the same distance from camera, pitch is the same but camera shows much larger area of the map: map area (2) is with inset, map area (1) is with no insets. I've scaled map to the screen center - it probably looks weird that it is in front of the screen but hopefully it helps explaining.

IMG_0822

Not sure if I have covered the question, though.

JustinGanzer commented 5 years ago

@astojilj You have fully answered my question with that insightful drawing of yours. Thanks, that really helped me understand exactly what is happening.

Again you have proven quite the keen eye for detail. With the experiment-screenshots I now also understand that the behaviour in Mapbox 4.10 was wrong and less desire-able. Disregard my remarks on the documentation along with issue 1 :).

I'm positive that tile LOD will be the curator of issue 2 (performance).

With that I have nothing more to add, but perhaps an idea for a convenience function in the future that works much like the old wrong behaviour in version 4.10? This may sounds strange at first but here is why I found the wrong behaviour useful: I have found myself wanting to center a point of the map in the top/bottom half of the screen -> but give the camera a pitch and distance relative to the device screen center, not the MapView/ViewPort center. For example inside a navigation. A Mapbox user might want the location of the user at the bottom, but have the camera at a 60 degree pitch at the center with a given distance. That is where I would make use of the wrong edgePadding or contentInset back in MapBox 4.10 and lower.

astojilj commented 5 years ago

@JustinGanzer

4.10 implementation was internally using API under MGLMapView's convertPoint to calculate latlng of the new center (offset from current screen center) and then to set it. I think this could be done on your end using existing API. Should you have issues with it, feel free to contact.

JustinGanzer commented 5 years ago

@astojilj Thanks for you help. With it, it was easy enough to create a setCamera method that mimics the old behaviour. 👍

astojilj commented 4 years ago

We have LOD integrated but it is not yet used in tile calculation (with edge insets, limit should be lower than 60 at https://github.com/mapbox/mapbox-gl-native/blob/30b4e57828acc1e2304373f9c8e4caecc39a3803/src/mbgl/util/tile_cover.cpp#L166).