Mapsui / Mapsui

Mapsui is a .NET Map component for: MAUI, WPF, Avalonia, Uno, Blazor, WinUI, Xamarin and Eto
https://mapsui.com
MIT License
1.24k stars 319 forks source link

Blank Screen with 2.0 (iOS / Forms) #570

Closed Sebastian1989101 closed 5 years ago

Sebastian1989101 commented 5 years ago

After testing a bit with the latest beta, I encounter a problem which I was able to reproduce with the sample project as well. The map results with a blank screen if the MapControl is directly used in XAML. This happens with the specific Forms Control as well with my old wrapper of the native Mapsui Map (the one I used prior to 2.0 for a Forms "implementation"). When I use the "MapView" control instead of the "MapControl" itself, I get the buttons at the top right corner but still a blank screen otherwise.

All messages I got from the output are this one (which repeats itself multiple times):

Failed to bind EAGLDrawable: <CAEAGLLayer: 0x600001a717a0> to GL_RENDERBUFFER 1
Failed to make complete multisample framebuffer object 8cd6
Failed to make complete framebuffer object 8cd6
Could not invert matrix

The same code seems to work on Android.

pauldendulk commented 5 years ago

Thanks for reporting.

Sebastian1989101 commented 5 years ago

It's not 100% the same thing happening (because for some reason the screen flashes to blue and the output in the log is missing) but here is a small example repo: https://cloud.kruse-familie.eu/index.php/s/VLfZGijNojSNC2v

I hope I didn't cut too much from the original app so this produces really the same error but we will see.

pauldendulk commented 5 years ago

Another update from Sebastian I got through IM

it seems like this happens if the map gets it tiles source later via binding instead of having it while the InitializeComponent() is called. If I build the map in code before InitializeComponent() it works for iOS too but if I use binding or build the control after the call it only works on Android. Should I add this info in the issue on github as well?

pauldendulk commented 5 years ago

So this problem should occur if you set the source of a tile layer. If a TileLayer.TileSource is set this code is called:

        private void SetTileSource(ITileSource tileSource)
        {
            _fetchMachine.Stop();
            MemoryCache.Clear();
            _tileFetchDispatcher.TileSource = tileSource;
            _tileSource = tileSource;

            if (_tileSource != null)
            {
                if (Attribution == null) Attribution = new Hyperlink();
                Attribution.Text = _tileSource.Attribution?.Text;
                Attribution.Url = _tileSource.Attribution?.Url;
            }

            OnPropertyChanged(nameof(Layer.DataSource)); // To trigger new RefreshData.
            OnPropertyChanged(nameof(Envelope));
        }

So a 'DataSource' property change is triggered. This event bubbles up (or does it trickle down?) through the LayerCollection and Map to the MapControl. There is should cause a RefreshData, judging from this code:

        private void MapPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(Layers.Layer.Enabled))
            {
                RefreshGraphics();
            }
            else if (e.PropertyName == nameof(Layers.Layer.Opacity))
            {
                RefreshGraphics();
            }
            else if (e.PropertyName == nameof(Map.BackColor))
            {
                RefreshGraphics();
            }
            else if (e.PropertyName == nameof(Layers.Layer.DataSource))
            {
                RefreshData(); // There is a new datasource so let's fetch the new data.
            }
            else if (e.PropertyName == nameof(Map.Layers))
            {
                Refresh();
            }
        }

By looking at the code I would say this should work correctly, but it is not hard to image I missed something.

charlenni commented 5 years ago

It's a refresh problem. When panning the map, than it appears. It is the same as #573.

charlenni commented 5 years ago

Tried to investigate this.

The problem is, that at start the extent, that is for RefreshData() is used, is {-160.0,-284.0 160.0,284.0}. This is the screen width and high. This is a valid area, but is very tiny on a map that has a coordinate system of {-371789.205579093,-708112.630033864 410925.964061103,681206.796077483}. So you see only a small part of your map, which is blue. It seems, that the map is blank. If you than pan the map, the extent is corrected and a bigger part of the map is displayed. It is corrected by the ViewportLimiter, which set the resolution to the minimum resolution. So the resolution changes from 1 to 2445 and than you see a bigger part of the map.

So the question is: How and when to determin the correct resolution? When a first layer is added to Layers?

charlenni commented 5 years ago

I'm not sure, what we should do in this case.

@Sebastian1989101 If you add a

mapsuiMap.Navigator.ZoomTo(mapResolutions.Last<double>());

to the function OnTilesDatabaseChanged(), than you could see an image like you expect.

Sebastian1989101 commented 5 years ago

@charlenni So there is a mistake in the mbtiles file then? Or what exactly defines the start extent? Because depending on the mbtiles file it should be -180,-85,180,85 - which is wrong too in this case but a different value compared to your result.

But why did it work when I change the code of MainPage.xaml.cs to this?

using Xamarin.Forms;

namespace Mapsui_2._0_BlankScreen
{
    public partial class MainPage : ContentPage
    {
        public string Database { get; }

        public MainPage(string database)
        {
            BindingContext = this;

            Database = database;
            OnPropertyChanged(nameof(Database));

            InitializeComponent();
        }
    }
}

The only difference is the movement of InitializeComponent().

But in this case the example was not exactly what it should be.. and I was already wondering why it went blue while it stays black in my original project.. I just tested it in my original project with panning and it won't work. Does Mapsui maybe require to get the TileSource set from a specific thread like the UI thread?

charlenni commented 5 years ago

@Sebastian1989101 The extent is the extent of the screen (iPhone SE simulator with 320 x 568 and a resolution of 1). This are the start values of for viewport. Than, when you add a layer, th Limiter calculates a new extent for all layers. With this, the extent is resized to the values belonging to the layers, in this case only one.

If you initialize the component later, the extent from your layer is known and used. Because of this, your map is shown correct.

If you could provide an example with a black map, than I could try to test this.

pauldendulk commented 5 years ago

I can reproduce the problem with your sample. Thanks a lot for that.

Updating all mac os stuff did not take an entire day this time. :)

Newby question. When I try to run the Mapsui iOS sample on my ipad I get

Could not find any available provisioning profiles for iOS. Mapsui.Samples.iOS

.. even though it worked properly the last time I ran it (months ago). What happened? Is there something expiring? Or is it because of the updates?

I resolved it by creating a new app in xcode and run it from xcode on my ipad. That always succeeds. Then I paste that app's bundle identifier in my app and then it runs properly.

pauldendulk commented 5 years ago

I could step through the code. What i see is that the width and height are set before the binding of tilelayer. When the width and height are set there is an attempt to initialize the map but the since the layer is not set the envelope is null, so that fails.

To resolve this you could call NavigateToFullEnvelope in the binding method.

        private static void OnTilesDatabaseChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            if (bindable is MapsuiMap mapsuiMap)
            {
                var tileLayer = new TileLayer(new MbTilesTileSource(new SQLiteConnectionString(newvalue.ToString(), true)));
                tileLayer.Attribution = null;
                tileLayer.MemoryCache.MaxTiles = 128;
                tileLayer.MemoryCache.MinTiles = 64;

                mapsuiMap.Map.Layers.Add(tileLayer);

                mapsuiMap.Navigator.NavigateToFullEnvelope();

                mapsuiMap.SingleTap += (sender, args) => { System.Diagnostics.Debug.WriteLine(args.ScreenPosition); };
            }
        }

To make this easier for users we could keep track of a 'initialized' status and try to initialize on the relevant events. This is when the size of the control is set and when the Envelope changes.

Sebastian1989101 commented 5 years ago

If you have problems with the provisioning profiles your certificates are outdated for sure (they only last 1 year and Xcode recreate them if you create a project there / thats why it works afterwards). You can see your certificates and profiles here: https://developer.apple.com/ - if you don't have an Account there, you only have the option to create them with new Xcode projects and run them once.

About the problem: If I use the NavigateToFullEnvelope(), the map still stays black on my project. Also it blocks the NavigateTo(Point, double) call in the next line (or do I need the awaiting again because the map is not fully initialized?). I have updated the sample project which now actual produces the real black screen issue. I still think it might be a thread problem but even if I sent it to the UI Thread this wont work: https://cloud.kruse-familie.eu/index.php/s/35hgDowVqWXVgzV

The only thing different, is that the mbtiles file is loaded from an On-Demand Resource and when the download is finished, the navigation to the map page is triggered.

charlenni commented 5 years ago

@Sebastian1989101 I have problems running your project. I get an NSCocoaErrorDomain error 4994 'InvalideTag' (The requested application data doesn’t exist.) in StartDownloadAsync.

pauldendulk commented 5 years ago

@Sebastian1989101 I can run the app. I press 'load map' and see a black screen. Panning and zooming has no effect. Is this what you see?

Sebastian1989101 commented 5 years ago

@charlenni Is the resource correctly flagged with the On-Demand Resource Tag (the my.mbtiles file should have a tag).

@pauldendulk yes thats exactly what I see. Maybe there is something wrong with the observer call which than leads to the navigation to the map.

pauldendulk commented 5 years ago

The problem seems to be with the hardware accelerated MapControl. I see this error in the log 'Failed to bind EAGLDrawable'. Perhaps we need to wait for the GL view to initialize. When I derive the MapControl from SKCanvasView I get the extent below.

image

charlenni commented 5 years ago

See this question for the same problem. A related issue is here.

charlenni commented 5 years ago

@Sebastian1989101 Yes, the On Demand Resource Tags are set.

grafik

pauldendulk commented 5 years ago

Perhaps we could create a minimal sample to reproduce the bug without Mapsui.

Things that could be related:

pauldendulk commented 5 years ago

@charlenni Yes, and there is also this one using Swift and a GL view. Related to initialization and was solved: https://stackoverflow.com/questions/38343952/failed-to-bind-eagldrawable-ios-swift?noredirect=1&lq=1

charlenni commented 5 years ago

Ok, could now see the black screen.

@Sebastian1989101 Found this article, where I read, that this problem could be device and build dependent. So I tried to use my real device, and there it "works".

SebastianKruseWago commented 5 years ago

@charlenni Good to know. That might explain some issues I had with ODR on other apps. As far as my investigation goes, it might really be a problem with the thread from the callback of the ODR download. Or maybe a timing issue?

Edit: Woops wrong github account

charlenni commented 5 years ago

@Sebastian1989101 Nice to know, what you doing the rest of the day :)

Ok, after I could reproduce the problem, I found the cause of it. We call InvalidateSurface() in RefreshGraphics(). The problem is, that this is called, even if the SKGLView has no GrContext. If this is the case, the SKGLView crashes. So the workaround is, that we check, if the GrContext is not null. Only in this cases, we should call InvalidateSurface(). If I do this, all works correct.

Further I assume, that this is a SkiaSharp problem. You could read my suggestion at mono/SkiaSharp #646.

pauldendulk commented 5 years ago

Could you test this with 2.0.0-beta.14?

Sebastian1989101 commented 5 years ago

@pauldendulk Seems to work now. I will put up a new TestFlight version to test it with the team.