通过EF Core和NetTopologySuite使用半径按区域搜索

问题描述

我正在构建一个应用程序,该应用程序需要能够根据企业的“交货区域”进行搜索

例如,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联盟出版。”