New to Telerik UI for WPF? Download free 30-day trial

Items Virtualization

There are numerous scenarios where large number of items should be visualized on the map surface. Using large data source within the visualization layer can decrease the overall performance of the map control, especially the initial loading and the performance during zooming or panning. This is why the visualization layer API supports UI Virtualization, which processes information only inside the current viewport of the RadMap thus boosting the initial loading and the overall performance of the control.

Properties

The VisualizationLayer exposes the following properties:

  • RenderWhileMotion: А property of type bool that gets or sets a value indicating whether the layer should request and render items while the user is zooming or panning.

  • VirtualizationSource: А property of type IMapVirtualizationSource that gets or sets the item source of the layer.

  • ZoomLevelGridList: А property of type ZoomLevelGridCollection that gets the collection of ZoomLevelGrids used to specify the map division.

If both VirtualizationSource and ZoomLevelGridList properties are set then the VisualizationLayer switches to use UI Virtualization. When this feature is enabled the layer doesn’t take its items from the ItemsSource property. Instead, it raises an items request event when the ZoomLevel or Center properties of the RadMap control are changed.

Map Division

The VisualizationLayer needs the map surface to be divided into regions. The division can be defined using the VisualizationLayer.ZoomLevelGridList collection. Each ZoomLevelGrid should define a minimum zoom level for its division. The maximum zoom level for a grid is the minimum zoom level of the next grid in the list.

The ZoomLevelGrid exposes the following properties:

  • CellWidth: A property of type int that gets or sets the width of the cell in pixels.

  • CellHeight: A property of type int that gets or sets the height of the cell in pixels.

  • MinZoom: A property of type int that gets or sets the minimum zoom.

By default the cell has a size which equals the size of tile (256x256) which is usually used by the map providers like Bing and OpenStreet. Specifying the size of а cell for the zoom level affects a number of requests to a Virtualization Source which is depended on the viewport size of the map.

If the requests are performed slowly (for example they use a service which has low performance) you can increase the cell size to lower the number of requests.

Example 1: Virtualization Layer declaration

<telerik:RadMap x:Name="radMap"> 
    <telerik:VisualizationLayer x:Name="visualizationLayer"> 
        <telerik:VisualizationLayer.ZoomLevelGridList> 
            <telerik:ZoomLevelGrid MinZoom="3" /> 
            <telerik:ZoomLevelGrid MinZoom="9" /> 
        </telerik:VisualizationLayer.ZoomLevelGridList> 
    </telerik:VisualizationLayer> 
</telerik:RadMap> 

Virtualization Source

The data provided to the VisualizationLayer should be wrapped in a class that implements the IMapItemsVirtualizationSource interface. This interface contains a MapItemsRequest method, which is used by the VisualizationLayer to request new data whenever the ZoomLevel or Center properties of the RadMap control are changed. The number of requests depends on the number of the grid cells visualized in the current viewport of the control. Below you can see examples of different viewports.

If the cell size is 256x256 pixels (the default one) and there are 9 cells in the viewport of the control - 9 requests will be sent to the virtualization source whenever this geographical region comes into the view. Rad Map visualization layer virtualization

In this case only 6 requests will be sent to the virtualization source whenever this geographical region comes into the view. Rad Map visualization layer virtualization 2

Example 2 desmonstrate how to implement simple virtualization source.

Example 2: Create custom IMapItemsVirtualizationSource

public class MyVirtualizationSource : IMapItemsVirtualizationSource 
{ 
    private XmlDocument document; 
 
    public MyVirtualizationSource() 
    { 
        StreamResourceInfo streamInfo = Application.GetResourceStream( 
            new Uri( 
                "/TestMapFeatures;component/Resources/GeoData/StoresLocation.xml",  
                UriKind.Relative)); 
 
        this.document = new XmlDocument(); 
        this.document.Load(streamInfo.Stream); 
    } 
 
    public void MapItemsRequest(object sender, MapItemsRequestEventArgs eventArgs) 
    { 
        double minZoom = eventArgs.MinZoom; 
        Location upperLeft = eventArgs.UpperLeft; 
        Location lowerRight = eventArgs.LowerRight; 
 
        if (this.document == null) 
            return; 
 
        if (minZoom == 3) 
        { 
            // request areas 
            List<StoreLocation> list = this.GetStores( 
                upperLeft.Latitude, 
                upperLeft.Longitude, 
                lowerRight.Latitude, 
                lowerRight.Longitude, 
                StoreType.Area); 
 
            eventArgs.CompleteItemsRequest(list); 
        } 
 
        if (minZoom == 9) 
        { 
            // request areas 
            List<StoreLocation> list = this.GetStores( 
                upperLeft.Latitude, 
                upperLeft.Longitude, 
                lowerRight.Latitude, 
                lowerRight.Longitude, 
                StoreType.Store); 
 
            eventArgs.CompleteItemsRequest(list); 
        } 
    } 
 
    internal List<StoreLocation> GetStores( 
        double upperLeftLat, 
        double upperLeftLong, 
        double lowerRightLat, 
        double lowerRightLong, 
        StoreType storeType) 
    { 
        List<StoreLocation> locations = new List<StoreLocation>(); 
 
        string latLonCondition = "[number(@Latitude) < "  
            + upperLeftLat.ToString(CultureInfo.InvariantCulture)  
            + " and number(@Latitude) > "  
            + lowerRightLat.ToString(CultureInfo.InvariantCulture)  
            + " and number(@Longitude) > "  
            + upperLeftLong.ToString(CultureInfo.InvariantCulture)  
            + " and number(@Longitude) < "  
            + lowerRightLong.ToString(CultureInfo.InvariantCulture)  
            + "]"; 
 
        switch (storeType) 
        { 
            case StoreType.Area: 
                { 
                    XmlNodeList nodeList = document.SelectNodes( 
                        "/StoresLocation/Area" + latLonCondition); 
                    foreach (XmlNode node in nodeList) 
                    { 
                        XmlElement element = (XmlElement)node; 
 
                        locations.Add(new StoreLocation( 
                            Convert.ToDouble( 
                                element.GetAttribute("Latitude"),  
                                CultureInfo.InvariantCulture), 
                            Convert.ToDouble( 
                                element.GetAttribute("Longitude"),  
                                CultureInfo.InvariantCulture), 
                            element.GetAttribute("Name"), StoreType.Area)); 
                    } 
                } 
                break; 
 
            case StoreType.Store: 
                { 
                    XmlNodeList nodeList = document.SelectNodes( 
                        "/StoresLocation/Area/" + latLonCondition); 
                    foreach (XmlNode node in nodeList) 
                    { 
                        XmlElement element = (XmlElement)node; 
 
                        locations.Add(new StoreLocation( 
                            Convert.ToDouble( 
                                element.GetAttribute("Latitude"),  
                                CultureInfo.InvariantCulture), 
                            Convert.ToDouble( 
                                element.GetAttribute("Longitude"),  
                                CultureInfo.InvariantCulture), 
                            element.GetAttribute("Name"), 
                            element.LocalName == "Market" ? StoreType.Market : StoreType.Store)); 
                    } 
                } 
                break; 
        } 
 
        return locations; 
    } 
}    

Here is a sample of the VisualizationLayer declaration with UI Virtualization enabled:

Example 3: Setting custom IMapItemsVirtualizationSource in XAML

<UserControl x:Class="TestMapFeatures.Views.VisualizationLayer.Virtualization.ItemsVirtualization" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:TestMapFeatures.Views.VisualizationLayer.Virtualization" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     mc:Ignorable="d"  
     d:DesignHeight="300" d:DesignWidth="300"> 
    <UserControl.Resources> 
        <DataTemplate x:Key="AreaTemplate"> 
            <Ellipse telerik:MapLayer.Location="{Binding Location}" 
             Width="20" Height="20" 
             VerticalAlignment="Stretch" 
             HorizontalAlignment="Center" 
             Fill="Red" /> 
        </DataTemplate> 
 
        <DataTemplate x:Key="MarketTemplate"> 
            <Rectangle telerik:MapLayer.Location="{Binding Location}" 
               Width="20" Height="20" 
               VerticalAlignment="Stretch" 
               HorizontalAlignment="Center" 
               Stroke="Orange" 
               StrokeThickness="8" 
               Fill="#01000000" /> 
        </DataTemplate> 
 
        <DataTemplate x:Key="StoreTemplate"> 
            <Ellipse telerik:MapLayer.Location="{Binding Location}" 
             Width="20" Height="20" 
             VerticalAlignment="Stretch" 
             HorizontalAlignment="Center" 
             Stroke="Green" 
             StrokeThickness="3" 
             Fill="#01000000" /> 
        </DataTemplate> 
 
        <local:StoreTemplateSelector x:Key="StoreTemplateSelector" 
                             AreaTemplate="{StaticResource AreaTemplate}" 
                             MarketTemplate="{StaticResource MarketTemplate}" 
                             StoreTemplate="{StaticResource StoreTemplate}"/> 
 
        <local:MyVirtualizationSource x:Key="MyVirtualizationSource" /> 
    </UserControl.Resources> 
    <Grid> 
        <telerik:RadMap x:Name="radMap" 
                ZoomLevel="4" 
                Center="37.684297,-99.06924"> 
            <telerik:RadMap.Provider> 
                <telerik:OpenStreetMapProvider /> 
            </telerik:RadMap.Provider> 
            <telerik:VisualizationLayer x:Name="visualizationLayer" 
                                ItemTemplateSelector="{StaticResource StoreTemplateSelector}" 
                                VirtualizationSource="{StaticResource MyVirtualizationSource}"> 
                <telerik:VisualizationLayer.ZoomLevelGridList> 
                    <telerik:ZoomLevelGrid MinZoom="3" /> 
                    <telerik:ZoomLevelGrid MinZoom="9" /> 
                </telerik:VisualizationLayer.ZoomLevelGridList> 
            </telerik:VisualizationLayer> 
        </telerik:RadMap> 
 
    </Grid> 
</UserControl> 

When there are no ZoomLevelGrids that satisfy the current zoom level, no request will be made. Also, when the changed value of the zoom level or the region stays in the range of a ZoomLevelGrid or one of its cells, no request is made.

In the above sample, for example, when the ZoomLevel is 1 or 2, no requests will be made, because there is no grid which represents this range. When the value is bigger than 9, requests will be made only when the grid cell changes, because the grid is defined for the range from 9 to the maximum zoom level.

In the snapshots below you can see how the above example will be displayed with different ZoomLevel settings

The result at ZoomLevel 3: radmap-visualization-layer-virtualization

The result at ZoomLevel 9: radmap-visualization-layer-virtualization-zoomed