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
|
|
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
|
|
Selecione o projeto ASP.NET Core Web API e clique em próximo
|
|
De um nome para o seu projeto e clique em próximo
|
|
Defina as configurações como abaixo e clique em criar
|
|
Em seu Solution Explorer deve ter os arquivos da imagem abaixo
|
|
Vamos criar nosso repositorio do Git
Clique com o botão direito sobre a Solução e Criar Repositório do Git
|
|
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
|
|
Acesse o seu git e verá que foi criado o seu repositório e foi feito um push do código inicial
|
|
|
|
Bora codar nossa api, mas antes execute seu projeto para ver se esta tudo ok
|
|
Ao executar seu projeto se apareceu o swagger execute o mesmo e verifique se esta recebendo uma resposta da sua api
|
|
Pare sua api e vamos codar
Apague os arquivos WeatherForecastController e WeatherForecast
|
|
Vamos instalar as bibliotecas em nosso projeto, clique com o botão direito em dependencies e Manage Nuget Packages
|
|
Irá abrir a tela abaixo, selecione Browse e procure pela dependência, informe a versão e instalar vamos instalar uma por uma
|
|
Bibliotecas instaladas
|
|
Vamos criar uma pasta Dominios onde irá ficar nossa classe que irá representar o local e dentro dela nossa classe Local
|
|
Clique com o botão direito na pasta Dominios e crie uma classe
|
|
Defina o nome e clique em Add
|
|
|
|
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
|
|
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
|
|
No package manage console digite:
add-migration Banco-Inicial + Enter
|
|
Será criada a pasta migrations com dois arquivos dentro como na firgura
|
|
O arquivo Banco-Inicial contém o script para geração do banco de dados
|
|
No package manage console digite update-database + enter
|
|
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
|
|
Informe o Server Name e selecione o banco de dados que criou
|
|
Verificar no Server Explorer o banco e a tabela Locais
|
|
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
|
|
Agora digite update-database e verifique seu banco de dados
|
|
Para vizualizar os dados clique sobre a tabela no Server Explorer e em Show Table Data
|
|
|
|
Vamos implementar nosso repositório, crie uma pasta Repositorios no projeto e dentro uma classe LocalRepositorio
|
|
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
|
|
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
|
|
Selecione Api Controller - Empty e clique em próximo
|
|
Informe o nome e clique em Adicionar
|
|
Controller criado
|
|
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
|
|
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
Para receber mais conteúdo inscreva-se no canal e siga nas redes sociais
Vamos que vamos