433 lines
9.8 KiB
C#
433 lines
9.8 KiB
C#
using OperationControl.Mapas.Models;
|
|
|
|
namespace OperationControl.Mapas.Services
|
|
{
|
|
/// <summary>
|
|
/// Gerencia a rota manual de retorno.
|
|
///
|
|
/// Não conhece Mapsui.
|
|
/// Não conhece WPF.
|
|
/// Não conhece mouse.
|
|
///
|
|
/// Internamente trabalha sempre com propriedades
|
|
/// explícitas Latitude/Longitude.
|
|
/// </summary>
|
|
public sealed class MapaRotaRetornoService
|
|
{
|
|
public const double
|
|
ToleranciaRemocaoPadraoMetros =
|
|
2.0;
|
|
|
|
|
|
/// <summary>
|
|
/// Alterna um ponto:
|
|
///
|
|
/// - se não existir ponto próximo, adiciona ao final;
|
|
/// - se existir ponto dentro da tolerância, remove o mais próximo.
|
|
///
|
|
/// Retorna true quando adicionou.
|
|
/// Retorna false quando removeu.
|
|
/// </summary>
|
|
public bool AlternarPonto(
|
|
MapaRotaRetorno rota,
|
|
double latitude,
|
|
double longitude,
|
|
double toleranciaMetros =
|
|
ToleranciaRemocaoPadraoMetros)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
ValidarCoordenada(
|
|
latitude,
|
|
longitude
|
|
);
|
|
|
|
if (
|
|
double.IsNaN(toleranciaMetros) ||
|
|
double.IsInfinity(toleranciaMetros) ||
|
|
toleranciaMetros < 0
|
|
)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(toleranciaMetros),
|
|
"A tolerância deve ser finita e não negativa."
|
|
);
|
|
}
|
|
|
|
|
|
var novo =
|
|
new MapaPontoRetorno(
|
|
latitude,
|
|
longitude
|
|
);
|
|
|
|
|
|
int indiceProximo =
|
|
EncontrarIndiceMaisProximo(
|
|
rota,
|
|
novo,
|
|
toleranciaMetros
|
|
);
|
|
|
|
|
|
if (indiceProximo >= 0)
|
|
{
|
|
rota.PontosMutaveis
|
|
.RemoveAt(
|
|
indiceProximo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
rota.PontosMutaveis.Add(
|
|
novo
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public void AdicionarPonto(
|
|
MapaRotaRetorno rota,
|
|
double latitude,
|
|
double longitude)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
ValidarCoordenada(
|
|
latitude,
|
|
longitude
|
|
);
|
|
|
|
|
|
rota.PontosMutaveis.Add(
|
|
new MapaPontoRetorno(
|
|
latitude,
|
|
longitude
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
public void Limpar(
|
|
MapaRotaRetorno rota)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
rota.PontosMutaveis.Clear();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Distância acumulada entre os pontos da rota.
|
|
///
|
|
/// Com zero ou um ponto, retorna 0.
|
|
/// </summary>
|
|
public double ObterDistanciaTotalMetros(
|
|
MapaRotaRetorno rota)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
|
|
if (rota.Quantidade < 2)
|
|
return 0.0;
|
|
|
|
|
|
double total =
|
|
0.0;
|
|
|
|
|
|
for (
|
|
int i = 1;
|
|
i < rota.Pontos.Count;
|
|
i++)
|
|
{
|
|
total += DistanciaMetros(
|
|
rota.Pontos[i - 1],
|
|
rota.Pontos[i]
|
|
);
|
|
}
|
|
|
|
|
|
return total;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Converte para o contrato legado atual do rover.
|
|
///
|
|
/// ATENÇÃO:
|
|
/// PontosRetorno trafega como:
|
|
///
|
|
/// [latitude, longitude]
|
|
///
|
|
/// Isso é diferente do GeoJSON:
|
|
///
|
|
/// [longitude, latitude]
|
|
///
|
|
/// Essa peculiaridade fica confinada SOMENTE aqui.
|
|
/// </summary>
|
|
public List<double[]> ExportarContrato(
|
|
MapaRotaRetorno rota)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
|
|
return rota.Pontos
|
|
.Select(
|
|
x =>
|
|
new[]
|
|
{
|
|
x.Latitude,
|
|
x.Longitude
|
|
}
|
|
)
|
|
.ToList();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Reconstrói a rota a partir do contrato recebido
|
|
/// do rover.
|
|
///
|
|
/// O método é transacional:
|
|
/// somente altera a rota depois que todos
|
|
/// os pontos forem validados.
|
|
/// </summary>
|
|
public void DefinirPeloContrato(
|
|
MapaRotaRetorno rota,
|
|
IEnumerable<double[]>?
|
|
pontosContrato)
|
|
{
|
|
ValidarRota(
|
|
rota
|
|
);
|
|
|
|
|
|
var novosPontos =
|
|
new List<MapaPontoRetorno>();
|
|
|
|
|
|
foreach (
|
|
double[] item
|
|
in pontosContrato
|
|
?? Enumerable.Empty<double[]>())
|
|
{
|
|
if (
|
|
item == null ||
|
|
item.Length < 2
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Foi recebido um ponto de retorno incompleto."
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Contrato atual:
|
|
*
|
|
* [0] = latitude
|
|
* [1] = longitude
|
|
*/
|
|
double latitude =
|
|
item[0];
|
|
|
|
double longitude =
|
|
item[1];
|
|
|
|
|
|
ValidarCoordenada(
|
|
latitude,
|
|
longitude
|
|
);
|
|
|
|
|
|
novosPontos.Add(
|
|
new MapaPontoRetorno(
|
|
latitude,
|
|
longitude
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
rota.PontosMutaveis.Clear();
|
|
|
|
rota.PontosMutaveis.AddRange(
|
|
novosPontos
|
|
);
|
|
}
|
|
|
|
|
|
private static int
|
|
EncontrarIndiceMaisProximo(
|
|
MapaRotaRetorno rota,
|
|
MapaPontoRetorno referencia,
|
|
double toleranciaMetros)
|
|
{
|
|
int melhorIndice =
|
|
-1;
|
|
|
|
double melhorDistancia =
|
|
double.PositiveInfinity;
|
|
|
|
|
|
for (
|
|
int i = 0;
|
|
i < rota.Pontos.Count;
|
|
i++)
|
|
{
|
|
double distancia =
|
|
DistanciaMetros(
|
|
referencia,
|
|
rota.Pontos[i]
|
|
);
|
|
|
|
|
|
if (
|
|
distancia <= toleranciaMetros &&
|
|
distancia < melhorDistancia
|
|
)
|
|
{
|
|
melhorIndice =
|
|
i;
|
|
|
|
melhorDistancia =
|
|
distancia;
|
|
}
|
|
}
|
|
|
|
|
|
return melhorIndice;
|
|
}
|
|
|
|
|
|
private static double DistanciaMetros(
|
|
MapaPontoRetorno a,
|
|
MapaPontoRetorno b)
|
|
{
|
|
const double raioTerra =
|
|
6371000.0;
|
|
|
|
|
|
double lat1 =
|
|
GrausParaRadianos(
|
|
a.Latitude
|
|
);
|
|
|
|
double lat2 =
|
|
GrausParaRadianos(
|
|
b.Latitude
|
|
);
|
|
|
|
double deltaLat =
|
|
GrausParaRadianos(
|
|
b.Latitude -
|
|
a.Latitude
|
|
);
|
|
|
|
double deltaLon =
|
|
GrausParaRadianos(
|
|
b.Longitude -
|
|
a.Longitude
|
|
);
|
|
|
|
|
|
double h =
|
|
Math.Sin(
|
|
deltaLat / 2.0
|
|
) *
|
|
Math.Sin(
|
|
deltaLat / 2.0
|
|
) +
|
|
Math.Cos(lat1) *
|
|
Math.Cos(lat2) *
|
|
Math.Sin(
|
|
deltaLon / 2.0
|
|
) *
|
|
Math.Sin(
|
|
deltaLon / 2.0
|
|
);
|
|
|
|
|
|
double c =
|
|
2.0 *
|
|
Math.Atan2(
|
|
Math.Sqrt(h),
|
|
Math.Sqrt(1.0 - h)
|
|
);
|
|
|
|
|
|
return raioTerra * c;
|
|
}
|
|
|
|
|
|
private static double
|
|
GrausParaRadianos(
|
|
double graus)
|
|
{
|
|
return graus *
|
|
Math.PI /
|
|
180.0;
|
|
}
|
|
|
|
|
|
private static void
|
|
ValidarCoordenada(
|
|
double latitude,
|
|
double longitude)
|
|
{
|
|
if (
|
|
double.IsNaN(latitude) ||
|
|
double.IsInfinity(latitude) ||
|
|
latitude < -90.0 ||
|
|
latitude > 90.0
|
|
)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(latitude),
|
|
"Latitude WGS84 inválida."
|
|
);
|
|
}
|
|
|
|
|
|
if (
|
|
double.IsNaN(longitude) ||
|
|
double.IsInfinity(longitude) ||
|
|
longitude < -180.0 ||
|
|
longitude > 180.0
|
|
)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(longitude),
|
|
"Longitude WGS84 inválida."
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
private static void ValidarRota(
|
|
MapaRotaRetorno rota)
|
|
{
|
|
if (rota == null)
|
|
{
|
|
throw new ArgumentNullException(
|
|
nameof(rota)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} |