318 lines
9.0 KiB
C#
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;
|
|
}
|
|
}
|
|
} |