Uso de datos espaciales en ASP.Net MVC con SQL Server
Una de las características más esperadas de Entity Framework 5 es la compatibilidad espacial.
Desde el lanzamiento de SQL 2008, muchos desarrolladores han solicitado compatibilidad con los tipos de datos espaciales en Entity Framework. Era un sueño para los usuarios de Microsoft ORM crear aplicaciones empresariales .NET rápidamente, utilizando datos espaciales. En mayo de este año se anunció la versión candidata para Entity Framework 5 (EF5). Esta versión ha aumentado el rendimiento en comparación con la versión anterior de EF y también es compatible con tipos espaciales. La funcionalidad espacial de EF5 requiere .NET 4.5.
La funcionalidad espacial de EF5 requiere .NET 4.5. Esto significa que necesitará tener instalado Visual Studios 2012. Puede descargar la versión candidata para VS 2012 aquí: http://www.microsoft.com/visualstudio/en-us
Datos espaciales en Entity Framework
Antes de Entity Framework 5.0 en .NET 4.5, el consumo de los datos anteriores requería el uso de procedimientos almacenados o comandos SQL sin procesar para acceder a los datos espaciales. Sin embargo, en Entity Framework 5, Microsoft introdujo los nuevos tipos DbGeometry y DbGeography. Estos tipos de ubicación inmutables proporcionan un montón de funcionalidades para manipular puntos espaciales mediante funciones de geometría que, a su vez, se pueden usar para realizar consultas espaciales comunes, como describí en la sintaxis SQL anterior.
Los tipos DbGeography/DbGeometry son inmutables, lo que significa que no se puede escribir en ellos una vez que se han creado. Son un poco extraños en el sentido de que necesita usar métodos de fábrica para instanciarlos: no tienen constructor() y no puede asignar a propiedades como Latitud y Longitud.
Es importante mencionar que estos tipos se definen en el ensamblado System.Data.Entity en el espacio de nombres System.Data.Spatial. A estas alturas, probablemente haya usado los tipos SqlGeometry y SqlGeography, definidos en el espacio de nombres Microsoft.SqlServer.Types
Creación de un modelo con datos espaciales
Empecemos por crear un modelo sencillo de Entity Framework que incluya entidades de las bases de datos de ejemplo Northwind y SpatialDemo. La entidad denominada world contiene una propiedad geom de tipo DbGeometry. El ejemplo es con SQL Server 2012, pero podría ejecutarlo con SQL Server 2008.
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
[DataMemberAttribute()]
public global::System.Data.Spatial.DbGeometry geom
{
get
{
return _geom;
}
set
{
OngeomChanging(value);
ReportPropertyChanging("geom");
_geom = StructuralObject.SetValidValue(value, true, "geom");
ReportPropertyChanged("geom");
OngeomChanged();
}
}
private global::System.Data.Spatial.DbGeometry _geom;
partial void OngeomChanging(global::System.Data.Spatial.DbGeometry value);
partial void OngeomChanged();
Entidades de Northwind

Ejemplo de base de datos SpatialDemo, incluidos los datos espaciales. Mundo de la tabla un archivo de tipo Geometría, llamado "geom". Este campo contiene los contornos de los países en forma de polígonos

El mundo de la entidad, generado a partir de la base de datos SpatialDemo, contiene un campo "geom" del tipo Geometría.

ASP.Net Aplicación MVC 4 con Entity Framework 5 RC y datos espaciales
Ahora es un dato espacial bastante fácil de usar en ASP.Net aplicaciones MVC.
- Controller
El controlador devuelve una vista que contiene un panel con Infragistics controles jQuery.
#region DashboardJs
public ActionResult DashboardJs()
{
ViewBag.Message = "Spatial Data Dashboard";
return View();
}
#endregion //DashboardJs
- Spatial Data Maintenance
Cuando tiene un tipo de datos de DbGeometry / DbGeography, no se puede serializar. Hay dos opciones:
- Para convertir el tipo de datos espaciales en WKT (texto conocido) y enviarlo al cliente (vista) como parte de JSON o XML
- Para usar sus propias clases que podrían ser serializadas
En este ejemplo se muestra el segundo enfoque
El método CountryByName serializa los resultados en JSON para que sea posible usarlo en la vista
#region CountryByName
[OutputCache(VaryByParam = "countryName", Duration = 120)]
public JsonResult CountryByName(string countryName)
{
switch (countryName)
{
case "UK":
countryName = "United Kingdom";
break;
case "USA":
countryName = "United States";
break;
}
var results = spDemo.worlds.Where(x => x.CNTRY_NAME == countryName);
List ret = new List();
foreach (world country in results)
{
CountryInfo info = new CountryInfo
{
Id = country.ID,
Code = country.CODE,
CountryName = country.CNTRY_NAME,
Population = country.POP_CNTRY,
Extend = GetGeometryBoundary(country)
};
ret.Add(info);
}
var retVal = Json(ret, JsonRequestBehavior.AllowGet);
return retVal;
}
#endregion //CountryByName
GetGeometryBoundary es un método auxiliar que se usa para obtener una lista de puntos, que representan un sobre de una instancia de DbGeometry. No olvide que los índices de puntos DbGeometry/DbGeography comienzan desde 1 !.
#region GetGeometryBoundary
public static SpatialRect GetGeometryBoundary(world country)
{
List multiPoints = new List();
var numPoints = country.geom.Envelope.ElementAt(1).PointCount;
for (int i = 1; i <= numpoints="" i="" pre="">
{
SpatialPoint pnt = new SpatialPoint((double)(country.geom.Envelope.ElementAt(1).PointAt(i).XCoordinate), (double)(country.geom.Envelope.ElementAt(1).PointAt(i).YCoordinate));
multiPoints.Add(pnt);
}
SpatialRect rect = multiPoints.GetBounds();
return rect;
}
#endregion //GetGeometryBoundary
ContryInfo es una clase auxiliar que se usa para serializar datos
#region CountryInfo
public class CountryInfo
{
public int Id { get; set; }
public string Code { get; set; }
public string CountryName { get; set; }
public long? Population { get; set; }
public SpatialRect Extend { get; set; }
}
#endregion //CountryInfo
SpatialPoint es una clase auxiliar para mantener datos de puntos. Te vendría muy bien:
#region SpatialPoint
public class SpatialPoint
{
public SpatialPoint(double x, double y)
{
this.X = x;
this.Y = y;
}
public double X { get; set; }
public double Y { get; set; }
}
#endregion //SpatialPoint
SpatialRect es una clase auxiliar para mantener una extensión del país
#region SpatialRect
public struct SpatialRect
{
public SpatialRect(double pLeft, double pTop, double pWidth, double pHeight)
{
left = pLeft;
top = pTop;
width = pWidth;
height = pHeight;
}
public double left;
public double top;
public double width;
public double height;
}
#endregion //SpatialRect
GetBounds es un método de extensión que se usa para obtener un límite de la lista de puntos.
#region Extensions
public static class Extensions
{
#region GetBounds
public static SpatialRect GetBounds(this IList points)
{
double xmin = Double.PositiveInfinity;
double ymin = Double.PositiveInfinity;
double xmax = Double.NegativeInfinity;
double ymax = Double.NegativeInfinity;
SpatialPoint p;
for (var i = 0; i < points.Count; i++)
{
p = points[i];
xmin = Math.Min(xmin, p.X);
ymin = Math.Min(ymin, p.Y);
xmax = Math.Max(xmax, p.X);
ymax = Math.Max(ymax, p.Y);
}
if (Double.IsInfinity(xmin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymax))
{
return new SpatialRect(0.0, 0.0, 0.0, 0.0);
}
return new SpatialRect(xmin, ymin, xmax - xmin, ymax - ymin);
}
#endregion //GetBounds
}
#endregion //Extensions
Vista
La vista presenta un panel de Infragistics cuadrícula, gráfico y mapa de jQuery.
La parte más importante del ejemplo es cómo consultar el método del controlador que devuelve datos espaciales (la extensión del país en este caso).
var countryUrl = "/Home/CountryByName?countryName=" + args.row.element[0].cells[1].textContent
...
$.getJSON(countryUrl,
function (json, text) {
$.each(json, function (index, value) {
var country = value;
var extend = country["Extend"];
var zoom = $("#map").igMap("getZoomFromGeographic", extend);
$("#map").igMap("option", "windowRect", zoom);
});
});
Infragistics jQuery Map instance definition.
$("#map").igMap({
width: "500px",
height: "500px",
panModifier: "control",
horizontalZoomable: true,
verticalZoomable: true,
windowResponse: "immediate",
overviewPlusDetailPaneVisibility: "visible",
seriesMouseLeftButtonUp: function (ui, args) {
var tets = args;
}
});
Infragistics jQuery Grid con zoom alrededor del país del cliente seleccionado.
$('#grid').igGrid({
virtualization: false, height: 280, width: 650,
dataSource: "/Home/Customers",
autoGenerateColumns: false,
columns: [
{ headerText: "Customer ID", key: "CustomerID", width: "120px", dataType: "string" },
{ headerText: "Country", key: "Country", width: "150px", dataType: "string" },
{ headerText: "City", key: "City", dataType: "string" },
{ headerText: "Contact Name", key: "ContactName", dataType: "string" },
{headerText: "Phone", key: "Phone", dataType: "string" }
],
features:
[{
name: 'Selection',
mode: 'row',
multipleSelection: false,
rowSelectionChanged: function (ui, args) {
$("#chart").igDataChart({
dataSource: "/Home/Orders?userID=" + args.row.element[0].cells[0].textContent
});
selected = args.row.element[0].cells[0].textContent; //keep track of selected user
var countryUrl = "/Home/CountryByName?countryName=" + args.row.element[0].cells[1].textContent
$.getJSON(countryUrl,
function (json, text) {
$.each(json, function (index, value) {
var country = value;
var extend = country["Extend"];
var zoom = $("#map").igMap("getZoomFromGeographic", extend);
$("#map").igMap("option", "windowRect", zoom);
});
});
}
}
,
{
name: 'Sorting',
type: "remote"
},
{
name: 'Paging',
type: "local",
pageSize: 10
}]
})
Datos espaciales en acción
Ejecute la aplicación y seleccione el "Panel de datos espaciales" en el menú.


Elija un cliente de igGrid y vea cómo el mapa muestra el país desde el que procede el cliente


Puede descargar el código fuente de la muestra aquí.
Para ejecutar este ejemplo, puede descargar las bases de datos de ejemplo Northwind y SpatialDemo
Como siempre, puedes seguirnos en Twitter: @mihailmateev y @ Infragistics, todos los tweets con hashtag #infragistcs y mantenerte en contacto en Facebook, Google+, LinkedIn y Infragistics grupo de usuarios de amigos!