Performance with RadMap

7 posts, 0 answers
  1. Doug
    Doug avatar
    15 posts
    Member since:
    Feb 2011

    Posted 18 Oct 2011 Link to this post

    Hi,

    I'm trying to load data from an sql database using the SQLGeospatialDataReader using the OpenStreetMapprovider. I've had some success using the InformationLayer as per the example. This works great when i'm trying to display only about 50 objects (multi lines) but the performance becomes poor when i'm up to around 5000. That is panning and zooming is very slow and jerky.

    What I would like to do would be to have smooth panning and zooming and just have database data show itself when it catches up / loads, I understand that this may take a few seconds but this method of behaviour will be a lot less frustrating for the user than the jerky unresponsive panning and zooming.

    I've tried listening for the zoom events and adding / removing the layer but this doesn't seem to make any difference and is actually worse.

    I'm aware of this DynamicLayer but for now I dont want to show different amounts of data depending on the zoom, as they are all equal... I have tried to accomplish the behaviour I specified above using the dynamic layer but i'm struggling to get anywhere with this and i'm not even sure it can help with what im trying to do.

    Please could you assist?

    Thanks
    Doug

  2. Doug
    Doug avatar
    15 posts
    Member since:
    Feb 2011

    Posted 19 Oct 2011 Link to this post

    Hi,

    I'm wondering if anyone has had a chance to look at this.

    I'm now looking into the DynamicLayer further and trying to get this working so that i can limit what data is shown to the user to try and improve performance. Is it even possible to use DynamicLayer with a SQLGeospatialDataReader underneath? When I try this it seems to read the data into the map irrespective of the ItemsRequest method.

    Also if I have my sql database entities loaded into a collection and the Geospatial property is now byte[] on one if the items in the collection (I created a new view using the STAsBinary in sql as per the documentation) how do I convert this field into something meaningful I can then use to create Map Shapes.

    As i said before i've got the SqlGeospatialReader working with an information layer but i now want to try and use it with a Dynamic Layer or at least be able to do soemthing with the entities which come out of the SQL server 2008 database...

    Xaml snippet below

    Thanks
    Doug

     <telerik:RadMap x:Name="radMap">
                <telerik:RadMap.Provider>
                    <Map:OpenStreetMapProvider />
                </telerik:RadMap.Provider>
                <!--<Map:InformationLayer x:Name="informationLayer">
                    <Map:InformationLayer.Reader>
                        <Map:SqlGeospatialDataReader x:Name="sqlGeospatialDataReader" 
                                                         GeospatialPropertyName="Point"
                                                         Source="{Binding Points}"
                                                         ToolTipFormat="{}{Name} ({Projects} : {LastModifiedTime})" />
                    </Map:InformationLayer.Reader>
                </Map:InformationLayer>-->
                <Map:DynamicLayer Name="WellsDynamicLayer">
                    <Map:DynamicLayer.Reader>
                        <Map:SqlGeospatialDataReader x:Name="sqlGeospatialDataReader" GeospatialPropertyName="Point" Source="{Binding Points}" ToolTipFormat="{}{Name} ({Projects} : {LastModifiedTime})"/>
                   </Map:DynamicLayer.Reader>
                    <Map:DynamicLayer.ZoomGridList>
                        <Map:ZoomGrid MinZoom="1" LatitudesCount="1" LongitudesCount="1"/>
                        <Map:ZoomGrid MinZoom="20" LatitudesCount="1" LongitudesCount="1"/>
                    </Map:DynamicLayer.ZoomGridList>
                </Map:DynamicLayer>
            </telerik:RadMap>
  3. DevCraft banner
  4. Andrey
    Admin
    Andrey avatar
    1681 posts

    Posted 21 Oct 2011 Link to this post

    Hi Doug,

    It looks like you are using DynamicLayer in a wrong way. It does not allow to add or to remove items directly. They should be added according to the logic of the IMapDynamicSource.ItemsRequest method implementation only, but when you use the SqlGeospatialDataReader in the DynamicLayer.Reader property then the reader adds the generated shapes directly.
    So, I would recommend using the SqlGeospatialDataReader from the codebehind for reading shapes into a separate collection. Later you can select the items from it within the ItemsRequest method for example using a LINQ query. The API of the SqlGeospatialDataReader allows to do it using the SqlGeospatialDataReader.Read method. You can start it when the data is already retrieved from database. You can use the SqlGeospatialDataReader.PreviewReadCompleted event to get the generated shapes.
    The sample code is below.
    public partial class MainPage : UserControl
    {
        private bool initialized;
     
        public MainPage()
        {
            InitializeComponent();
     
            this.radMap.InitializeCompleted += new EventHandler(radMap_InitializeCompleted);
        }
     
        private void radMap_InitializeCompleted(object sender, EventArgs e)
        {
            if (!this.initialized)
            {
                this.initialized = true;
     
                var dbContext = new Database1Entities(new Uri("http://localhost:55304/WcfDataService1.svc"));
     
                var entity = new DataServiceCollection<SQLReaderWCF.ServiceReference1.LocationsWKB>();
                entity.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(entity_LoadCompletedLocationsWKB);
                entity.LoadAsync(dbContext.LocationsWKB);
            }
        }
     
        private void entity_LoadCompletedLocationsWKB(object sender, LoadCompletedEventArgs e)
        {
            var entity = sender as DataServiceCollection<SQLReaderWCF.ServiceReference1.LocationsWKB>;
            entity.LoadCompleted -= new EventHandler<LoadCompletedEventArgs>(entity_LoadCompletedLocationsWKB);
     
            var reader = new SqlGeospatialDataReader();
            reader.PreviewReadCompleted += new PreviewReadShapesCompletedEventHandler(reader_PreviewReadCompleted);
            reader.Read(entity, "Point");
        }
     
        private void reader_PreviewReadCompleted(object sender, PreviewReadShapesCompletedEventArgs eventArgs)
        {
            if (eventArgs.Error == null)
            {
                this.InitializeDynamicSource(eventArgs.Items);
            }
        }
    }

    Second issue is the DynamicLayer.ZoomGridList you use for dynamic layer.
    The dynamic layer adds/removes items dynamically. It works with squares which are calculated according to its ZoomGridList. When a square appears within the visible area of map then the dynamic layer adds its items onto the view. When a square gets out from visible area then the dynamic layer removes its items.
    During the panning a few squares can change its visibility. When squares contain a lot of points then this process takes a long time.
    Mostly, the performance of dynamic layer depends on its ZoomGridList you use in your application.
    The optimal value for the Latitudes and Longitudes count depends on the viewport size of the map control and the min zoom level of the zoom grid.
    I think that the dynamic layer will have best performance when the square size for request will be close to the viewport size. The count of the Latitudes and Longitudes could be calculated using the following way:
    1. For example the viewport size is 1024x1024.
    2. The map size is 512x512 when the zoom level is 1, 1024x1024 for 2 and 2048x2048 for 3 etc. The map size is calculated as 2 ^ (zoom level + 8).
    3. When the zoom level is 3, then the count could be calculated as 2048 / 1024 = 2. I.e. count is calculated as map size/ viewport size.

    I would recommend using the way of ZoomGridList calculation above for achieving good performance for high zoom levels. The following sample code of the InitializeDynamicSource method generates the ZoomGridList according to this rule:
    private void InitializeDynamicSource(List<FrameworkElement> shapes)
    {
        const int viewportWidth = 2000;
        const int viewportHeight = 2000;
     
        for (int zoomLayerIndex = 1; zoomLayerIndex <= 20; zoomLayerIndex += 1)
        {
            int latitudes = (int)(Math.Pow(2, zoomLayerIndex + 8) / viewportWidth);
            latitudes = Math.Max(latitudes, 1);
     
            int longitudes = (int)(Math.Pow(2, zoomLayerIndex + 8) / viewportHeight);
            longitudes = Math.Max(longitudes, 1);
     
            this.dynamicLayer.ZoomGridList.Add(new ZoomGrid(latitudes, longitudes, zoomLayerIndex));
        }
     
        this.dynamicLayer.DynamicSource = new DynamicSource(shapes);
    }

    The sample implementation of IMapDynamicSource below uses a LINQ query to select points within the requested area. I think you should improve it to select shapes on low zoom levels (you can check the zoom level using the ItemsRequestEventArgs.MinZoom property) according to additional condition or to select them from another collection which will be represents items for low zoom levels.
    public class DynamicSource : IMapDynamicSource
    {
        private List<FrameworkElement> shapes;
     
        public DynamicSource(List<FrameworkElement> shapes)
        {
            this.shapes = shapes;
        }
     
        public void ItemsRequest(object sender, ItemsRequestEventArgs e)
        {
            double minZoom = e.MinZoom;
            Location upperLeft = e.UpperLeft;
            Location lowerRight = e.LowerRight;
            LocationRect rect = new LocationRect(upperLeft, lowerRight);
     
            var items = from object shp
                            in this.shapes
                            where
                            this.ShouldBeAdded(rect, shp)
                            select shp;
     
            e.CompleteItemsRequest(items);
        }
     
        private bool ShouldBeAdded(LocationRect rect, object item)
        {
            bool shouldBeAdded = false;
            var point = item as MapPinPoint;
            if (point != null)
            {
                Location location = MapLayer.GetLocation(point);
                shouldBeAdded = rect.Contains(location);
            }
     
            return shouldBeAdded;
        }
    }

    Greetings,
    Andrey Murzov
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

  5. Doug
    Doug avatar
    15 posts
    Member since:
    Feb 2011

    Posted 24 Oct 2011 Link to this post

    Thanks very much for the comprehensive reply. I'm looking into this now.

    Doug
  6. Doug
    Doug avatar
    15 posts
    Member since:
    Feb 2011

    Posted 24 Oct 2011 Link to this post

    Hi,

    I've implemented the solution as described and it works fine for a layer if the items of interest only number 40 but has huge performance problems for a layer  with data that number approximately 5000 (in fact a few hundred also really causes problems). I have the following zoom gridlist printing the results from the calculation (width 1000, height 800).

    latitudes: 1 longitudes: 1 zoomLayerIndex: 1
    latitudes: 1 longitudes: 1 zoomLayerIndex: 2
    latitudes: 1 longitudes: 1 zoomLayerIndex: 3
    latitudes: 2 longitudes: 2 zoomLayerIndex: 4
    latitudes: 4 longitudes: 4 zoomLayerIndex: 5
    latitudes: 8 longitudes: 8 zoomLayerIndex: 6
    latitudes: 16 longitudes: 16 zoomLayerIndex: 7
    latitudes: 32 longitudes: 32 zoomLayerIndex: 8
    latitudes: 65 longitudes: 65 zoomLayerIndex: 9
    latitudes: 131 longitudes: 131 zoomLayerIndex: 10
    latitudes: 262 longitudes: 262 zoomLayerIndex: 11
    latitudes: 524 longitudes: 524 zoomLayerIndex: 12
    latitudes: 1048 longitudes: 1048 zoomLayerIndex: 13
    latitudes: 2097 longitudes: 2097 zoomLayerIndex: 14
    latitudes: 4194 longitudes: 4194 zoomLayerIndex: 15
    latitudes: 8388 longitudes: 8388 zoomLayerIndex: 16
    latitudes: 16777 longitudes: 16777 zoomLayerIndex: 17
    latitudes: 33554 longitudes: 33554 zoomLayerIndex: 18
    latitudes: 67108 longitudes: 67108 zoomLayerIndex: 19
    latitudes: 134217 longitudes: 134217 zoomLayerIndex: 20

    The linq query returns quickly so the problem comes after doing the e.CompleteItemsRequest.

    I've limited based on zoom in the ItemsRequest method (which isn't really ideal) and it doesn't cope with a few hundred items in the CompleteItemsRequest never mind a few thousand. 

    I'm using a pretty simple point template which is basically an ellipse but that doesn't seem to be the problem either as i've also tried after removing the template.

    I also just tried switching back to the InformationLayer and that, whilst slow /unresponsive is still far more performant than using the dynamic layer solution (a few seconds compared to a minute at low / lowish zoom levels).

    I also wonder whether there may be a memory leak. When i'm zoom limiting it is fine until i zoom in (as expected), then really suffers whilst completing the request but then still suffers once i zoom out even though now not drawing the items.

    It doesn't seem to me unreasonable to try and draw a few hundred to a few thousand points (not complex shapes) and expect reasonable performance. Unfortunately I just cant use DynamicLayers with the results i'm currently seeing.

    It may be that i'm doing something wrong somewhere but i've implemented pretty much exactly as described above... although I load the entities into a collection slightly differently to begin with using the DomainContext Load method.

    Are these surprising results, or is it simply not possible to deal with this amount of data? I've debugged it and looks like its all doing what its supposed to programmatically speaking.

    The other thing is it would be nice if it didnt freeze up completely after having completed CompleteItemsRequest method. I would have hoped that it would still be possible to zoom and pan and then have it "catch up" when its painted the image.

    Have you got any suggestions?

    Thanks
    Doug
  7. Doug
    Doug avatar
    15 posts
    Member since:
    Feb 2011

    Posted 24 Oct 2011 Link to this post

    Hi,

    I'll go ahead and partly answer my own question. After looking a bit more through the forum I found a post called 'Performance with thousands of displayed controls' from Feb 2011. There was an example solution called dynamiclayerperformance.zip. The solution uses a dynamic layer looking at a 100000 points (a lot of data) randomly distributed over the World. However, it only  displays these for zoomlayers index 8 and higher. When you look at how many items the ItemsRequest deals with, it is around 40 to 50 on average. The performance is fine. However if you modify the solution to say display from zoomlayer 7 and above then the ItemsRequest has to deal with about 190 items on average and it freezes up the same way as in our application.

    I would have to conclude that DynamicLayers are not suitable where you need to display upwards of 50 points in the view port, which isn't many to my mind. Unfortunately our data will not be uniformly distributed spatially so we can't make use of the dynamic layer.

    Are there any tricks or methods you know of to get around this limitation? Are there any plans to improve the performance of dynamic layers? The information layer can handle significantly more data before performance becomes an issue but will still struggle with more than a couple of thousand items. I'm really struggling to see a way forward at the moment.

    Thanks
    Doug


  8. Andrey
    Admin
    Andrey avatar
    1681 posts

    Posted 27 Oct 2011 Link to this post

    Hello Doug,

    The dynamic layer adds/removes items dynamically. It works with squares which are calculated according to its ZoomGridList. When a square appears within the visible area of map then the dynamic layer adds its items onto the view. When a square gets out from visible area then the dynamic layer removes its items.
    During the panning a few squares can change its visibility. When squares contain a lot of points then this process takes a long time. In fact it could be a problem when the number of shapes which are returned in the CompleteItemsRequest more than 50-100.

    Best wishes,
    Andrey Murzov
    the Telerik team

    Explore the entire Telerik portfolio by downloading the Ultimate Collection trial package. Get it now >>

Back to Top
DevCraft banner