Log in to like this post! Symbolization of Elements in XamMap [Infragistics] Mihail Mateev / Tuesday, August 31, 2010 Custom symbolization for the map elements in XamMap component is a common case . The solution often is to use a Value Template for the specific MapLayer. By default position of the symbol is in the center of the bounding rectangle of the multi-polygons.It is possible to have a symbol outside of the polylines, included in the multi-polygons, representing surface elements. Sample demo application demonstrates how to change a position of the MapElement Symbol into the Centroid (x = Sum(point[i].X/n, y= Sum(y[i]/n) where n is the number of points for the polyline with a biggest area from all polylines in the map element). Demo application: Demo application will be created for Silverlight. It is possible to make a similar one for WPF. Requirements: To be possible to build demo applications you need to install: SQL Server 2008 R2 Express or higher license. http://www.microsoft.com/express/Database/InstallOptions.aspx NetAdvantage for Silverlight Data Visualization 2010 vol.2 http://ko.infragistics.com/dotnet/netadvantage/silverlight/data-visualization.aspx#Downloads Steps to create a demo application: Steps to create a demo application: Create a base Silverlight application, using XamMap and data from SQL Server 2008 Spatial. Add a custom ValueTemplate for a MapLayer, named "world": Add an Event Handler for the MapLayer.Imported event and calculate in its implementation a new position for MapElement.SymbolOrigin property: Change the size of the symbols, depending of the value: Implementation: Create a base Silverlight application, using XamMap and data from SQL Server 2008 Spatial. Demo application is based on a demo, used for the article:"Using Infragistics XamMap Silverlight/WPF control with SQL Server Spatial":https://ko.infragistics.com/community/blogs/b/mihail_mateev/posts/using-infragistics-xammap-silverlight-wpf-control-with-sql-server-spatial Add a custom value template for a MapLayer, named "world": <ig:MapLayer Name="World" Brushes="#4C2C42C0 #4C218B93 #4CDC1C1C #4C3D7835 #4C701B51" FillMode="Choropleth" WorldRect="{Binding ElementName=MainWindow, Path=WorldRect}" DataMapping="Name=Name;Value=Value;Caption=Value" VisibleFromScale="0" > <ig:MapLayer.Reader> <ig:SqlShapeReader DataMapping="Data=geom; Name=CNTRY_NAME; Value=POP_CNTRY; Caption=CNTRY_NAME; ToolTip=CNTRY_NAME"> </ig:SqlShapeReader> </ig:MapLayer.Reader> <ig:MapLayer.ValueScale> <ig:LinearScale IsAutoRange="True"/> </ig:MapLayer.ValueScale> <ig:MapLayer.ValueTemplate> <DataTemplate> <Grid Width="20" Height="20"> <ToolTipService.ToolTip> <Border CornerRadius="4" BorderBrush="Bisque" BorderThickness="3" Background="Beige" Padding="8"> <StackPanel> <TextBlock FontSize="12" FontWeight="Bold" Text="{Binding Path=ToolTip, StringFormat='Location: {0}'}" /> <TextBlock FontSize="12" FontWeight="Bold" Text="{Binding Path=Value, StringFormat='Population: {0}'}" /> </StackPanel> </Border> </ToolTipService.ToolTip> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.15*"/> <ColumnDefinition Width="0.7*"/> <ColumnDefinition Width="0.15*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.04*"/> <RowDefinition Height="0.4*"/> <RowDefinition Height="0.56*"/> </Grid.RowDefinitions> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" Fill="Aqua" Opacity="0.7"/> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <LinearGradientBrush> <GradientStop Color="Transparent" Offset="0"/> <GradientStop Color="#30000000" Offset="1"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <RadialGradientBrush> <GradientStop Color="Transparent" Offset="0"/> <GradientStop Color="#50000000" Offset="1"/> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> <Ellipse Grid.Row="1" Grid.Column="1" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <LinearGradientBrush StartPoint="0, 0" EndPoint="0, 1"> <GradientStop Color="#a0ffffff" Offset="0.0"/> <GradientStop Color="#00ffffff" Offset="1.0"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> </Grid> </DataTemplate> </ig:MapLayer.ValueTemplate> </ig:MapLayer> Run the application: Startup screen has symbols for each country. Some of them are outside of the parts of multi-polygons, representing countries. Silverlight application Initial screen. Add an Event Handler for the MapLayer.Imported event and calculate in its implementation a new position for MapElement.SymbolOrigin property: <ig:MapLayer Name="World" Brushes="#4C2C42C0 #4C218B93 #4CDC1C1C #4C3D7835 #4C701B51" FillMode="Choropleth" WorldRect="{Binding ElementName=MainWindow, Path=WorldRect}" DataMapping="Name=Name;Value=Value;Caption=Value" VisibleFromScale="0" Imported="Layer_Imported"> ..... private void Layer_Imported( object sender, MapLayerImportEventArgs e) { MapLayer layer = sender as MapLayer; if (layer == null) { return; } List<MapElement> list = new List<MapElement>(layer.Elements); foreach (MapElement ele in list) { var el = ele as SurfaceElement; if (el == null) { continue; } MapPolylineCollection plines = el.Polylines; var ordered = plines.OrderByDescending(c => c.GetPolygonArea()); MapPolyline poly = ordered.ElementAt(0); ele.SymbolOrigin = poly.GetCentroid(); } } /// <summary> /// Implements extension methods to calculate a geometry properties of a polyline /// </summary> public static class Util { public static double GetPolygonSignedArea(this IList<Point> c) { int n = c.Count; double a = 0.0; if (n > 2) { for (int k = n - 2, j = n - 1, i = 0; i < n; k = j, j = i, ++i) { a += c[j].X * (c[i].Y - c[k].Y); } } return a; } public static double GetPolygonArea(this IList<Point> c) { return Math.Abs(0.5 * GetPolygonSignedArea(c)); } public static Point GetCentroid(this IEnumerable<Point> points) { double x = 0.0; double y = 0.0; double c = 0.0; foreach (Point point in points) { x += point.X; y += point.Y; c += 1.0; } return new Point(x / c, y / c); } } Run the application: Positions of the symbols now are on appropriate places Change the size of the symbols, depending of the value: <ig:MapLayer Name="World" Brushes="#4C2C42C0 #4C218B93 #4CDC1C1C #4C3D7835 #4C701B51" FillMode="Choropleth" WorldRect="{Binding ElementName=MainWindow, Path=WorldRect}" DataMapping="Name=Name;Value=Value;Caption=Value" VisibleFromScale="0" Imported="Layer_Imported"> <ig:MapLayer.Reader> <ig:SqlShapeReader DataMapping="Data=geom; Name=CNTRY_NAME; Value=POP_CNTRY; Caption=CNTRY_NAME; ToolTip=CNTRY_NAME"> </ig:SqlShapeReader> </ig:MapLayer.Reader> <ig:MapLayer.ValueScale> <ig:LinearScale IsAutoRange="True"/> </ig:MapLayer.ValueScale> <ig:MapLayer.ValueTemplate> <DataTemplate> <Grid Width="{Binding Path=Value}" Height="{Binding Path=Value}"> <ToolTipService.ToolTip> <Border CornerRadius="4" BorderBrush="Bisque" BorderThickness="3" Background="Beige" Padding="8"> <StackPanel> <TextBlock FontSize="12" FontWeight="Bold" Text="{Binding Path=ToolTip, StringFormat='Location: {0}'}" /> <TextBlock FontSize="12" FontWeight="Bold" Text="{Binding Path=Value, StringFormat='Population: {0}'}" /> </StackPanel> </Border> </ToolTipService.ToolTip> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.15*"/> <ColumnDefinition Width="0.7*"/> <ColumnDefinition Width="0.15*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="0.04*"/> <RowDefinition Height="0.4*"/> <RowDefinition Height="0.56*"/> </Grid.RowDefinitions> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" Fill="Aqua" Opacity="0.7"/> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <LinearGradientBrush> <GradientStop Color="Transparent" Offset="0"/> <GradientStop Color="#30000000" Offset="1"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Ellipse Grid.RowSpan="3" Grid.ColumnSpan="3" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <RadialGradientBrush> <GradientStop Color="Transparent" Offset="0"/> <GradientStop Color="#50000000" Offset="1"/> </RadialGradientBrush> </Ellipse.Fill> </Ellipse> <Ellipse Grid.Row="1" Grid.Column="1" IsHitTestVisible="False" Opacity="0.7"> <Ellipse.Fill> <LinearGradientBrush StartPoint="0, 0" EndPoint="0, 1"> <GradientStop Color="#a0ffffff" Offset="0.0"/> <GradientStop Color="#00ffffff" Offset="1.0"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> </Grid> </DataTemplate> </ig:MapLayer.ValueTemplate> </ig:MapLayer> const double Min = 5; const double Max = 70; .... private void Layer_Imported( object sender, MapLayerImportEventArgs e) { MapLayer layer = sender as MapLayer; if (layer == null) { return; } List<MapElement> list = new List<MapElement>(layer.Elements); foreach (MapElement ele in list) { var el = ele as SurfaceElement; if (el == null) { continue; } MapPolylineCollection plines = el.Polylines; var ordered = plines.OrderByDescending(c => c.GetPolygonArea()); MapPolyline poly = ordered.ElementAt(0); ele.SymbolOrigin = poly.GetCentroid(); //Normalize a population value double value = ele.Value / 5000000.0; if (value < Min) { value = Min; } else if (value > Max) { value = Max; } ele.Value = value; } } Run the application: Size of the symbols depends on country population, normalized in the event handler