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; 
    } 
}    
Public Class MyVirtualizationSource 
    Implements IMapItemsVirtualizationSource 
    Private document As XmlDocument 
 
    Public Sub New() 
        Dim streamInfo As StreamResourceInfo = 
            Application.GetResourceStream( 
                New Uri("/TestMapFeatures;component/Resources/GeoData/StoresLocation.xml", 
                        UriKind.Relative)) 
 
        Me.document = New XmlDocument() 
        Me.document.Load(streamInfo.Stream) 
    End Sub 
 
    Public Sub MapItemsRequest(sender As Object, eventArgs As MapItemsRequestEventArgs) 
        Dim minZoom As Double = eventArgs.MinZoom 
        Dim upperLeft As Location = eventArgs.UpperLeft 
        Dim lowerRight As Location = eventArgs.LowerRight 
 
        If Me.document Is Nothing Then 
            Return 
        End If 
 
        If minZoom = 3 Then 
            ' request areas ' 
            Dim list As List(Of StoreLocation) = 
                Me.GetStores(upperLeft.Latitude, 
                             upperLeft.Longitude, 
                             lowerRight.Latitude, 
                             lowerRight.Longitude, 
                             StoreType.Area) 
 
            eventArgs.CompleteItemsRequest(list) 
        End If 
 
        If minZoom = 9 Then 
            ' request areas' 
            Dim list As List(Of StoreLocation) = 
                Me.GetStores(upperLeft.Latitude, 
                             upperLeft.Longitude, 
                             lowerRight.Latitude, 
                             lowerRight.Longitude, 
                             StoreType.Store) 
 
            eventArgs.CompleteItemsRequest(list) 
        End If 
    End Sub 
 
    Friend Function GetStores(upperLeftLat As Double, 
                              upperLeftLong As Double, 
                              lowerRightLat As Double, 
                              lowerRightLong As Double, 
                              storeType__1 As StoreType) As List(Of StoreLocation) 
        Dim locations As New List(Of StoreLocation)() 
 
        Dim latLonCondition As String = 
            "[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)  
            & "]" 
 
        Select Case storeType__1 
            Case StoreType.Area 
                If True Then 
                    Dim nodeList As XmlNodeList = document.SelectNodes( 
                        "/StoresLocation/Area" & latLonCondition) 
                    For Each node As XmlNode In nodeList 
                        Dim element As XmlElement = DirectCast(node, XmlElement) 
 
                        locations.Add(New StoreLocation( 
                                      Convert.ToDouble( 
                                          element.GetAttribute("Latitude"), 
                                          CultureInfo.InvariantCulture), 
                                      Convert.ToDouble( 
                                          element.GetAttribute("Longitude"), 
                                          CultureInfo.InvariantCulture), 
                                      element.GetAttribute("Name"), 
                                      StoreType.Area)) 
                    Next 
                End If 
                Exit Select 
 
            Case StoreType.Store 
                If True Then 
                    Dim nodeList As XmlNodeList = document.SelectNodes( 
                        "/StoresLocation/Area/" & latLonCondition) 
                    For Each node As XmlNode In nodeList 
                        Dim element As XmlElement = DirectCast(node, XmlElement) 
 
                        locations.Add(New StoreLocation( 
                                      Convert.ToDouble( 
                                          element.GetAttribute("Latitude"), 
                                          CultureInfo.InvariantCulture), 
                                      Convert.ToDouble( 
                                          element.GetAttribute("Longitude"), 
                                          CultureInfo.InvariantCulture), 
                                      element.GetAttribute("Name"), 
                                      If(element.LocalName = "Market", StoreType.Market, StoreType.Store))) 
                    Next 
                End If 
                Exit Select 
        End Select 
 
        Return locations 
    End Function 
End Class 

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

See Also

In this article