问题描述
我正在构建一个应用程序,该应用程序需要能够根据企业的“交货区域”进行搜索
例如,London Business
提供的服务距离经纬度最远10000米
Southampton Business
提供的服务距离经纬度1000米
使用EF Core和NetTopologySuite参与其中。
我正在使用以下简化代码:
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var londonBusiness = new Business
{
Name = "London Business",Area = geometryFactory.CreatePoint(new Coordinate(-0.127758,51.507351)),Arearadius = 10000
};
var southamptonBusiness = new Business
{
Name = "Southampton Business",Area = geometryFactory.CreatePoint(new Coordinate(1.4044,50.9097)),Arearadius = 1000
};
await _dbContext.Businesses.AddAsync(londonBusiness);
await _dbContext.Businesses.AddAsync(southamptonBusiness);
await _dbContext.SaveChangesAsync();
// QUERY
// this is very clsoe to the londonBusiness (a couple of km)
var searchLocation = _geometryFactory.CreatePoint(new Coordinate(-0.142500,51.539188));
var query = _dbContext
.Businesses
.Where(x => x.AreaLocation.distance(searchLocation) <= x.Arearadius)
.ToList()
// this projection is for debug purposes
.Select(x => new
{
distance = x.AreaLocation.distance(searchLocation),radius = x.Arearadius
});
这将返回以下结果:
{ Name = "London Business",distance = 0.035084485645370242,Radius = 10000 }
{ Name = "Southampton Business",distance = 1.6700762713552944,Radius = 1000 }
所以,我认为我的问题出在距离遥远的地方 我显然误会了距离是什么/与之有关。 他们是相对正确的-伦敦的商务距离比南安普敦的商务距离小得多
解决方法
使用ProjNet4GeoAPI
包进行演示以将过滤后的值的点映射到投影坐标系,并使用它们来计算以米为单位的距离。
using GeoAPI.CoordinateSystems.Transformations;
using Microsoft.EntityFrameworkCore;
using NetTopologySuite.Geometries;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
using System.Diagnostics;
using System.Linq;
namespace ConsoleApp7
{
public class Location
{
public int Id { get; set; }
public Point Point { get; set; }
}
public class Business
{
public int Id { get; set; }
public Point Point { get; set; }
public double Distance { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Location> Locations { get; set; }
public DbSet<Business> Businesses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=DESKTOP-5PVJ0I5;Database=geog;Integrated Security=true;",options => options.UseNetTopologySuite());
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
CoordinateTransformationFactory ctfac = new CoordinateTransformationFactory();
var from = GeographicCoordinateSystem.WGS84;
var to = ProjectedCoordinateSystem.WGS84_UTM(30,true);
// convert points from one coordinate system to another
ICoordinateTransformation trans = ctfac.CreateFromCoordinateSystems(from,to);
var businessCoordinate = new GeoAPI.Geometries.Coordinate(-0.127758,51.507351);
var searchLocationCoordinate = new GeoAPI.Geometries.Coordinate(-0.142500,51.539188);
var mathTransform = trans.MathTransform;
var businessLocation = mathTransform.Transform(businessCoordinate);
var searchLocation = mathTransform.Transform(searchLocationCoordinate);
// calculate distance in meters
var dist = businessLocation.Distance(searchLocation); // 3687m
// arrange db
var dbContext = new MyDbContext();
dbContext.Database.Migrate();
var location = new Location()
{
Point = new Point(searchLocationCoordinate.X,searchLocationCoordinate.Y) { SRID = 4326,}
};
// one business has radius to include location point
var businessWithLocationInRadius = new Business()
{
Distance = 4000,Point = new Point(businessCoordinate.X,businessCoordinate.Y) { SRID = 4326,}
};
// and this one has too low range
var businessWithLocationNOTInRadius = new Business()
{
Distance = 3500,}
};
dbContext.Add(location);
dbContext.Add(businessWithLocationInRadius);
dbContext.Add(businessWithLocationNOTInRadius);
dbContext.SaveChanges();
var query = dbContext
.Businesses
.Where(x => x.Point.Distance(location.Point) <= x.Distance)
.ToList()
.Select(x => new
{
Distance = searchLocation
.Distance(mathTransform.Transform(new GeoAPI.Geometries.Coordinate(x.Point.X,x.Point.Y))),// 3687m
Radius = x.Distance
})
.ToList();
Debugger.Break();
}
}
}
,
参考:SRID Ignored during client operations:
NTS在操作过程中会忽略SRID值。它假设一个平面坐标系。这意味着,如果您根据经度和纬度指定坐标,则某些客户端评估的值(例如距离,长度和面积)将以度为单位,而不是以米为单位。为了获得更有意义的值,在计算这些值之前,首先需要使用ProjNet4GeoAPI之类的库将坐标投影到另一个坐标系。
这是因为NetTopologySuite是JTS拓扑套件的.NET端口。参考:NetTopologySuite:
.NET GIS解决方案,对于.NET平台是快速而可靠的。 NetTopologySuite是JTS拓扑套件提供的所有功能的直接端口:NTS以“ .NET方式”公开JTS,例如使用“属性”,“索引器”等。
JTS网站的摘录也说明了NTS的功能:“ JTS拓扑套件是用于建模和处理二维线性几何的API。它提供了许多几何谓词和功能。JTS符合SQL的简单功能规范。由开放式GIS联盟出版。”