Calculando a distância entre dois pontos com .Net 6
1
0

Calculando a distância entre dois pontos com .Net 6

Aplicativos como IFood, Uber Eats, Google Places pegam a sua localização e mostram os locais próximos da sua posição, isto pode ser feito de forma simples e rápida usando .Net 6 + SQL Server +  EF Core + NetTopologySuite

Fernando Guerra
5 min
1
0
Email image

Aplicativos como IFood, Uber Eats, Google Places pegam a sua localização e mostram os locais próximos da sua posição, isto pode ser feito de forma simples e rápida usando .Net 6 + SQL Server +  EF Core + NetTopologySuite

Hoje iremos desenvolver uma api que a partir da latitude e longitude passada irá listar os locais mais próximos e a distância entre a sua posição e o local.

Para este exemplo iremos usar:

Abra o Visual Studio e crie um novo projeto

Email image

Selecione o projeto ASP.NET Core Web API e clique em próximo

Email image

De um nome para o seu projeto e clique em próximo

Email image

Defina as configurações como abaixo e clique em criar

Email image

Em seu Solution Explorer deve ter os arquivos da imagem abaixo

Email image

Vamos criar nosso repositorio do Git

Clique com o botão direito sobre a Solução e Criar Repositório do Git

Email image

Configure o Git como a imagem abaixo

Não esqueça de alterar o .gitignore para Visual Studio, definir o nome a descrição e clique em create e push

Email image

Acesse o seu git e verá que foi criado o seu repositório e foi feito um push do código inicial

Email image
Email image

Bora codar nossa api, mas antes execute seu projeto para ver se esta tudo ok

Email image

Ao executar seu projeto se apareceu o swagger execute o mesmo e verifique se esta recebendo uma resposta da sua api

Email image

Pare sua api e vamos codar

Apague os arquivos WeatherForecastController e WeatherForecast

Email image

Vamos instalar as bibliotecas em nosso projeto, clique com o botão direito em dependencies e Manage Nuget Packages

Email image

Irá abrir a tela abaixo, selecione Browse e procure pela dependência, informe a versão e instalar vamos instalar uma por uma

Email image

Bibliotecas instaladas

Email image

Vamos criar uma pasta Dominios onde irá ficar nossa classe que irá representar o local e dentro dela nossa classe Local

Email image

Clique com o botão direito na pasta Dominios e crie uma classe

Email image

Defina o nome e clique em Add

Email image
Email image

Nossa classe Local irá ficar como o código abaixo:

using NetTopologySuite.Geometries;
using System.ComponentModel.DataAnnotations.Schema;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Dominios
{
[Table("Locais")]
public class Local
{
public Guid Id { get; set; }
public string Nome { get; set; }
public string Endereco { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public Point LatLong { get; set; }
}
}

Vamos criar uma pasta para nossas Interfaces e criar uma interface com o nome ILocalRepositorio, implemente o código abaixo:

Nossa interface terá apenas um método que retornar os locais mais próximos de um determinado ponto e dentro de um faixa de distancia

using CorujasDev.CalcularDistanciaLatLog.Api.Dominios;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Interfaces
{
public interface ILocalRepositorio
{
/// <summary>
/// Lista os locais mais próximos de um determinado local
/// </summary>
/// <param name="latitude">Latitude do local que deseja se basear</param>
/// <param name="longitude">Longitude do local que deseja se basear</param>
/// <param name="km">Raio de distancia que deseja buscar os locais mais próximos</param>
/// <returns></returns>
public ICollection<Local> ListarPorLatitudeLongitude(double latitude, double longitude, int metros = 1000);
}
}

Vamos criar nosso contexto e criar nosso banco de dados

Crie uma pasta com o nome Contextos e dentro uma classe DbLocalContext

Email image

Bora implementar nosso contexto

Herda o DbContext da Biblioteca Microsoft.EntityFrameworkCore

Define o DbSet Locais que representa nossa tabela no banco

Sobrescrevemos o método OnConfiguring para definir a nossa conexão com o banco de dados, no caso SQL Server

using CorujasDev.CalcularDistanciaLatLog.Api.Dominios;
using Microsoft.EntityFrameworkCore;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Contextos
{
public class DbLocalContext : DbContext
{
public DbSet<Local> Locais { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(@"Data Source=.\SQLEXPRESS;Initial Catalog=Local_Dev;Integrated Security=True",
x => x.UseNetTopologySuite()
);
}
}
}
}

Vamos gerar nosso banco através do entity framework

Abra o Nuget Package Console - Tools -> Nuget Package Manager -> Package Manager Nuget

Email image

No package manage console digite:

add-migration Banco-Inicial + Enter

Email image

Será criada a pasta migrations com dois arquivos dentro como na firgura

Email image

O arquivo Banco-Inicial contém o script para geração do banco de dados

Email image

No package manage console digite update-database + enter

Email image

Aparecendo Done informa que o banco foi criado e pode ser usado

Vamos conectar em nosso banco de dados, vai no menu tools → Connect Database

Email image

Informe o Server Name e selecione o banco de dados que criou

Email image

Verificar no Server Explorer o banco e a tabela Locais

Email image

Com o banco criado vamos popular o mesmo, para isto vamos sobrescrever o método OnModelCreating em nossa classe DbLocalContext, usei a latitude e longitude de locais próximos a mim, inclua o código abaixo na classe DbLocalContext

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Local>().HasData(
new Local { Id = Guid.NewGuid(), Nome = "Hotel Luar", Endereco = "Av. Celso Garcia, 5836 - Tatuapé, São Paulo - SP, 03064-000", Latitude = -23.5307344, Longitude = -46.5588578, LatLong = new Point(-23.5307344, -46.5588578) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "O Boticário", Endereco = "R. Antônio de Barros, 203 - Tatuapé, São Paulo - SP, 03089-000", Latitude = -23.5321539, Longitude = -46.5621212, LatLong = new Point(-23.5321539, -46.5621212) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Kanguru Supermercado", Endereco = "R. Antônio de Barros, 285 - Tatuapé, São Paulo - SP, 03089-000", Latitude = -23.5328637, Longitude = -46.5618107, LatLong = new Point(-23.5328637, -46.5618107) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Cacau Show", Endereco = "R. Antônio de Barros, 213 - Tatuapé, São Paulo - SP, 03089-000", Latitude = -23.5322009, Longitude = -46.5620454, LatLong = new Point(-23.5322009, -46.5620454) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Lopes Supermercados", Endereco = "R. São Jorge, 168 - Parque São Jorge, São Paulo - SP, 03087-000", Latitude = -23.5319057, Longitude = -46.5669481, LatLong = new Point(-23.5319057, -46.5669481) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Casa do Pastel Tatuapé", Endereco = "R. Tuiuti, 1807 - Tatuapé, São Paulo - SP, 03307-000", Latitude = -23.5413278, Longitude = -46.5774019, LatLong = new Point(-23.5413278, -46.5774019) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Pró3 Academia - Vila Carrão", Endereco = "R. Azevedo Soares, 2042 - Tatuape, São Paulo - SP, 03322-002", Latitude = -23.5471431, Longitude = -46.5593086, LatLong = new Point(-23.5471431, -46.5593086) { SRID = 4326 } },
new Local { Id = Guid.NewGuid(), Nome = "Hotel Flert Tatuapé", Endereco = "R. Bom Sucesso, 510 - Cidade Mãe do Céu, São Paulo - SP, 03305-000", Latitude = -23.5450925, Longitude = -46.5787504, LatLong = new Point(-23.5450925, -46.5787504) { SRID = 4326 } }
);
base.OnModelCreating(modelBuilder);
}

No package manage console digite Add-Migration SeedInitialData para criar um novo arquivo com o script para inserção dos dados no banco

Deve ter um novo arquivo SeedInitialData na pasta Migrations e o seu código parecido com o abaixo

Email image

Agora digite update-database e verifique seu banco de dados

Email image

Para vizualizar os dados clique sobre a tabela no Server Explorer e em Show Table Data

Email image
Email image

Vamos implementar nosso repositório, crie uma pasta Repositorios no projeto e dentro uma classe LocalRepositorio

Email image

Implemente o código abaixo:

using CorujasDev.CalcularDistanciaLatLog.Api.Contextos;
using CorujasDev.CalcularDistanciaLatLog.Api.Dominios;
using CorujasDev.CalcularDistanciaLatLog.Api.Interfaces;
using NetTopologySuite;
using NetTopologySuite.Geometries;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Repositorios
{
public class LocalRepositorio : ILocalRepositorio
{
protected new readonly DbLocalContext _context;
public LocalRepositorio()
{
_context = new DbLocalContext();
}
public ICollection<Local> ListarPorLatitudeLongitude(double latitude, double longitude, int metros = 1000)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var myLocation = geometryFactory.CreatePoint(new Coordinate(latitude, longitude));
var locais = _context.Locais.Where(x => x.LatLong != null);
return locais.OrderBy(x => x.LatLong.Distance(myLocation))
.Where(x => x.LatLong.IsWithinDistance(myLocation, metros))
.ToList();
}
}
}

Vamos criar nossos Data Transfers Objects, no caso será um para o request e outro para o response

Crie uma pasta DataTransferObject e dentro duas classes LocalRequestDTO e LocalResponseDTO

Email image

Implemente o código abaixo na classe LocalRequestDTO

Estamos obrigando a passar a Latitude e Longitude e caso não passe e gerado um erro, no caso dos metros caso não seja informado ele por default será 1000.

using System.ComponentModel.DataAnnotations;
namespace CorujasDev.CalcularDistanciaLatLog.Api.DataTransferObject
{
public class LocalRequestDTO
{
[Required(ErrorMessage = "Informe a latitude")]
public double Latitude { get; set; }
[Required(ErrorMessage = "Informe a longitude")]
public double Longitude { get; set; }
public int Metros { get; set; } = 1000;
}
}

Nossa classe LocalResponseDTO irá ficar como o código abaixo:

namespace CorujasDev.CalcularDistanciaLatLog.Api.DataTransferObject
{
public class LocalResponseDTO
{
public Guid Id { get; set; }
public string Nome { get; set; }
public string Endereco { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public double Distancia { get; set; }
}
}

Vamos criar o Controller LocalController, clique com o botão direitos sobre a pasta Controllers e selecione Controller

Email image

Selecione Api Controller - Empty e clique em próximo

Email image

Informe o nome e clique em Adicionar

Email image

Controller criado

Email image

Vamos injetar nossa dependência da Interface ILocalRepositorio como o código abaixo:

using CorujasDev.CalcularDistanciaLatLog.Api.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LocalController : ControllerBase
{
private readonly ILocalRepositorio _localRepositorio;
public LocalController(ILocalRepositorio localRepositorio)
{
_localRepositorio = localRepositorio;
}
}
}

Não esqueça de configurar a Interface na classe Program.cs

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddTransient<ILocalRepositorio, LocalRepositorio>();
var app = builder.Build();

Em nossa classe LocalController vamos incluir os métodos que irão fazer o calculo da distancia

using CorujasDev.CalcularDistanciaLatLog.Api.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LocalController : ControllerBase
{
private readonly ILocalRepositorio _localRepositorio;
public LocalController(ILocalRepositorio localRepositorio)
{
_localRepositorio = localRepositorio;
}
public static double Radians(double x)
{
return x * Math.PI / 180;
}
public static double GreatCircleDistance(double lon1, double lat1, double lon2, double lat2)
{
double R = 6371e3; // m
double sLat1 = Math.Sin(Radians(lat1));
double sLat2 = Math.Sin(Radians(lat2));
double cLat1 = Math.Cos(Radians(lat1));
double cLat2 = Math.Cos(Radians(lat2));
double cLon = Math.Cos(Radians(lon1) - Radians(lon2));
double cosD = sLat1 * sLat2 + cLat1 * cLat2 * cLon;
double d = Math.Acos(cosD);
double dist = R * d;
return dist;
}
}
}

E por último mas não menos importante vamos implementar nosso método Get

using CorujasDev.CalcularDistanciaLatLog.Api.DataTransferObject;
using CorujasDev.CalcularDistanciaLatLog.Api.Interfaces;
using Microsoft.AspNetCore.Mvc;
using NetTopologySuite;
using NetTopologySuite.Geometries;
namespace CorujasDev.CalcularDistanciaLatLog.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LocalController : ControllerBase
{
private readonly ILocalRepositorio _localRepositorio;
public LocalController(ILocalRepositorio localRepositorio)
{
_localRepositorio = localRepositorio;
}
[HttpGet]
public async Task<IActionResult> Get([FromQuery] LocalRequestDTO request, CancellationToken cancelationToken)
{
var locais = _localRepositorio.ListarPorLatitudeLongitude(request.Latitude, request.Longitude, request.Metros);
if (locais == null) {
return await Task.FromResult(NotFound());
}
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var currentLocation = geometryFactory.CreatePoint(new Coordinate(request.Latitude, request.Longitude));
var locaisOrdenadosDistancia = locais
.ToList()
.Select(local => new LocalResponseDTO(){
Id = local.Id,
Nome = local.Nome,
Endereco = local.Endereco,
Latitude = local.Latitude,
Longitude = local.Longitude,
Distancia = GreatCircleDistance(local.Longitude, local.Latitude, request.Longitude, request.Latitude)
})
.OrderBy(x => x.Distancia);
return await Task.FromResult(Ok(locaisOrdenadosDistancia));
}
public static double Radians(double x) {
return x * Math.PI / 180;
}
public static double GreatCircleDistance(double lon1, double lat1, double lon2, double lat2){
double R = 6371e3; // m
double sLat1 = Math.Sin(Radians(lat1));
double sLat2 = Math.Sin(Radians(lat2));
double cLat1 = Math.Cos(Radians(lat1));
double cLat2 = Math.Cos(Radians(lat2));
double cLon = Math.Cos(Radians(lon1) - Radians(lon2));
double cosD = sLat1 * sLat2 + cLat1 * cLat2 * cLon;
double d = Math.Acos(cosD);
double dist = R * d;
return dist;
}
}
}

Execute o projeto, com o swagger informe a latitude e a longitude, no caso estou usando -23.5313603, -46.5580917 e clique em executar, aumente e diminua a distância para ver se esta realmente seguindo a regra

Email image

Response

[
{
"id": "edb19deb-db32-4b16-91fd-5bb6d6db0d76",
"nome": "Hotel Luar",
"endereco": "Av. Celso Garcia, 5836 - Tatuapé, São Paulo - SP, 03064-000",
"latitude": -23.5307344,
"longitude": -46.5588578,
"distancia": 104.61240935330473
},
{
"id": "abd81ad5-df34-4343-9528-93ebd12e0cad",
"nome": "Cacau Show",
"endereco": "R. Antônio de Barros, 213 - Tatuapé, São Paulo - SP, 03089-000",
"latitude": -23.5322009,
"longitude": -46.5620454,
"distancia": 413.76687880466574
},
{
"id": "616845fc-3022-41c9-bdcd-a8ac6056ba76",
"nome": "Kanguru Supermercado",
"endereco": "R. Antônio de Barros, 285 - Tatuapé, São Paulo - SP, 03089-000",
"latitude": -23.5328637,
"longitude": -46.5618107,
"distancia": 414.36138615495594
},
{
"id": "ecfc0b6b-7cb2-4761-9390-b716d13586d7",
"nome": "O Boticário",
"endereco": "R. Antônio de Barros, 203 - Tatuapé, São Paulo - SP, 03089-000",
"latitude": -23.5321539,
"longitude": -46.5621212,
"distancia": 420.16985809644063
},
{
"id": "72397a9a-6a5f-4de3-a1f7-3345ff4a6ce6",
"nome": "Lopes Supermercados",
"endereco": "R. São Jorge, 168 - Parque São Jorge, São Paulo - SP, 03087-000",
"latitude": -23.5319057,
"longitude": -46.5669481,
"distancia": 904.92611440529
},
{
"id": "21a061db-c9b2-4257-a092-01aa4fcf4584",
"nome": "Pró3 Academia - Vila Carrão",
"endereco": "R. Azevedo Soares, 2042 - Tatuape, São Paulo - SP, 03322-002",
"latitude": -23.5471431,
"longitude": -46.5593086,
"distancia": 1759.3462982283133
}
]

Não esqueça de criar um push para o GitHub

Espero que tenha gostado

Código fonte

Para receber mais conteúdo inscreva-se no canal e siga nas redes sociais 

Vamos que vamos