progressive maprefresh?

Topics: SharpMap v0.9 / v1.x
May 23, 2014 at 11:31 AM
Edited May 23, 2014 at 11:31 AM
Hi,
is it possible to refresh the map in a progressive way - like Esri ArcGIS?

I still have a big Shapefile and if i zoom to extent it takes a long time (e.g. 10 seconds).
The progressbar runs...
After waiting the map refreshed complete and show layer with correct extent.

Thanks.
Editor
May 23, 2014 at 12:07 PM
The issue with doing this is that you are using up CPU to do the incremental refresh, rather than doing the rendering. So it would increase your overall load time.

One way around this might be to split your data up over several layers, and add them into a LayerGroup. Then as each layer gets rendered it should update the map I think (might have to check this though), you could split the data up on load into say long lines, medium lines and short lines. There should be less long lines, so it would load first, etc.

Probably worth giving it a go
May 23, 2014 at 12:16 PM
Thanks,
but this workaround is not possible for me.
I have to load original data into map, no splitting over serveral layers.

So , we can say : there ist no progressive way in sharpmap to refreh the map!?
Coordinator
May 23, 2014 at 1:11 PM
You could mimic TileAsyncLayer, but instead of fetching tiles you could render them. I see if I can come up with a proof of concept.
Coordinator
May 23, 2014 at 2:37 PM
Edited May 23, 2014 at 3:28 PM
Try this:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.Serialization;
using System.Threading;
using GeoAPI.Geometries;
using SharpMap.Data;

namespace SharpMap.Layers
{
    /// <summary>
    /// A proxy class to allow async tile rendering for static layers
    /// </summary>
    /// <example lang="C#">
    /// var map = new SharpMap.Map(new System.Drawing.Size(1024, 786));
    /// var provider = new SharpMap.Data.Providers.Shapefile("&lt;Path to shapefile&gt;", true);
    /// var layer = new SharpMap.Layers.VectorLayer("LAYER1", provider);
    /// map.BackgroundLayer.Add(AsyncLayerProxyLayer.Create(layer));
    /// </example>
    [Serializable]
    public class AsyncLayerProxyLayer : ITileAsyncLayer, IDisposable, ICanQueryLayer
    {
        private readonly ILayer _baseLayer;
        private bool _onlyRedrawWhenComplete;
        [NonSerialized]
        private int _numPendingDownloads;
        private readonly Size _cellSize;
        private ImageAttributes _imageAttributes = new ImageAttributes();

        private class RenderTask
        {
            /// <summary>
            /// The token to cancel the task
            /// </summary>
            internal System.Threading.CancellationTokenSource CancellationToken;
            /// <summary>
            /// The task
            /// </summary>
            internal System.Threading.Tasks.Task Task;
        }

        [NonSerialized]
        private List<RenderTask> _currentTasks = new List<RenderTask>();

        /// <summary>
        /// Method to warp a usual layer in an async layer
        /// </summary>
        /// <param name="layer">The layer to wrap</param>
        /// <param name="tileSize">The size of the tile</param>
        /// <returns>A async tile layer</returns>
        public static ILayer Create(ILayer layer, Size? tileSize = null)
        {
            if (layer == null)
                throw new ArgumentNullException("layer");
            if (layer is ITileAsyncLayer)
                return layer;

            tileSize = tileSize ?? new Size(256, 256);

            return new AsyncLayerProxyLayer(layer, tileSize.Value);
        }

        /// <summary>
        /// Creates an instance of this class
        /// </summary>
        /// <param name="baseLayer">The layer to proxy</param>
        /// <param name="cellSize">The size of the tile</param>
        private AsyncLayerProxyLayer(ILayer baseLayer, Size cellSize)
        {
            _baseLayer = baseLayer;
            _cellSize = cellSize;
        }

        double ILayer.MinVisible
        {
            get { return _baseLayer.MinVisible; }
            set { _baseLayer.MinVisible = value; }
        }

        double ILayer.MaxVisible
        {
            get { return _baseLayer.MaxVisible; }
            set { _baseLayer.MaxVisible = value; }
        }

        bool ILayer.Enabled
        {
            get { return _baseLayer.Enabled; }
            set { _baseLayer.Enabled = value; }
        }

        string ILayer.LayerName
        {
            get { return _baseLayer.LayerName; }
            set { _baseLayer.LayerName = value; }
        }

        Envelope ILayer.Envelope
        {
            get { return _baseLayer.Envelope; }
        }

        int ILayer.SRID
        {
            get { return _baseLayer.SRID; }
            set { _baseLayer.SRID = value; }
        }

        int ILayer.TargetSRID
        {
            get { return _baseLayer.TargetSRID; }
        }

        string ILayer.Proj4Projection
        {
            get { return _baseLayer.Proj4Projection; }
            set { _baseLayer.Proj4Projection = value; }
        }

        void ILayer.Render(Graphics g, Map map)
        {
            if (!_baseLayer.Enabled)
                return;

            // Cancel old rendercycle
            ((ITileAsyncLayer)this).Cancel();

            var mapViewport = map.Envelope;
            var mapSize = map.Size;

            var columns = (int)Math.Ceiling((double) mapSize.Width/_cellSize.Width);
            var rows = (int)Math.Ceiling((double)mapSize.Height / _cellSize.Height);
            var renderMapSize = new Size(columns*_cellSize.Width, rows * _cellSize.Height);
            var horizontalFactor = (double) renderMapSize.Width/mapSize.Width;
            var verticalFactor = (double) renderMapSize.Height/mapSize.Height;

            var diffX = 0.5d*(horizontalFactor*mapViewport.Width - mapViewport.Width);
            var diffY = 0.5d*(verticalFactor*mapViewport.Height-mapViewport.Height);

            var totalRenderMapViewport = mapViewport.Grow(diffX, diffY);
            var columnWidth = totalRenderMapViewport.Width/columns;
            var rowHeight = totalRenderMapViewport.Height/rows;

            var miny = totalRenderMapViewport.MinY;
            for (var i = 0; i < rows; i ++)
            {
                var minx = totalRenderMapViewport.MinX;
                for (var j = 0; j < columns; j++)
                {
                    var tmpMap = new Map(_cellSize);
                    
                    tmpMap.Layers.Add(_baseLayer);
                    tmpMap.DisposeLayersOnDispose = false;
                    tmpMap.ZoomToBox(new Envelope(minx, minx + columnWidth, miny, miny + rowHeight));

                    var cancelToken = new CancellationTokenSource();
                    var token = cancelToken.Token;
                    var t = new System.Threading.Tasks.Task(delegate
                    {
                        if (token.IsCancellationRequested)
                            token.ThrowIfCancellationRequested();

                        var res = RenderCellOnThread(token, tmpMap);
                        if (res)
                        {
                            Interlocked.Decrement(ref _numPendingDownloads);
                            var e = DownloadProgressChanged;
                            if (e != null)
                                e(_numPendingDownloads);
                        }

                    }, token);
                    var dt = new RenderTask {CancellationToken = cancelToken, Task = t};
                    lock (_currentTasks)
                    {
                        _currentTasks.Add(dt);
                        _numPendingDownloads++;
                    }
                    t.Start();
                    minx += columnWidth;
                }
                miny += rowHeight;
            }
        }

        public event MapNewTileAvaliabledHandler MapNewTileAvaliable;
        
        public event DownloadProgressHandler DownloadProgressChanged;

        bool ITileAsyncLayer.OnlyRedrawWhenComplete
        {
            get { return _onlyRedrawWhenComplete; }
            set { _onlyRedrawWhenComplete = value; }
        }

        void ITileAsyncLayer.Cancel()
        {
            lock (_currentTasks)
            {
                foreach (var t in _currentTasks)
                {
                    if (!t.Task.IsCompleted)
                        t.CancellationToken.Cancel();
                }
                _currentTasks.Clear();
                _numPendingDownloads = 0;
            }
        }

        int ITileAsyncLayer.NumPendingDownloads
        {
            get { return _numPendingDownloads; }
        }

        private bool RenderCellOnThread(CancellationToken token, Map map)
        {
            var tile = new Bitmap(map.Size.Width, map.Size.Height, PixelFormat.Format32bppArgb);
            using (var g = Graphics.FromImage(tile))
            {
                g.Clear(Color.Transparent);
                _baseLayer.Render(g, map);
                map.Layers.Clear();
            }

            if (!token.IsCancellationRequested)
            {
                OnTileRendered(map.Envelope, tile);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Method to raise the map tile available event
        /// </summary>
        /// <param name="env"></param>
        /// <param name="bmp"></param>
        protected virtual void OnTileRendered(Envelope env, Bitmap bmp)
        {
            var h1 = MapNewTileAvaliable;
            if (h1 != null)
            {
                h1(null, env, bmp, bmp.Width, bmp.Height, _imageAttributes);
            }

            var h2 = DownloadProgressChanged;
            if (h2 != null)
            {
                Interlocked.Decrement(ref _numPendingDownloads);
                h2(_numPendingDownloads);
            }

        }

        public void Dispose()
        {
            ((ITileAsyncLayer)this).Cancel();
            _imageAttributes.Dispose();
        }

        void ICanQueryLayer.ExecuteIntersectionQuery(Envelope box, FeatureDataSet ds)
        {
            if (!((ICanQueryLayer)this).IsQueryEnabled)
                return;
            ((ICanQueryLayer)this).ExecuteIntersectionQuery(box, ds);
        }

        void ICanQueryLayer.ExecuteIntersectionQuery(IGeometry geometry, FeatureDataSet ds)
        {
            if (!((ICanQueryLayer)this).IsQueryEnabled)
                return;
            ((ICanQueryLayer)this).ExecuteIntersectionQuery(geometry, ds);
        }

        bool ICanQueryLayer.IsQueryEnabled
        {
            get
            {
                var cql = _baseLayer as ICanQueryLayer;
                if (cql != null && cql.IsQueryEnabled)
                    return true;
                return false;
            }
            set
            {
                var cql = _baseLayer as ICanQueryLayer;
                if (cql != null )
                    cql.IsQueryEnabled = value;
            }
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            _imageAttributes = new ImageAttributes();
            _currentTasks = new List<RenderTask>();
        }
    }
}
Coordinator
May 23, 2014 at 2:43 PM
Edited May 25, 2014 at 9:10 PM
Usage:
map.BackgroundLayer.Add(AsyncLayerProxyLayer.Create(layer));
// if you want a different tile size than 256x256, you can use this:
//map.BackgroundLayer.Add(AsyncLayerProxyLayer.Create(layer, new System.Drawing.Size(128, 128)));
// if it is too flickering, you can try:
//var al = AsyncLayerProxyLayer.Create(layer);
//((IAsyncTileLayer)al).OnlyRedrawWhenComplete = true;
//map.BackgroundLayer.Add(al);

Anyone has a better name?

There is room for improvement, the rendered tiles could be cached, so if the viewport don't change they would not have to be regenerated.

Also, I have not tried to use it ogether with some other basemap service (OSM,BING, you name it). I assume you would get some nice patchwork. If it does, that would mean we'd have to have an backgroundlayerimage for every backgroundlayer that has to be merged when updating the total background image.
Editor
May 23, 2014 at 11:51 PM
Hi felix,

Nice use of the "proxy layer" pattern!

I'll give this a try on tuesday, as it might work well for some of my work. Good job!

Thanks
Jun 19, 2014 at 2:40 PM
Is this AsnycLayerProxyLayer only working for BackgroundLayers?
I tried in Layers collection but then I don´t see any shapes.
Developer
Jun 20, 2014 at 10:11 AM
Yes, it only works on the BackgroundLayers collection, I'm thinking about to extend it to the other collections though.