Ignite UI Map (igMap) is one of the most exciting features of the new Infragistics Ignite UI Vol. 2.12 . This blog is the first of a series of articles dedicated to new opportunities of Ignite UI Geospatial Map
Ignite UI Geospatial Map
Released as CTP in 12.1, the Geospatial Map is now RTM. Create highly detailed, thematic geographical maps using an innovative feature set that includes custom shape templates, the ability to render polyline and polyshapes, Map Progression, Scatter Area Plots, and intuitive Overview Pane and much more.
The underlying rendering engine of the Geospatial Map (igMap) is the same as the Data Chart, so in addition to a wide variety of tile sources, you can also bind & render thousands to millions of data points on the map. Bing, CloudMade, OpenStreet, ESRI Shape Files and custom tiles are supported, as well as the following series types:
- Contour Line
- Polyline
- Scatter Area
- Geographic Shape
- Geographic Symbol
One of the amazing new features is One of the most impressive new features is the ability Geographic Shape Series to use as data source JavaScript arrays. This enables you to use different spatial data sources, if you can convert the data into JavaScript or JSON array.
1: $(function () {
2: var data = [{
3: points: [[{
4: x: 0, y: 0
5: }, {
6: x: 30, y: 0
7: }, {
4: public string Code { get; set; }
8: x: 30, y: 30
9: }, {
10: x: 0, y: 30
11: }, {
12: x: 0, y: 0
13: }]]
14: }, {
15: points: [[{
16: x: 40, y: 0
17: }, {
18: x: 70, y: 0
19: }, {
20: x: 70, y: 30
21: }, {
22: x: 40, y: 30
23: }, {
24: x: 40, y: 0
25: }]]
26: }];
27:
28:
29: $("#map").igMap({
30: width: "700px",
31: height: "500px",
32: series: [{
33: name: "series1",
34: type: "geographicShape",
35: markerType: "none",
36: dataSource: data,
37: shapeMemberPath: "points",
38: shapeStyle: {
39: fill: "red",
40: stroke: "black",
41: thickness: 8.0
42: }
43: }],
44: overviewPlusDetailPaneVisibility: "visible",
45: horizontalZoomable: true,
46: verticalZoomable: true,
47: windowResponse: "immediate"
48: });
49:
50: });
The code above should work for either shape or polyline series. Basically the member path should point at an array of arrays of points. Its an array of arrays so that you can define multiple shapes to be part of the same “item”.
Spatial Data in the Entity Framework
For developers, it would be useful to see how to apply these new features in practice. In this article we will create an example where using Ignite UI Map, Microsoft SQL Server 2012 Spatial Data and Entity Framework 5.
Many developers have been asking for support of Spatial data types in the Entity Framework. It was a dream for the Microsoft ORM users to create .NET business applications quickly, using spatial data. Entity Framework 5 has support for spatial types. The Spatial functionality in EF5 requires .NET 4.5.
The Spatial functionality in EF5 requires .NET 4.5. This means you will need Visual Studios 2012 installed. You can download the release candidate for VS 2012 here: http://www.microsoft.com/visualstudio/eng/downloads
Prior to Entity Framework 5.0 on .NET 4.5 consuming of the data above required using stored procedures or raw SQL commands to access the spatial data. In Entity Framework 5 however, Microsoft introduced the new DbGeometryandDbGeography types. These immutable location types provide a bunch of functionality for manipulating spatial points using geometry functions which in turn can be used to do common spatial queries like I described in the SQL syntax above.
The DbGeography/DbGeometry types are immutable, meaning that you can't write to them once they've been created. They are a bit odd in that you need to use factory methods in order to instantiate them - they have no constructor() and you can't assign to properties like Latitude and Longitude.
It is important to mention that these types are defined in System.Data.Entity assembly in System.Data.Spatialnamespace. By now you have probably used types SqlGeometry and SqlGeography types, defined inMicrosoft.SqlServer.Types namespace.
Entity Model with Spatial Data
In this sample we will use data from two sample databases : Northwind and SqlSpatialDemo.
SqlSpatialDemo database contains table world. This table contains filed “geom” from type “geometry”.
Spatial Data Types Overview
There are two types of spatial data. The geometry data type supports planar, or Euclidean (flat-earth), data. The geometry data type both conforms to the Open Geospatial Consortium (OGC) Simple Features for SQL Specification version 1.1.0 and is compliant with SQL MM (ISO standard).
In addition, SQL Server supports the geography data type, which stores ellipsoidal (round-earth) data, such as GPS latitude and longitude coordinates.
Spatial Data Objects
The geometry and geography data types support sixteen spatial data objects, or instance types. However, only eleven of these instance types are instantiable; you can create and work with these instances (or instantiate them) in a database. These instances derive certain properties from their parent data types that distinguish them as Points, LineStrings, CircularStrings, CompoundCurves, Polygons, CurvePolygons or as multiple geometry or geography instances in a GeometryCollection. Geography type has an additional instance type, FullGlobe.
x
Overview of Geometric "Set Theory" Methods
These Methods help to understand the relationship between spatial objects & also to create new objects that show that relationship.
In this example we will use methods STBoundaty, STEnvelope and STCentroid.
Let’s create a query that returns an object from geometry type:
1: DECLARE @g geometry;
2:
3: SELECT @g = [geom]
4: FROM [SqlSpatialDemo].[dbo].[world] WHERE CNTRY_NAME = 'Sweden';
5:
6: SELECT @g;
Method .STBoundary()
- Returns :
- Perimeter of a polygon, as a Linestring
- Start/End Points of a Line. As a MultiPoint( (End X,Y), (Start X,Y) )
NB: If the line is closed ie: Start & End are the same, then returns Empty
- Works with Geometry objects only. It is not a method of Geography Objects.
Syntax: geo.STBoundary(geo2)
1: DECLARE @g geometry;
2:
3: SELECT @g = [geom]
4: FROM [SqlSpatialDemo].[dbo].[world] WHERE CNTRY_NAME = 'Sweden';
5:
6: SELECT @g.STBoundary();
Method .STEnvelope()
- Returns the minimum axis-aligned bounding rectangle of the instance.
Syntax: geo.STEnvelope();
1: DECLARE @g geometry;
2:
3: SELECT @g = [geom]
4: FROM [SqlSpatialDemo].[dbo].[world] WHERE CNTRY_NAME = 'Sweden';
5:
6: SELECT @g.STEnvelope();
Method .STCentroid()
- Geometric centre of a (multi)Polygon. Null for Points & Lines.
- Works with Geometry objects only. It is not a method of Geography Objects.
- Similar to geography method STEnvelopeCentre()
Syntax: geo.STCentroid()
1: DECLARE @g geometry;
2:
3: SELECT @g = [geom]
4: FROM [SqlSpatialDemo].[dbo].[world] WHERE CNTRY_NAME = 'Sweden';
5:
6: SELECT @g.STCentroid().ToString();
Result:
POINT (16.741489174464782 62.787187265501657)
You can use these methods with geometry objects from Entity Framework using Geometry.Envelope, Geometry.Centroid and some additional logic to create geometry boundary.
ASP.Net MVC 4 Application with Entity Framework 5 and Ignite UI Components
Controller
- Spatial Data Maintenance
When you have a data from DbGeometry / DbGeography type you can’t serialize it.
We will create own classes that could be serialized
ContryInfo is a helper class used to serialize data
1: public class CountryInfo
2: {
3: public int Id { get; set; }
5: public string CountryName { get; set; }
6: public long? Population { get; set; }
7: public SpatialRect Extend { get; set; }
8: public Object Center { get; set; }
9: //Polygon Shape
10: public List<SpatialPoints> ShapeData { get; set; }
11:
12: }
13: #endregion //CountryInfo
SpatialPoint is a helper class to keep a point data.
1: #region SpatialPoint
2:
3: public class SpatialPoint
4: {
5: public SpatialPoint(double x, double y)
6: {
7: this.X = x;
8: this.Y = y;
9: }
10:
11: public double X { get; set; }
12: public double Y { get; set; }
13: }
14:
15: #endregion //SpatialPoint
SpatialPoints represents an object that contains property “Point” – a nested list of points. This object will be serialized as the same type of JavaScript array that expect igMap Geograpic Shape Series as a data source.
1: #region SpatialPoints
2:
3: public class SpatialPoints
4: {
5: public SpatialPoints()
6: {
7: this.Points = new List<List<Object>>();
8: }
9:
10: public List<List<Object>> Points { get; set; }
11:
12: }
13:
14: #endregion //SpatialPoints
SpatialRect is a helper class to keep an extend of the country
1: public struct SpatialRect
2: {
3: public SpatialRect(double pLeft, double pTop, double pWidth, double pHeight)
4: {
5: left = pLeft;
6: top = pTop;
7: width = pWidth;
8: height = pHeight;
9: }
10:
11: public double left;
12: public double top;
13: public double width;
14: public double height;
15: }
16:
17: #endregion //SpatialRect
CountryByName method serializes results to JSON to be possible to use it in the view
1: #region CountryByName
2: [OutputCache(VaryByParam = "countryName", Duration = 120)]
3: public JsonResult CountryByName(string countryName)
4: {
5: switch (countryName)
6: {
7: case "UK":
8: countryName = "United Kingdom";
9: break;
10: case "USA":
11: countryName = "United States";
12: break;
13: }
14: var results = spDemo.worlds.Where(x => x.CNTRY_NAME == countryName);
15:
16:
17: List<CountryInfo> ret = new List<CountryInfo>();
18: foreach (world country in results)
19: {
20: CountryInfo info = new CountryInfo
21: {
22: Id = country.ID,
23: Code = country.CODE,
24: CountryName = country.CNTRY_NAME,
25: Population = country.POP_CNTRY,
26: Extend = GetGeometryBoundary(country),
27: Center = GetGeometryCentroid(country),
28: ShapeData = GetGeometryPoints(country)
29: };
30:
31: ret.Add(info);
32: }
33:
34: var retVal = Json(ret, JsonRequestBehavior.AllowGet);
35: return retVal;
36: }
37: #endregion //CountryByName
GetGeometryBoundary is a helper method used to get a list of points, representing an envelope of a DbGeometry instance. Don't forget that DbGeometry/DbGeography point indexes start from 1 !.
1: #region GetGeometryBoundary
2: public static SpatialRect GetGeometryBoundary(world country)
3: {
4: List<SpatialPoint> multiPoints = new List<SpatialPoint>();
5: var numPoints = country.geom.Envelope.ElementAt(1).PointCount;
6:
7: for (int i = 1; i <= numPoints; i++)
8: {
9: SpatialPoint pnt = new SpatialPoint((double)(country.geom.Envelope.ElementAt(1).PointAt(i).XCoordinate), (double)(country.geom.Envelope.ElementAt(1).PointAt(i).YCoordinate));
10: multiPoints.Add(pnt);
11:
12: }
13: SpatialRect rect = multiPoints.GetBounds();
14: return rect;
15: }
16: #endregion //GetGeometryBoundary
GetBounds is an extension method used to get a boundary of the list of points.
1: #region Extensions
2: public static class Extensions
3: {
4:
5: #region GetBounds
6: public static SpatialRect GetBounds(this IList<SpatialPoint> points)
7: {
8: double xmin = Double.PositiveInfinity;
9: double ymin = Double.PositiveInfinity;
10: double xmax = Double.NegativeInfinity;
11: double ymax = Double.NegativeInfinity;
12:
13: SpatialPoint p;
14: for (var i = 0; i < points.Count; i++)
15: {
16: p = points[i];
17: xmin = Math.Min(xmin, p.X);
18: ymin = Math.Min(ymin, p.Y);
19:
20: xmax = Math.Max(xmax, p.X);
21: ymax = Math.Max(ymax, p.Y);
22: }
23:
24: if (Double.IsInfinity(xmin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymax))
25: {
26: return new SpatialRect(0.0, 0.0, 0.0, 0.0);
27: }
28:
29: return new SpatialRect(xmin, ymin, xmax - xmin, ymax - ymin);
30: }
31: #endregion //GetBounds
32: }
33: #endregion //Extensions
GetGeometryCentroid is a helper method used to get point representing geometric center of a DbGeometry instance.
1: #region GetGeometryCentroid
2: public static Object GetGeometryCentroid(world country)
3: {
4: SpatialPoint center = null;
5: var geometry = country.geom;
6: if (geometry != null)
7: {
8:
9: center = new SpatialPoint((double)geometry.Centroid.XCoordinate, (double)geometry.Centroid.YCoordinate);
10: }
11:
12:
13: return new {x = center.X, y = center.Y};
14: }
15: #endregion //GetGeometryCentroid
GetGeometryPoints is a helper method used to get a nested list of point representing nodes of a DbGeometry instance.
1: public static List<SpatialPoints> GetGeometryPoints(world country)
2: {
3: List<SpatialPoints> multiPolygonPoints = new List<SpatialPoints>();
4: var numElements = country.geom.ElementCount;
5:
6: for (int i = 1; i <= numElements; i++)
7: {
8: SpatialPoints spatialPoints = new SpatialPoints();
9: var currElem = country.geom.ElementAt(i);
10: if (null != currElem.PointCount)
11: {
12: spatialPoints.Points.Add(new List<object>());
13: int numPoints = (int)currElem.PointCount;
14:
15: for (int j = 1; j <= numPoints; j++)
16: {
17: double x = (double)currElem.PointAt(j).XCoordinate;
18: double y = (double)currElem.PointAt(j).YCoordinate;
19: spatialPoints.Points[0].Add(new { x = x, y = y });
20: }
21: }
22:
23: multiPolygonPoints.Add(spatialPoints);
24:
25: }
26:
27: return multiPolygonPoints;
28: }
29:
30: #endregion GetGeometryPoints
View
The view presents a dashboard from Infragistics jQuery Grid, Chart and Map.
Infragistics Ignite UI Map instance definition.
1: $("#map").igMap({
2: width: "500px",
3: height: "500px",
4: panModifier: "control",
5: horizontalZoomable: true,
6: verticalZoomable: true,
7: windowResponse: "immediate",
8: overviewPlusDetailPaneVisibility: "visible",
9: seriesMouseLeftButtonUp: function (ui, args) {
10: var tets = args;
11: }
12: });
Set the igMap extend
1: var country = value;
2: var extend = country["Extend"];
3:
4: var zoom = $("#map").igMap("getZoomFromGeographic", extend);
5: $("#map").igMap("option", "windowRect", zoom);
Create Geographic Shape Series to use as data source JavaScript array
1: var country = value;
2:
3: var shapeData = country["ShapeData"];
4:
5: selectedColor = $('#color1')[0].value;
6:
7: $("#map").igMap("option", "series", [{
8: name: "series1",
9: type: "geographicShape",
10: markerType: "none",
11: dataSource: shapeData,
12: shapeMemberPath: "Points",
13: shapeStyle: {
14: fill: selectedColor,
15: stroke: "black",
16: thickness: 8.0
17: }
18:
19: }]
20: );
Create Geographic Symbol Series that use markers with position, specified as a geometry centroid.
1: var center = country["Center"];
2: var name, lat, lon, centerData;
3:
4: name = country["CountryName"];
5:
6: lat = parseFloat(center.y);
7: lon = parseFloat(center.x);
8:
9: centerData = [{ Name: name, Latitude: lat, Longitude: lon }];
10:
11: $("#map").igMap("option", "series", [{
12: name: "Countries",
13: type: "geographicSymbol",
14: longitudeMemberPath: "Longitude",
15: latitudeMemberPath: "Latitude",
16:
17: /*
18: The provided object should have properties called render and optionally measure.
19: These are functions which will be called that will be called to handle the user specified custom rendering.
20: */
21: markerTemplate: {
22: render: function (renderInfo) {
23: var ctx = renderInfo.context; //2d canvas context
24: var x = renderInfo.xPosition;
25: var y = renderInfo.yPosition;
26:
27: if (renderInfo.isHitTestRender) {
28: // This is called for tooltip hit test only
29: // Rough marker rectangle size calculation
30: ctx.fillStyle = "yellow";
31: ctx.fillRect(x, y, renderInfo.availableWidth, renderInfo.availableHeight);
32: } else {
33: //actual marker drawing is here:
34: var markerData = renderInfo.data;
35: var name = markerData.item()["Name"];
36: //set font or measure will be for the default one
37: ctx.font = '10pt Segoe UI';
38: var textWidth = ctx.measureText(name).width;
39:
40: //Move the path point to the desired coordinates:
41: ctx.moveTo(x, y);
42: //Draw lines:
43: ctx.beginPath();
44: ctx.lineTo(x - (textWidth / 2) - 5, y + 5);
45: ctx.lineTo(x - (textWidth / 2) - 5, y + 40); // 35width rect.
46: ctx.lineTo(x + (textWidth / 2) + 5, y + 40); // full textWidth line plus 5 margin
47: ctx.lineTo(x + (textWidth / 2) + 5, y + 5); // 35 up
48: ctx.lineTo(x, y);
49: //finish the shape
50: ctx.closePath();
51: ctx.fillStyle = "rgba(78,183,226,0.7)";
52: ctx.fill();
53: ctx.lineWidth = 0.5;
54: ctx.strokeStyle = "#185170";
55: ctx.stroke();
56: //add a point at the start
57: ctx.beginPath();
58: ctx.fillStyle = "black";
59: ctx.arc(x, y, 1.5, 0, 2 * Math.PI, true);
60: ctx.fill();
61:
62: // Draw text
63: ctx.textBaseline = "top";
64: ctx.fillStyle = "black";
65: ctx.textAlign = 'center';
66: ctx.fillText(selected, x, y + 8);
67: ctx.fillText(name, x, y + 20);
68: }
69: }
70: },
71:
72: dataSource: centerData
73: }]
74: );
Let's put it all together
The most important part in the sample is how to query the controller’s method that returns spatial data (the country extend in this case). The code below shows how jQuery code calls the controller, when the grid row selection is changed.
1:
2: $('#grid').igGrid({
3: virtualization: false, height: 280, width: 650,
4: dataSource: "/Home/Customers",
5: autoGenerateColumns: false,
6: columns: [
7: { headerText: "Customer ID", key: "CustomerID", width: "120px", dataType: "string" },
8: { headerText: "Country", key: "Country", width: "150px", dataType: "string" },
9: { headerText: "City", key: "City", dataType: "string" },
10: { headerText: "Contact Name", key: "ContactName", dataType: "string" },
11: {headerText: "Phone", key: "Phone", dataType: "string" }
12: ],
13: features: [
14:
15: {
16: name: 'Selection',
17: mode: 'row',
18: multipleSelection: false,
19: rowSelectionChanged: function (ui, args) {
20: $("#chart").igDataChart({
21: dataSource: "/Home/Orders?userID=" + args.row.element[0].cells[0].textContent
22: });
23:
24: selected = args.row.element[0].cells[0].textContent; //keep track of selected user
25: var countryUrl = "/Home/CountryByName?countryName=" + args.row.element[0].cells[1].textContent;
26:
27: $.getJSON(countryUrl,
28: function (json, text) {
29: $.each(json, function (index, value) {
30: var country = value;
31: var extend = country["Extend"];
32:
33: var shapeData = country["ShapeData"];
34:
35: selectedColor = $('#color1')[0].value;
36:
37: var zoom = $("#map").igMap("getZoomFromGeographic", extend);
38: $("#map").igMap("option", "windowRect", zoom);
39:
40: $("#map").igMap("option", "series", [{
41: name: "series1",
42: type: "geographicShape",
43: markerType: "none",
44: dataSource: shapeData,
45: shapeMemberPath: "Points",
46: shapeStyle: {
47: fill: selectedColor,
48: stroke: "black",
49: thickness: 8.0
50: }
51:
52: }]
53: );
54:
55: //MM1
56: var center = country["Center"];
57: var name, lat, lon, centerData;
58:
59: name = country["CountryName"];
60:
61: lat = parseFloat(center.y);
62: lon = parseFloat(center.x);
63:
64: centerData = [{ Name: name, Latitude: lat, Longitude: lon }];
65:
66: $("#map").igMap("option", "series", [{
67: name: "Countries",
68: type: "geographicSymbol",
69: longitudeMemberPath: "Longitude",
70: latitudeMemberPath: "Latitude",
71:
72: /*
73: The provided object should have properties called render and optionally measure.
74: These are functions which will be called that will be called to handle the user specified custom rendering.
75: */
76: markerTemplate: {
77: render: function (renderInfo) {
78: var ctx = renderInfo.context; //2d canvas context
79: var x = renderInfo.xPosition;
80: var y = renderInfo.yPosition;
81:
82: if (renderInfo.isHitTestRender) {
83: // This is called for tooltip hit test only
84: // Rough marker rectangle size calculation
85: ctx.fillStyle = "yellow";
86: ctx.fillRect(x, y, renderInfo.availableWidth, renderInfo.availableHeight);
87: } else {
88: //actual marker drawing is here:
89: var markerData = renderInfo.data;
90: var name = markerData.item()["Name"];
91: //set font or measure will be for the default one
92: ctx.font = '10pt Segoe UI';
93: var textWidth = ctx.measureText(name).width;
94:
95: //Move the path point to the desired coordinates:
96: ctx.moveTo(x, y);
97: //Draw lines:
98: ctx.beginPath();
99: ctx.lineTo(x - (textWidth / 2) - 5, y + 5);
100: ctx.lineTo(x - (textWidth / 2) - 5, y + 40); // 35width rect.
101: ctx.lineTo(x + (textWidth / 2) + 5, y + 40); // full textWidth line plus 5 margin
102: ctx.lineTo(x + (textWidth / 2) + 5, y + 5); // 35 up
103: ctx.lineTo(x, y);
104: //finish the shape
105: ctx.closePath();
106: ctx.fillStyle = "rgba(78,183,226,0.7)";
107: ctx.fill();
108: ctx.lineWidth = 0.5;
109: ctx.strokeStyle = "#185170";
110: ctx.stroke();
111: //add a point at the start
112: ctx.beginPath();
113: ctx.fillStyle = "black";
114: ctx.arc(x, y, 1.5, 0, 2 * Math.PI, true);
115: ctx.fill();
116:
117: // Draw text
118: ctx.textBaseline = "top";
119: ctx.fillStyle = "black";
120: ctx.textAlign = 'center';
121: ctx.fillText(selected, x, y + 8);
122: ctx.fillText(name, x, y + 20);
123: }
124: }
125: },
126:
127: dataSource: centerData
128: }]
129: ); //MM2
130:
131: });
132: });
133:
134: }
135: }
136: ,
137:
138: {
139: name: 'Sorting',
140: type: "remote"
141: },
142: {
143: name: 'Paging',
144: type: "local",
145: pageSize: 10
146: }]
147: ,
148: rendered: function (ui, args) {
149: //set up on-load selection
150: $('#grid').igGridSelection("selectRow", 0);
151: //another way to get cell value independant of event parameters
152: var id = $('#grid').igGrid("getCellValue", 0, "CustomerID");
153: $("#chart").igDataChart({
154: dataSource: "/Home/Orders?userID=" + id
155: });
156:
157:
158: }
159: });
Demo Application
Run the application
Color Picker
For this sample is used Really Simple Color Picker in jQuery.
Usage of color picker is very straightforward. Users can either pick a color from the predefined color palette or enter hexadecimal value for a custom color.
The code below shows how to change the Geographic Shape Series color.
1: $('#color1').change(function () {
2: selectedColor = $('#color1')[0].value;
3:
4: var rowIndex = $('#grid').igGridSelection("selectedRow").index;
5:
6: var currRow = $('#grid').igGridSelection("selectedRow");
7:
8: $("#map").igMap("option", "series", [{ name: "series1", remove: true }]);
9:
10: $("#map").igMap("option", "series", [{ name: "Countries", remove: true }]);
11:
12: if (rowIndex != null) {
13: $('#grid').igGridSelection("selectRow", rowIndex);
14:
15: $('#grid').data("igGridSelection").options.rowSelectionChanged(null, { row: currRow});
16: }
17:
18:
19:
20: });
You can navigate around the map with both Geographic Shape Series and Geographic Symbol Series
You are probably thinking, how do I get my hands on Ignite UI Map? It’s easy.
Click on this image to get a fully support trial version of Infragistics Ignite UI controls:
To view all the samples and code for HTML, MVC & ASP.NET, click there:
http://www.infragistics.com/products/jquery/samples
Follow news from Infragistics for more information about new Infragistics products.
Source code is available here. Sample project is created with Visual Studio 2012, but you can use NetAdvantage 12.2 controls with both: Visual Studio 2012 and Visual Studio 2012. As always, you can follow us on Twitter @mihailmateev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!