ThinkGeo.com    |     Documentation    |     Premium Support

Online maps and tile cache

Hi,

We are using DrawingExceptionMode.DrawException with our custom DrawingException method with online maps. Custom exception is correctly drawn if there is problems with network connection.

But problem is that drawn exceptions are also stored to tile cache for that online map. This causes that only way to get map to work correctly after network issue is to clear cache so that tiles are downloaded again (otherwise these exceptions are shown from cache).

I think that in case of exceptions tiles should not be stored to cache to prevent this kind of issue. Is there anyway to achieve this kind of funtionality ?

Br, Simo

Hi Simo,

You can throw the exception in your CustomLayer.DrawExceptionCore method

    protected override void DrawExceptionCore(GeoCanvas canvas, Exception e)
    {
       base.DrawExceptionCore(canvas, e);
       throw e;
    }

In this way, after drawing the exception on the canvas, it throws the exception that can be caught by the TileView, which therefore cancels the caching.

DrawingExceptionMode now has ThrowException and DrawException, what you need though is “DrawExceptionAndThenThrowException”. Let us think about it whether it’s worth it to add this new mode…

Thanks,
Ben

Hi,

Thanks, throwing the exception from custom method like this seems to solve that issue with cache:

    overlay.DrawingException += DrawCustomException;

    public static void DrawCustomException(object sender, DrawingExceptionTileOverlayEventArgs e)
    {
         ...Draw custom exception...
         throw e.Exception
    }

I guess this does the same as your example ? Is it now so that TileView (or some other place) handles this exception and does not throw it again in any case (just wan’t to make sure that this would not cause crash in any case) ?

This issue happens also when DrawException is used without custom drawing. I guess that there is no such use case where those drawn exceptions would needed to be cached so it would make sense you to internally cancel caching when DrawException is used.

Br, Simo

Hi Simo,

Here below are something we did to solve the issue while avoiding breaking changes. They are available in the latest beta113.

  1. Added DrawingExceptionMode.DrawAndThrowException:
    This new mode is now the default. It indicates that exceptions will be both drawn on the canvas and rethrown, allowing users to catch them if needed.

  2. Deprecated Overlay.DrawingExceptionMode:
    While you can still set the DrawingExceptionMode for individual internal layers (if available), this setting is no longer supported at the Overlay level.

  3. Added Overlay.ThrowingExceptionMode { SuppressException, ThrowException (Default) }:

  • ThrowException: The overlay raises the Overlay.ThrowingException event and then rethrows the exception. This rethrowing can be canceled in the event
  • SuppressException: The overlay does not throw an exception, and no event is raised.

Now the default mode DrawAndThrowException propagates the exception to the overlay, which will stop doing the caching when catching any exceptions from the layer.

Please have a try. We will update the HandleExceptions sample in the HowDoI accordingly.

Thanks,
Ben

Hi,

Your new way to handle exceptions sound good but we need to solve this issue using 14.2.2.

So could you please check if my example was correct way to go and does not cause exception to be thrown in any case if it is re-thrown from custom exception drawing ?

Br, Simo

Hi Simo,

  1. Yes, your code (rethrowing the exception in overlay.DrawingException event) is similar to my code (rethrowing the exception in DrawExceptionCore), the exception will be swallowed if your overlay.DrawingExceptionMode is set to DrawException, or e.Handled is set to true in Overlay.ThrowingException event. Just feel free to rethrow it.

  2. Instead of overlay.DrawingException, I’d suggest using OverlayInternalLayer.DrawingException instead. The code would be clearer as the exception in fact comes from the layer, not the overlay, and it makes more sense for a layer to handle the drawing. We will consider obsoleting overlay.DrawingException event in the future versions. Again, your current code works fine with v14.2.2 and will work fine even after we obsolete that event.

Thanks,
Ben

Hi,

Thanks, I think that we will use my example now with 14.2.2 since it works and then re-factor implementation to use new way to handle exceptions introduced on beta113 once we upgrade to newer release at some point.

Br, Simo

Simo,

That sounds good.

The changes introduced in beta113 will be available in v14.3. I’ve outlined some of the major changes in v14.3 in this post: Breaking changes – ThinkGeo UI for Desktop. We will also create an official blog post for the release, but feel free to take a quick look if you’re interested for now.

Thanks,
Ben

Hi,

We have been using this overlay.DrawingException way to draw custom exceptions to map for a while. It seems that this works fine with WmtsOverlay but not with WmsOverlay. With WmsOverlay method for drawing custom exceptions is not ever called and default exceptions are drawn instead.

Could you please check why it does not work with WmsOverlay (ThinkGeo version 14.2.2) ?

Br, Simo

Hi Simo,

Our HowDoI sample is using WmsAsyncLayer + LayerOverlay, it works as expected.

I tested with WmsOverlay and you are right that wmsOverlay.DrawingException event is not raised. It’s because the internal WmsAsyncLayer.DrawingException is not set to Drawing and in v14.2.2, we don’t have access to the internal WmsAsyncLayer.

So can you use LayerOverlay + WmsAsyncLayer instead of WmsOverlay? You can reference the code here. Sorry for the inconveniences.

It would not be an issue in the upcoming v14.3.

Thanks,
Ben

Hi,

I tested HowDol sample with actual WMS server instead of non-existing server.

It seems that if there is no connection to server when application is started everything works as expected (custom exception is drawn correctly). But if connection is first working (map is correctly loaded) and after that connection is disconnected custom exceptions are not drawn anymore (original exceptions are drawn instead). Same behavior was seen also with our application using LayerOverlay + WmsAsyncLayer. Also since custom drawing is not done drawn exceptions are stored to cache as well.

Could you please check why custom drawing is not working in such case ?

Br, Simo

Hi Simo,

I figured that the WmsLayer is hard-coded to swallow exceptions and display an error message on the map. As a result, the exception is never thrown, which is why DrawExceptionCore is not being called. This behavior appears to be specific to WmsLayer.

You can work around this by overriding the DrawAsyncCore method (see my code below) in your CustomWmsAsyncLayer (derived from WmsAsyncLayer). By removing the hard-coded exception handling, the exception will be raised properly and DrawExceptionCore will be invoked.

Thank you for reporting this issue. We plan to fix it in the upcoming v14.3 release.

Thanks,
Ben

    protected override async Task DrawAsyncCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
    {
        ValidatorHelper.CheckObjectIsNotNull(canvas, "view");
        ValidatorHelper.CheckLayerIsOpened(IsOpen);
        ValidatorHelper.CheckGeoCanvasIsInDrawing(canvas.IsDrawing);

        var wmsRequestExtent = canvas.CurrentWorldExtent;
        // Apply projection if need
        if (ProjectionConverter != null)
            wmsRequestExtent = ProjectionConverter.ConvertToInternalProjection(canvas.CurrentWorldExtent);

        GeoImage tempImage = null;
        try
        {
            PointShape centerPointShape = canvas.CurrentWorldExtent.GetCenterPoint();
            int canvasWidth = (int)(canvas.Width * canvas.ScaleFactor);
            int canvasHeight = (int)(canvas.Height * canvas.ScaleFactor);

            var imageBytes = await DownloadImageAsync(canvasWidth, canvasHeight, 1,
                wmsRequestExtent, canvas.MapUnit, canvas.CancellationToken);

            if (canvas.CancellationToken.IsCancellationRequested || imageBytes == null)
                return;

            tempImage = new GeoImage(imageBytes);

            // Check if the image needs to be projected
            if (ProjectionConverter != null && tempImage != null)
            {
                // Project the downloaded image to the external projection
                RasterProjectionResult projectedResult = ProjectionConverter.ConvertToExternalProjection(tempImage, wmsRequestExtent);
                if (canvas.CancellationToken.IsCancellationRequested) { return; }

                // Dispose the original image and use the projected one
                tempImage.Dispose();
                tempImage = projectedResult.Image;
            }

            if (tempImage != null)
            {
                if (Math.Abs(canvas.ScaleFactor - 1.0) < double.Epsilon)
                    canvas.DrawWorldImageWithoutScaling(tempImage, centerPointShape.X, centerPointShape.Y, DrawingLevel.LevelOne);
                else
                    canvas.DrawWorldImage(tempImage, centerPointShape.X, centerPointShape.Y, (tempImage.Width / canvas.ScaleFactor), (tempImage.Height / canvas.ScaleFactor), DrawingLevel.LevelOne);
            }
        }
        finally
        {
            tempImage?.Dispose();
        }
    }

Hi,

Yes, that override seems to fix custom drawing for WMS.

There is still one more issues with online maps and cache. Sometimes empty tiles are stored to cache. This problem seems to happen more likely if there is a slow network connection or there are multiple online maps in use (I guess that it actually slows down connection).

Empty tiles appears to cache when you zoom in/out (and pan) map quickly. It feels like that this happens when you change zoom level when current zoom level is loading/drawing. If you wait until current zoom level is loaded/drawn before zooming again everything works correctly. Don’t know how it is implemented but is there some kind of cancellation for loading/drawing previous zoom level when new level is set ? Maybe there is something on that which could cause those empty tiles if cancellation occurs in correct phase of tile download/drawing/caching ?

When this happens tiles on cache looks like this:

image

Only way to recover from this is to clear cache so that tiles are attempted to be downloaded again.

Br,Simo

Hi Simo,

My bad. In the code I provided yesterday, if the request is canceled or the return bytes is invalid, instead of just return:

Let’s throw an exception instead.

    if (canvas.CancellationToken.IsCancellationRequested || imageBytes == null)
        throw new TaskCanceledException();

This exception will be taken care of by the caller and the tile will not be cached.

A request timeout or the internal cancellation causes the issue and that’s why you saw it with a slow connection or zoom in/out map quickly (which causes the internal cancellation).

Thanks,
Ben

Hi,

  1. There is also return after projection in the code you provided, should it throw an exception also ?

  2. When I replaced return with throw it seems that now timeout correctly draws an exception to map showing that download failed. But problem is that these exceptions are stored to cache. I have already added rethrowing the exception from custom drawing and it seems to cancel caching if there is no-connection but does not do that for this timeout. Could you please check this issue ?

Br, Simo

Hi Simo,

Here I created a quick sample for you:

HandleException_04112025.zip (5.7 KB)

  1. It is using v14.2.2.
  2. Run the project, it starts to show the tiles from WMS Server. The cache tiles are generated as expected.
  3. Turn off the network, and it start to show the exception message on the screen. And the cache tiles stop being generated.

Have a try and see if it works for you.
For your first question, yes, it should throw an exception as well.

Also just FYI you don’t need the workaround code any more in the upcoming version v14.3. You can have a look from the beta branch if you are interested: samples/wpf/HowDoISample/Samples/Miscellaneous/HandleExceptions.xaml.cs · develop · ThinkGeo / Public / Desktop Maps · GitLab

Thanks,
Ben

Hi,

Ok, so instead of re-throwing original exception on DrawExceptionCore like on earlier examples you are now throwing new System.Exception instead. This seems to prevent caching in case of timeout.

Unfortunately our quality monitoring does not allow to throw System.Exception by user code but re-placing TaskCanceledException with OperationCanceledException in DrawAsyncCore seems to prevent caching as well (with re-throwing original exception on DrawExceptionCore).

Br, Simo

Hi Simo,

Yes, that’s because when a timeout occurs, an OperationCanceledException (internally wrapping a TimeoutException) is thrown. However, OperationCanceledException is caught internally to avoid excessive exception throwing since cancellations happen all the time in the map (for example when zooming in before the map is fully rendered).

In v14.3, we’ll separate timeout exceptions from regular OperationCanceledExceptions internally, so this is no longer an issue. For v14.2.2 as a workaround, here you should throw any exception other than OperationCanceledException, or it will be swallowed. (TaskCanceledException doesn’t work because it’s a subset of OperationCanceledException)

If you are not allowed to throw an exception in the code, how about wait for v14.3, which is scheduled to release around 1st week in May. All the new features are available in the beta versions and the HandleException HowDoI sample has been updated accordingly. We’re now in the final stages of testing and API review for the release.

Thanks,
Ben

Hi,

We need to get this solved also with 14.2.2.

With “For v14.2.2 as a workaround, here you should throw any exception other than OperationCanceledException, or it will be swallowed.”

Do you mean throwing exception from DrawAsyncCore or after drawing custom exception ?

Br, Simo

Hi Simo,

I meant throwing exception in DrawExceptionCore(GeoCanvas canvas, Exception e). In my demo I was throwing a generic Exception, any exception other than OperationCanceledException would work as well. It would work with v14.2.2 with this workaround.

Thanks,
Ben