Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

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

Open
1ec5 opened this issue Jul 18, 2019 · 18 comments · Fixed by #15195
Open

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

1ec5 opened this issue Jul 18, 2019 · 18 comments · Fixed by #15195
Assignees
Labels
Core The cross-platform C++ core, aka mbgl navigation For the Mapbox Navigation SDK for Android or iOS or navigation use cases in general performance Speed, stability, CPU usage, memory usage, or power usage

Comments

@1ec5
Copy link
Contributor

1ec5 commented Jul 18, 2019

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 1ec5 added performance Speed, stability, CPU usage, memory usage, or power usage navigation For the Mapbox Navigation SDK for Android or iOS or navigation use cases in general Core The cross-platform C++ core, aka mbgl labels Jul 18, 2019
@1ec5
Copy link
Contributor Author

1ec5 commented Jul 18, 2019

There are two potential fixes for this issue:

@astojilj
Copy link
Contributor

There are two potential fixes for this issue:

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.

@chloekraw chloekraw removed the release blocker Blocks the next final release label Jul 19, 2019
@chloekraw
Copy link
Contributor

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
Copy link
Contributor Author

1ec5 commented Jul 19, 2019

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.

@chloekraw chloekraw added this to the release-queso milestone Jul 22, 2019
@astojilj
Copy link
Contributor

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 added a commit that referenced this issue Jul 23, 2019
…rix.

As we don't want to show horizon, there is a need to limit max pitch based on edge insets.
The limit shouldn't be that bad, as to e.g. have 60 deg pitch, top - bottom insets should be less than 0.732 of screen height.

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 23, 2019
TileCoverPitchedViewport modified to have pitch capped by targe top inset, returning 28098 tiles in zoom level 8.

TileCover.PitchOverAllowedByContentInsets test verifies pitch capping by large top inset. Expectation was calculated using previous tileCover algorithm implementation.

Related to: #15163
@astojilj
Copy link
Contributor

There are two potential fixes for this issue:

@1ec5 , to further clarify what I'm working on in #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 added a commit that referenced this issue Jul 24, 2019
TileCover: Replaced 4 scanSpans by 3. As the split lines (triangle, trapezoid, triangle) is horizontal, there is no need to handle duplicates.

Benchmarks (release build) on MacBookPro11,2 (Mid 2014) with Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz compared against src/mbgl/util/tile_cover.cpp from master and from the patch:

```
                            master  |   patch
                          ---------------------
TileCoverPitchedViewport    72000ns |  50300ns
TileCoverBounds              1620ns |   1400ns

```

TileCoverPitchedViewport modified to have pitch capped by targe top inset, returning 1124 tiles at zoom level 8.

TileCover.PitchOverAllowedByContentInsets test verifies pitch capping by large top inset. Expectation was calculated using previous tileCover algorithm implementation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 24, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 24, 2019
TileCover: Replaced 4 scanSpans by 3. As the split lines (triangle, trapezoid, triangle) is horizontal, there is no need to handle duplicates.

Benchmarks (release build) on MacBookPro11,2 (Mid 2014) with Intel(R) Core(TM) i7-4770HQ CPU @ 2.20GHz compared against src/mbgl/util/tile_cover.cpp from master and from the patch:

```
                            master  |   patch
                          ---------------------
TileCoverPitchedViewport    72000ns |  50300ns
TileCoverBounds              1620ns |   1400ns

```

TileCoverPitchedViewport modified to have pitch capped by targe top inset, returning 1124 tiles at zoom level 8.

TileCover.PitchOverAllowedByContentInsets test verifies pitch capping by large top inset. Expectation was calculated using previous tileCover algorithm implementation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 26, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 26, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Jul 27, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Aug 1, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
astojilj added a commit that referenced this issue Aug 1, 2019
…ProjMatrix.

Patch partly fixes #15163 in a way that it doesn't allow loading tens of thousands of tiles and attempt to show area above horizon:

Limit pitch based on edge insets. It is not too bad - current limit of 60 degrees stays active until center of perspective is moved towards the bottom, to 84% of screen height. The plan is to split removal of 60 degrees limit to follow up patch.

Fix max Z calculation in getProjMatrix. TransformState::getProjMatrix calculation of farZ was complex with possibility to lead to negative z values. Replacing it with simpler, precise calculation:
furthestDistance = cameraToCenterDistance / (1 - tanFovAboveCenter * std::tan(getPitch()));

TransformState::getProjMatrix calculation of farZ was an aproximation. Replacing it with simpler, but precise calculation.

Related to: #15163
@astojilj
Copy link
Contributor

astojilj commented Aug 6, 2019

This is not fixed yet - work is ongoing in #15230

@JustinGanzer
Copy link

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
Copy link
Contributor

@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
Copy link

@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

With Inset:
loc2_YesInset

Images with Mapbox 5.5

Without Inset:
loc2_NoInset

With Inset:
loc2_YesInset

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

5.5 with insets
5_5_Performance

@JustinGanzer
Copy link

JustinGanzer commented Nov 12, 2019

@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
Copy link
Contributor

@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?

#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
Copy link

@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
Copy link
Contributor

astojilj commented Nov 15, 2019

@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 mapbox/mapbox-gl-js#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

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 mapbox/mapbox-gl-js#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
Copy link

JustinGanzer commented Nov 15, 2019

@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
Copy link
Contributor

@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
Copy link

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

@astojilj
Copy link
Contributor

astojilj commented Jun 6, 2020

We have LOD integrated but it is not yet used in tile calculation (with edge insets, limit should be lower than 60 at

const uint8_t minZoom = state.getPitch() <= (60.0 / 180.0) * M_PI ? z : 0;
).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Core The cross-platform C++ core, aka mbgl navigation For the Mapbox Navigation SDK for Android or iOS or navigation use cases in general performance Speed, stability, CPU usage, memory usage, or power usage
Projects
None yet
4 participants