Winner's Excogitations

A chronicle of the thoughts, learning experiences, ideas and actions of a tech junkie, .NET, JS and Mobile dev, aspiring entrepreneur, devout Christian and travel enthusiast.

[HOW TO] Run A Geospatial Search With EF Core and Npgsql

4 years ago ยท 3 minutes read

bolorundurowb_com/production/article/rxhkrbkadavczfvfuphg

Introduction

As a developer, progressing in your career means building more and more complex applications with more strenuous requirements. That may include applications that have geospatial requirements. A common scenario you may face is finding entities which fall within a given geographic boundary provided by Google Maps or any other mapping service.

For this guide, we'd be examining a hypothetical scenario where we have a bunch of restaurant entities that we need to display on a map. We'd be demonstrating this using C#, Entity Framework Core, Npgsql and Postgres.

Setup

For this, I'll assume that you have the .NET Core runtime installed on your local machine (if you don't, then this installation walkthrough should help), at some experience with C# and .NET Core as well as having an ASP.NET Core Project setup.

If you don't already have Entity Framework and Npgsql installed in your projects, then you'd need to run these commands to get the packages installed:

$ dotnet add package Microsoft.EntityFrameworkCore
$ dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

we also need to add support for geospatial types by adding this package as well:

$ dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite

For any project utilizing Entity Framework, we'd need a database context. That is a specialized class that details the collections that our database "understands" or has "context" on. I won't be going into a lot of detail about database contexts as I assume that you understand that fairly well already. So for this guide, we'd have a database context file called AppDbContext.cs and should contain:

using Microsoft.EntityFrameworkCore;

public class AppDbContext : DbContext
{
  public DbSet<Restaurant> Restaurants { get; }

  public AppDbContext(DbContextOptions<Restaurant> options): base(options)
  {
  }

  // you'd also need the PostGIS extension added
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.HasPostgresExtension("postgis");
  }
}

If you copied and pasted the code above into your text editor or IDE, you would most definitely have some red squiggly lines under the Restaurant references as that class does not exist yet. We'd be adding that as well. To our Restaurant.cs file, we add the following:

using NetTopologySuite.Geometries;

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

  // to persist the geo location of each restaurant
  public Point Location { get; set; }
}

You'd also need to ensure that the database connection configuration is setup to enable querying for and by geospatial info. So in your Startup.cs file, you need to have a line akin to:

...

public void ConfigureServices(IServiceCollection services)
{
  ...

  services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql("<your db connection string>", builder =>
    {
      builder.UseNetTopologySuite();
    }));

  ...
}

Querying

Usually, when working with mapping services like Google Maps or MapBox or Bing Maps, the visible bounds of the map are provided as the northern latitude, the southern latitude, the east longitude and west longitude as decimal values. With those values given, we can query against our database to find out which of our restaurants fall within the visible map view.

To query, we need to construct coordinates of the four corners of the view as well as create a polygon defini9ng the area to search. With that, our search method would look something like this:

// populate this through DI via your constructor
private readonly AppDbContext _dbContext;
private readonly GeometryFactory _geometryFactory =
   new(new PrecisionModel(), 4326); // 4326 represents WGS 84

...

public async Task<List<Restaurant>> GetRestaurantsInView(double north, double east, double south, double west)
{
  var nw = new Coordinate(west, north);
  var ne = new Coordinate(east, north);
  var sw = new Coordinate(west, south);
  var se = new Coordinate(east, south);
  var polygon = new Polygon(new LinearRing(new[] {ne, nw, sw, se, ne}), Array.Empty<LinearRing>(),
    _geometryFactory);

  return _dbContext.Restaurant
    .Where(x => x.Location.Within(polygon))
    .ToListAsync();
}

If you wanted to exclude some interior section of the given area, then you'd need to replace Array.Empty<LinearRing>() with the dimensions of the interior sections.

Conclusion

That's about it. I created this guide because I could not find a simple walkthrough around querying against geospatial fields and the library documentation pages don't help much.

Cheers.

Share on:
Xamarin Forms: How To Use Custom Fonts
How to use custom fonts in a Xamarin Forms app
[HOW TO] Convert An Angular Component Into HTML
Walkthrough on converting a defined component into valid rendered HTML.
Winner-Timothy Bolorunduro
Winner-Timothy Bolorunduro is a senior .NET developer with over 6 years experience helping organizations and individuals build compelling, stable and scalable web applications. Having spent the last three years in a fast-paced startup environment working remotely, he understands what goes into being part of a team that produces value for clients.

Comments