ThinkGeo.com    |     Documentation    |     Premium Support

V14.2 - WmsLayer and V10.6 WmsServer Issue

ThinkGeo Team,

I’m in the process of upgrading applications to V14.2 and have discovered an issue with ThinkGeo.Core.Async.WmsLayer receiving imagery from our V10.6 WmsServer. Recall that our WmsServer acts as a proxy requesting imagery from NearMap. This has worked without issue for all MapSuite & ThinkGeo versions except for V14.

I have a testbed/development application that allows imagery request direct to NearMap or via our WmsServer, which makes the request to NearMap. When making the request directly to NearMap it all works just fine. However, if the application makes the request via our local WmsServer it fails.

Instead of the actual imagery being displayed an image of a C# Exception Message is displayed. Refer to screen-captures below. Our WmsServer caches images received from NearMap. When the application, employing V14.2 is used, our WmsServer successfully requests, receives, and caches the NearMap imagery. I can then use the application that employs ThinkGeo V13.3 and the imagery from cache is displayed very quickly. This tells me that the original request and cache from the V14.2 application is working. But the return/display of that imagery to the client is not.

Upon receipt by the V14.2 application the RequestedImageException event is triggered. The ReceivedHttpResponse event is also triggered with a Status of OK. In the exception event the application does a Request.Save to save the image to a file. That image is the same image that appears on the display. The two exceptions being displayed are:
1. (Parameter ‘codec’)
Object Reference not set to an instance of an object

Unfortunately, RequestedImageExceptionEventArgs contains only the Exception Message property. It does not contain the Exception itself. Which means there is no hint as to the code that encountered the exception. I’m not seeing any exceptions from the WmsServer ASP.NET application.

Going back to WmsServer caching: When the application, employing V13.3 is used, our WmsServer successfully requests, receives, caches, and displays the NearMap imagery. When the V14.2 application is then used to display the same extent of imagery I can tell that WmsServer retrieves the imagery from the cache because the exceptions being rendered appear very quick.

The application sets the WmsLayer DrawingExceptionMode = ThrowException. However, neither DrawnException nor DrawingException is triggered.

Your assistance is appreciated.
Dennis

hi @Dennis,

It’s very important for us to have a sample to reproduce the issue. I’ve uploaded a sample, could you please update it and send it back to us?

WmsSample.zip (7.2 KB)

Regards,
Leo

Greetings Leo,

In my post I pointed out that the RequestedImageException event is being trigged, but unfortunately, RequestedImageExceptionEventArgs contains only the Exception Message property.

I believe that the exception is occurring in the V14.2 client application, as opposed to the WmsServer. I would suggest that this event be modified such that the entire Exception be returned as part of the EventArgs. This would then give a more detailed description of where & what is happening.

Other ThinkGeo events such as DrawingException & DrawnException do include Exception as part of the EventArgs.

Your thoughts?

Regards,
Dennis

hi @Dennis,

Thanks for your suggestion. I’ve added a new property “Exception” to RequestedImageExceptionEventArgs, you could upgrade ThinkGeo.Core to 14.2.0-beta031 and give it a try.

Regards,
Leo

hi Leo,

Thanks for adding the full Exception as it does shed some light on the issue. Below are traces of the two exceptions that are occurring. The exceptions occur after the ReceivedHttpResponse event is triggered. The exceptions occur in SkiaSharp after being called by ThinkGeo api’s.

Digging further into the Exception I also discovered the following:

TheEventArgsException.TargetSite.DeclaringType.AssemblyQualifiedName–>>
SkiaSharp.SKBitmap, SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756

My application has SkiaSharp.dll V2.88.8 in the .\Debug\net8.0-windows directory. SkiaSharp is not specified as a Dependency in the project.

Does the issue revolve around SkiaSharp V2.88.0 vs V2.88.8?

Regards,
Dennis

2024-08-12 13:24:14,878 FINEST OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_ReceivedHttpResponse:
Received HttpResponse

2024-08-12 13:25:00,991 ERROR OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_RequestedImageException:
Unable to Draw WMS Layer for NearMap Due To:
Message=Object reference not set to an instance of an object.
Result=ThinkGeo.Core.GeoImage
Height=512, Width=512
Source=ThinkGeo.Core
TargetSite=Void MoveNext()
StackTrace= at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers) Message=Object reference not set to an instance of an object. System.NullReferenceException: Object reference not set to an instance of an object. at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)

2024-08-12 13:25:01,009 ERROR OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_RequestedImageException:
Unable to Draw WMS Layer for NearMap Due To:

Message=Value cannot be null. (Parameter ‘codec’)

Result=ThinkGeo.Core.GeoImage
Height=512, Width=512

Source=SkiaSharp
TargetSite=SkiaSharp.SKBitmap Decode(SkiaSharp.SKCodec)

StackTrace= at SkiaSharp.SKBitmap.Decode(SKCodec codec)
at SkiaSharp.SKBitmap.Decode(ReadOnlySpan1 buffer) at SkiaSharp.SKBitmap.Decode(Byte[] buffer) at GRU=.QRU=.QhU=(Byte[] bytes) at ThinkGeo.Core.GeoImage..ctor(Byte[] bytes) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)
Message=Value cannot be null. (Parameter ‘codec’)
System.ArgumentNullException: Value cannot be null. (Parameter ‘codec’)
at SkiaSharp.SKBitmap.Decode(SKCodec codec)
at SkiaSharp.SKBitmap.Decode(ReadOnlySpan1 buffer) at SkiaSharp.SKBitmap.Decode(Byte[] buffer) at GRU=.QRU=.QhU=(Byte[] bytes) at ThinkGeo.Core.GeoImage..ctor(Byte[] bytes) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)

2024-08-12 13:35:09,975 FINEST OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_ReceivedHttpResponse:
Received HttpResponse

2024-08-12 13:35:09,977 ERROR OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_RequestedImageException:
Unable to Draw WMS Layer for NearMap Due To:
Message=System.NullReferenceException: Object reference not set to an instance of an object.
at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)

Result=ThinkGeo.Core.GeoImage
Height=512, Width=512

Source=ThinkGeo.Core

TargetSite=Void MoveNext()

StackTrace= at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers) Message=Object reference not set to an instance of an object. System.NullReferenceException: Object reference not set to an instance of an object. at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)

TheEventArgsException.Message–>>
Value cannot be null. (Parameter ‘codec’)

TheEventArgsException.TargetSite.DeclaringType.AssemblyQualifiedName–>>
SkiaSharp.SKBitmap, SkiaSharp, Version=2.88.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756

TheEventArgsException.StackTrace–>>
at SkiaSharp.SKBitmap.Decode(SKCodec codec)
at SkiaSharp.SKBitmap.Decode(ReadOnlySpan`1 buffer)
at SkiaSharp.SKBitmap.Decode(Byte[] buffer)
at GRU=.QRU=.QhU=(Byte[] bytes)
at ThinkGeo.Core.GeoImage…ctor(Byte[] bytes)
at ThinkGeo.Core.Async.WmsLayer.G0E=.MoveNext()

TheEventArgsException.SerializationStackTraceString–>>
at SkiaSharp.SKBitmap.Decode(SKCodec codec)
at SkiaSharp.SKBitmap.Decode(ReadOnlySpan1 buffer) at SkiaSharp.SKBitmap.Decode(Byte[] buffer) at GRU=.QRU=.QhU=(Byte[] bytes) at ThinkGeo.Core.GeoImage..ctor(Byte[] bytes) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)

hi Leo,

I’ve discovered that my await’s are not waiting for completion. When I step thru the debugger and press F10 to execute the await wmsLayer.OpenAsync() statement the remainder of the code in the containing function is not executed and instead control immediately returns to the calling function.

This is not the kind of flow that my application is designed for. The code needs to wait until the Open is complete and then to act on the layer.

How does one implement this synchronously as it was prior to V14.2?

Thanks,
Dennis

Hi Leo,

With a bit of research and past experience, I came up with the following to run synchronously:

            var task = Task.Run(async () => { await TheMapLayerItem.TheWmsLayerCapabilities.OpenAsync(); });
            task.Wait();

In one instance where I made this change my application works as expected. Since it is late for me, I will continue applying this paradigm in the morning to the remaining OpenAsync invocations.

Thanks,
Dennis

hi @Dennis,

I believe the issue may not be related to the SkiaSharp version. I have some doubts about whether there might be a problem with the HTTP response. Could you try printing out the image URL by attaching to the SendingHttpRequest event, and then access the URL using Chrome or Edge to verify if it returns the correct image?

Regarding the await issue, if OpenAsync is called within a method that is not marked as async, it might not behave as expected. Please ensure that the calling method is marked as async and properly uses await.

Thanks,
Leo

Leo,

There is no problem with the HTTP Response. I only included it for completeness.

I can indeed place the request in the Edge Browser and the requested image is downloaded and displayed in the browser.

The OpenAsync is indeed within a method marked as async–>>

public static async Task LoadLayer(string TheProvider)
await TheWmsLayer.OpenAsync());

I modified the WmsSample you provided by adding code to request the WmsLayer from my WmsServer with same results. The RequestedImageException Event is throwing the same exception as my application. I emailed ThinkGeo support a zip file containing the project.

Recall that my WmsServer acts as a proxy server. When a request is made to my WmsServer it in turns makes a request to the NearMap server.

What I believe the issue to be is that the OpenAsync from the client application returns once the layer on the WmsServer is opened, but before the layer from NearMap is opened. That make sense?

This issue all revolves around this new async design. It is a synchronization issue where execution is continuing prior to everything being returned from my WmsServer.

What determines when the OpenAsync returns? What’s the trigger that says it has done its’ job and can now return to the calling method?

Regards,
Dennis

This is the exception from the modified WmsSample.
TheWmsLayer_RequestedImageException–>>Exception–>>
Unable to Draw WMS Layer for NearMap Due To:
Message=System.NullReferenceException: Object reference not set to an instance of an object.
at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)
Result=ThinkGeo.Core.GeoImage
Height=512, Width=512

TheEventArgs.Exception.StackTrace
at ThinkGeo.Core.Async.WmsLayer.F0E=.MoveNext()
at ThinkGeo.Core.Async.WmsLayer.G0E=.MoveNext()

Some screen-captures of the exception.

hi @Dennis,

Thanks for your sample, I can easily reproduce the issue.

The exception “Value cannot be null. (Parameter ‘codec’)” was caused by task cancellation, which was not handled properly on our side. I’ve already fixed it in ThinkGeo.Core 14.2.0-beta032.

Additionally, I noticed a potential issue in your code: calling WpfMap.CenterAtAsync , WpfMap.ZoomToAsync , and WpfMap.RefreshAsync in sequence would lead to task cancellation. I recommend using only the WpfMap.ZoomToAsync method to avoid this problem.

Regards,
Leo

hi Leo,

Thanks for the update.

Updated to the latest versions. I’m no longer seeing the (Parameter ‘codec’) exception. However, still getting the ‘object reference…’ exception as shown below.

Per your recommendation I removed all but the ZoomToAsync.

Regards,
Dennis

image

2024-08-15 08:45:12,247 ERROR OriStarWmsClient - OriStarWmsClient->TheWmsProviderOriStar.TheWmsLayer_RequestedImageException:
Unable to Draw WMS Layer for NearMap Due To:
Message=System.NullReferenceException: Object reference not set to an instance of an object.
at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers) Result=ThinkGeo.Core.GeoImage Height=1024, Width=1024 Source=ThinkGeo.Core TargetSite=Void MoveNext() StackTrace= at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken) at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection1 labelsInAllLayers)
Message=Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object.
at ThinkGeo.Core.Async.WmsLayer.DownloadImageAsyncCore(Double width, Double height, Double scaleFactor, RectangleShape extent, GeographyUnit mapUnit, CancellationToken cancellationToken)
at ThinkGeo.Core.Async.WmsLayer.DrawAsyncCore(GeoCanvas canvas, Collection`1 labelsInAllLayers)

hi @Dennis,

I cannot reproduce the issue with ThinkGeo.Core 14.2.0-beta032, see my gif below.

Could you let me know how to reproduce it?

Regards,
Leo

Leo,

These issues do not occur when going directly to either NearMap or British Geological Survey.

This only occurs when the client makes a request to my proxy WmsServer, which then forwards the request to NearMap.

I can always recreate this on my development machine.

For you to recreate you’ll have to setup a MapSuite WmsServer to act as a proxy.

From my prior post this is what I suspect happens:
What I believe the issue to be is that the OpenAsync from the client application returns once the layer on the WmsServer is opened, but before the layer from NearMap is opened. That make sense?

I am certain this is not an issue within my application.

Your thoughts?

Thanks,
Dennis

Hi Dennis,

It doesn’t have issue when going directly to NearMap, but does have the issue when hitting the proxy. It is a red flag on your proxy server, right?

From what you said:
What I believe the issue to be is that the OpenAsync from the client application returns once the layer on the WmsServer is opened, but before the layer from NearMap is opened. That make sense?

Are you suggesting every time the desktop client calls layer.OpanAsync(), the proxy server will then initialize the NearMap on the server side? WmsLayer.OpenAsync() method fetches the capabilities which could be cached(so no request will be sent), are you sure it will send the request to your proxy server anyway? If yes make sure the NearMap on the server side is opened before returning.

Send us the wms server project if you still see issues.

Thanks,
Ben

hi Ben,

Thanks for getting back to me.

To answer your questions:

  1. It doesn’t have issue when going directly to NearMap, but does have the issue when hitting the proxy – Correct.

  2. It is a red flag on your proxy server, right? – I don’t yet see how it is proxy server.

  3. Are you suggesting every time the desktop client calls layer.OpenAsync(), the proxy server will then initialize the NearMap on the server side? – I’ll have to dive back into the code to verify this, but if I remember correctly once a NearMap Layer is opened it remains opened.

  4. WmsLayer.OpenAsync() method fetches the capabilities which could be cached so no request will be sent), are you sure it will send the request to your proxy server anyway? – I’ll verify, but remember correctly a request is sent to the proxy every time a layer is opened.

I’m certainly willing to send you the proxy server Visual Studio projects.

The thing that bothers me is this all works just fine up to and including ThinkGeo V13.3.

Doing the async really changes the ballgame. And I’m only working on the WMS layers now in an isolated fashion. I’m not sure what is going to happen for Shape, ESRI GeoDataBase, SQLite, Memory Files, and MrSid.

Thanks,
Dennis

Hi Dennis,

WmsLayer is within async namespace in which all the classes fetch the data from the server using .net async methods, while all the other layers you mentioned like MrSid keep the old way, so I don’t think you will meet the same issue with other layers.

In fact, I don’t think thats an issue from the async itself, it could be the way we send the request was not accepted by your current v10 WmsServer. So please make sure the request was sent from the client as expected and was accepted on the server side successfully.

Thanks,
Ben

hi Ben,

Thank you for the reassurance that only WmsLayer operates as async.

In case you missed these details in my original post I repost (modified) here, which addresses your second paragraph:

When the application, employing V14.2 is used, our WmsServer successfully receives the client requests, forwards the requests on to NearMap, receives the NearMap responses, caches the NearMap images, and returns to the client. There are eight requests for one extent change of 512x512.

Upon receipt by the V14.2 application the ReceivedHttpResponse events are triggered with a Status of OK. The RequestedImageException events are also triggered with the images containing “Object Reference not set to an instance of an object”. Refer to my previous posts for the details of the .NET Exceptions.

If I then use the application that employs ThinkGeo V13.3 the imagery from cache is displayed very quickly. This tells me that the original request and cache from the V14.2 application is working. But the return/display of that imagery to the V14.2 client is not. I also verified the caching of the images by first deleting the cache and reissuing the request and then seeing the same images re-cached.

Hope this sheds some light on where the issue might be located, which I believe is somewhere in the OpenAsync logic of the client.

Regards,
Dennis

Hi Dennis,

Thanks for the explanation. I got a much better understanding, so:

  1. v13 client works fine with the WMS Server but the v14 client doesn’t.
  2. The v14 client works if go straight to NearMap, but not working if to the proxy server.
  3. It looks the server side accepts the request and generates the tiles successfully; it should be some issues when retuning/display the images.

I see the exception comes from DownloadImageAsync, Can you create a custom layer like following, and use it in your v14 client(Latest beta is needed)? you’ll then see where the exception is located. I guess it’s because the content type of the WMS response doesn’t start with “image” but let’s make sure.

class MyWmsLayer : WmsLayer
{
    protected override async Task<byte[]> DownloadImageAsyncCore(double width, double height, double scaleFactor, RectangleShape extent,
        GeographyUnit mapUnit, CancellationToken cancellationToken)
    {
        var worldExtent = extent;
        var canvasWidth = (int)width;
        var canvasHeight = (int)height;
        ValidatorHelper.CheckRasterSourceIsOpen(IsOpen);
        ValidatorHelper.CheckObjectIsNotNull(worldExtent, "worldExtent");
        ValidatorHelper.CheckShapeIsValid(worldExtent, "worldExtent");
        ValidatorHelper.CheckInputValueIsLargerThan(canvasWidth, "canvasWidth", 0, RangeCheckingInclusion.ExcludeValue);
        ValidatorHelper.CheckInputValueIsLargerThan(canvasHeight, "canvasHeight", 0, RangeCheckingInclusion.ExcludeValue);
        //if (!this._fastMode)
        //{
        //    ValidatorHelper.CheckImageFormatIsValid(OutputFormat, _serverOutputFormats, Resource.WmsServerNotSupportFormat + _uri);
        //}
       
        //_width = canvasWidth;
        //_height = canvasHeight;
        //_currentExtent = worldExtent;

        //string httpMethod = _fastMode ? HttpMethod.Get.Method : GetPreferredMethod().Type;

        string requestMapUrl = GetRequestUrl(worldExtent, canvasWidth, canvasHeight);
        Uri requestMapUri = new Uri(requestMapUrl);

        var myWebRequest = new HttpRequestMessage(HttpMethod.Get, requestMapUri);
        //SetUserAgent(myWebRequest);
        //myWebRequest.Method = httpMethod == HttpMethod.Post.Method ? HttpMethod.Post : HttpMethod.Get;

        //myWebResponse = (HttpWebResponse)SendWebRequest(myWebRequest);
        using (var myWebResponse = await SendWebRequestAsync(myWebRequest, cancellationToken))
        {
            if (cancellationToken.IsCancellationRequested)
                return null;
            if (myWebResponse == null)
                return null;
            if (myWebResponse.Content.Headers.ContentType.ToString()
                .StartsWith("image", StringComparison.OrdinalIgnoreCase))
            {
                var temp = await myWebResponse.Content.ReadAsStreamAsync();
                var memoryStream = new MemoryStream();
                await temp.CopyToAsync(memoryStream, 81920, cancellationToken); //80k is the default buffer
                return memoryStream.ToArray();
            }
            if (myWebResponse.Content.Headers.ContentType.ToString().ToUpperInvariant().Contains("XML"))
            {
                var temp = await myWebResponse.Content.ReadAsStreamAsync();
                XmlTextReader xmlTextReader = new XmlTextReader(temp);
                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(xmlTextReader);

                //string errorMsg = GetFormattedErrorMessage(GetErrorMessage(xmlDocument), canvasWidth);
                //throw new Exception(errorMsg);
            }
            return null;
        }
    }
}

Thanks,
Ben

hi Ben,

You were spot-on in your suspicion as to the issue!!!

I applied your code to both the WmsSample and to my client application.

The value of myWebResponse.Content.Headers.ContentType is null when the request is made to my WmsServer. It is image/png when the request is made to British GeoSurvey.

To prove a point, I modified your code to first check if ContentType is null. If so, then the code just assumes it is an image. The NearMap imagery is then rendered successfully.

In the debugging screen-captures below you’ll see that ContentType has an InvalidValue of image/image/png when the request is made to my WmsServer. I’m thinking that WmsServer sets the header to image/image/png which is not being recognized at the client.

What do you think is the real solution?

Thanks,
Dennis

Using my WmsServer to request NearMap:

When going direct to British GeoSurvey:
TheContentHeaders-Value-12-BritishGeo

Hi Dennis,

All right. So it seems it’s because the WMS Server doesn’t set the ContentType correctly. Can you review your server side code see if anything you can change? I cannot tell you how to do it without seeing your code.

Thanks,
Ben