agrobot_base/AgroBase/OperationControl/Mapas/Services/MapaContratoImportService.cs

318 lines
9.0 KiB
C#

using AgroBase.Models;
using NetTopologySuite.Geometries;
using OperationControl.Mapas.Models;
using static AgroBase.Models.Enums;
namespace OperationControl.Mapas.Services
{
/// <summary>
/// Reconstrói um MapaDocumento a partir do contrato
/// operacional utilizado na comunicação base/rover.
///
/// Este serviço não lê arquivos físicos.
/// </summary>
public sealed class MapaContratoImportService
{
private readonly GeometryFactory _geometryFactory =
new GeometryFactory(
new PrecisionModel(),
4326
);
private readonly MapaValidationService
_validationService;
public MapaContratoImportService(
MapaValidationService? validationService = null)
{
_validationService =
validationService
?? new MapaValidationService();
}
public MapaDocumento Importar(
MapaFeatureCollectionModel mapa,
TipoMapaOperacao tipo,
string nome = "Mapa operacional recebido")
{
if (mapa == null)
{
throw new ArgumentNullException(
nameof(mapa)
);
}
if (mapa.features == null ||
mapa.features.Count == 0)
{
throw new InvalidOperationException(
"O mapa recebido não possui ruas."
);
}
var documento =
new MapaDocumento
{
Nome =
string.IsNullOrWhiteSpace(nome)
? "Mapa operacional recebido"
: nome.Trim(),
CaminhoOrigem = null,
FormatoOrigem =
MapaFormatoOrigem
.ContratoOperacional,
Tipo = tipo,
Modificado = false
};
var idsUsados =
new HashSet<string>(
StringComparer.OrdinalIgnoreCase
);
for (
int i = 0;
i < mapa.features.Count;
i++)
{
var feature =
mapa.features[i];
if (feature == null)
{
throw new InvalidOperationException(
$"A feature {i + 1} é nula."
);
}
if (feature.geometry == null)
{
throw new InvalidOperationException(
$"A feature {i + 1} não possui geometria."
);
}
if (!string.Equals(
feature.geometry.type,
"LineString",
StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(
$"A feature {i + 1} não é uma LineString."
);
}
if (feature.geometry.coordinates == null ||
feature.geometry.coordinates.Count < 2)
{
throw new InvalidOperationException(
$"A feature {i + 1} não possui pontos suficientes."
);
}
/*
* IMPORTANTE:
*
* properties.Id é autoritativo.
*
* O fluxo legado já produziu mapas onde
* geometry.id podia divergir de properties.Id.
*
* RuasPercorrer e a trajetória do rover trabalham
* pelo properties.Id.
*/
string? idProperties =
feature.properties?
.Id?
.Trim();
string? idGeometry =
feature.geometry?
.id?
.Trim();
string? id =
!string.IsNullOrWhiteSpace(
idProperties)
? idProperties
: idGeometry;
if (string.IsNullOrWhiteSpace(id))
{
throw new InvalidOperationException(
$"A feature {i + 1} não possui ID."
);
}
if (
!string.IsNullOrWhiteSpace(idProperties) &&
!string.IsNullOrWhiteSpace(idGeometry) &&
!string.Equals(
idProperties,
idGeometry,
StringComparison.OrdinalIgnoreCase
)
)
{
documento.AvisosImportacao.Add(
$"Feature {i + 1}: " +
$"properties.Id='{idProperties}' e " +
$"geometry.id='{idGeometry}' divergem. " +
$"Foi utilizado properties.Id."
);
}
if (!idsUsados.Add(id))
{
throw new InvalidOperationException(
$"O mapa recebido possui o ID '{id}' duplicado."
);
}
var coordenadas =
new List<Coordinate>();
for (
int p = 0;
p <
feature.geometry.coordinates.Count;
p++)
{
var ponto =
feature.geometry.coordinates[p];
if (ponto == null ||
ponto.Count < 2)
{
throw new InvalidOperationException(
$"A feature '{id}', ponto {p + 1}, " +
"possui coordenada incompleta."
);
}
double longitude =
ponto[0];
double latitude =
ponto[1];
if (
!double.IsFinite(longitude) ||
!double.IsFinite(latitude) ||
longitude < -180.0 ||
longitude > 180.0 ||
latitude < -90.0 ||
latitude > 90.0
)
{
throw new InvalidOperationException(
$"A feature '{id}', ponto {p + 1}, " +
"possui coordenada WGS84 inválida."
);
}
coordenadas.Add(
new Coordinate(
longitude,
latitude
)
);
}
LineString linha =
_geometryFactory
.CreateLineString(
coordenadas.ToArray()
);
documento.Ruas.Add(
new MapaRua
{
Id = id,
IdOriginal = id,
IdGeradoAutomaticamente =
false,
Nome =
string.IsNullOrWhiteSpace(
feature.properties?.Name)
? $"Rua {id}"
: feature.properties.Name,
Geometria =
linha,
/*
* Contrato já enviado pelo próprio sistema.
* Posteriormente o GeometryService vai
* centralizar este cálculo.
*/
ComprimentoMetros =
feature.properties?.Length ?? 0.0,
Dist1 =
feature.properties?.Dist1 ?? 0.0,
Dist2 =
feature.properties?.Dist2 ?? 0.0,
IndiceFeatureOrigem =
i + 1
}
);
}
var validacao =
_validationService.Validar(
documento
);
if (!validacao.Valido)
{
throw new InvalidOperationException(
"O mapa operacional recebido é inválido:" +
Environment.NewLine +
string.Join(
Environment.NewLine,
validacao.Erros
)
);
}
return documento;
}
}
}