Radiofisik

my knowledge base

PostGis

Для использования PostGis с EF Core установим из nuget NetTopologySuite и плагин на Postgres. Добавим в код контекста

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseNpgsql("Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=postgres",
                o => o.UseNetTopologySuite());
        }

protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.HasPostgresExtension("postgis");
        }

создадим сущность с использованием геоданных

 public class City
    {
        public int Id { get; set; }
        public string Name { get; set; }

        [Column(TypeName = "geometry (point)")]
        public Point Location { get; set; }
    }

добавим данные в таблицу

var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var cities = new List<City>()
    {
        new City() {Name = "Kaliningrad", Location = geometryFactory.CreatePoint(new Coordinate(20.4640505, 54.7406165))},
        new City() {Name = "Pionerskiy", Location = geometryFactory.CreatePoint(new Coordinate(20.2377613, 54.9487813))}
    };

var context = new BloggingContext();
await context.Database.MigrateAsync();

context.Cities.Add(cities);
context.SaveChanges();

Тут важно заметить что для задания координат используется система координат с SRID 4326 (WGS 84) используемую в GPS. Координаты представлены в виде (X, Y) где Х - долгота, Y - широта, что выглядит не привычно, потому что обычно они следуют в обратном порядке.

Пусть есть точка с координатами 54.718202, 20.4976303 Найдем города, расстояние до которых меньше 10км.

var cities = context.Cities.Where(c => c.Location.Distance(work) < 10/111.0).ToList();

Тут используется то приближение что в одном градусе примерно 111 километров, а .Location.Distance() преобразуется в функцию ST_Distance, которая возвращает результат в градусах.

Теперь пусть есть точка с координатами 54.718202, 20.4976303 определим расстояние до городов

SELECT "Name", ST_AsEWKT("Location"),
       ST_DistanceSphere(
         "Location",
         ST_GeomFromEWKT('SRID=4326;POINT(20.4976303 54.718202)')
       )  AS dist
FROM "Cities" ORDER BY dist LIMIT 3;


Kaliningrad	SRID=4326;POINT(20.4640505 54.7406165)	3295.56496736
Pionerskiy	SRID=4326;POINT(20.2377613 54.9487813)	30567.22264395

тут используется функция ST_DistanceSphere которая вычисляет расстояние в метрах в отличие от ST_Distance. Для повышения точности можно использовать функцию ST_DistanceSpheroid которая принимает параметром параметры геоида SPHEROID["WGS 84",6378137,298.257223563]

SELECT "Name", ST_AsEWKT("Location"),
       ST_DistanceSpheroid(
         "Location",
         ST_GeomFromEWKT('SRID=4326;POINT(20.4976303 54.718202)'),
           'SPHEROID["WGS 84",6378137,298.257223563]'
       )  AS dist
FROM "Cities" ORDER BY dist LIMIT 3;

Kaliningrad	SRID=4326;POINT(20.4640505 54.7406165)	3302.3994771128814
Pionerskiy	SRID=4326;POINT(20.2377613 54.9487813)	30621.986248194833

Более оптимально использовать функцию ST_DWithin, которая умеет работать с индексами

CREATE INDEX cities_idx ON "Cities" USING gist("Location");

SELECT "Name", ST_AsEWKT("Location")
FROM "Cities"
WHERE ST_DWithin(
        "Location",
        ST_GeomFromEWKT('SRID=4326;POINT(20.4976303 54.718202)'),
        0.1);



Index Scan using cities_idx on "Cities"  (cost=0.14..10.29 rows=1 width=43)
  Index Cond: ("Location" && '...'::geometry)
  Filter: (('...'::geometry && st_expand("Location", '0.100000000000000006'::double precision)) AND _st_dwithin("Location", '...'::geometry, '0.100000000000000006'::double precision))

Использовались материалы https://eax.me/postgis/

Для отображения геоданных можно использовать QGIS https://habr.com/ru/post/321710/

Cсылка на тестовый проект https://github.com/Radiofisik/PostGisTest