using AgroBase.Models;
using Newtonsoft.Json;
using OperationControl.Mapas.Models;
using System.IO;
using System.Text;
namespace OperationControl.Mapas.Services
{
public class MapaExportService
{
private readonly MapaValidationService
_validationService;
public MapaExportService(
MapaValidationService? validationService = null)
{
_validationService =
validationService
?? new MapaValidationService();
}
///
/// Converte o documento canônico do Operation Control
/// para o contrato atualmente consumido pelo rover.
///
/// Nenhuma referência geométrica do documento é compartilhada
/// com o objeto exportado.
///
public MapaFeatureCollectionModel
CriarMapaOperacional(
MapaDocumento documento)
{
MapaResultadoValidacao validacao =
_validationService.Validar(
documento
);
if (!validacao.Valido)
{
string erros =
string.Join(
Environment.NewLine,
validacao.Erros
);
throw new InvalidOperationException(
"O mapa não pode ser exportado porque " +
"possui erros de validação:" +
Environment.NewLine +
erros
);
}
var mapa =
new MapaFeatureCollectionModel
{
type =
"FeatureCollection",
features =
new List()
};
foreach (
MapaRua rua
in documento.Ruas)
{
var coordenadas =
new List>();
foreach (
var coordenada
in rua.Geometria.Coordinates)
{
/*
* Contrato GeoJSON / rover:
*
* [longitude, latitude]
*/
coordenadas.Add(
new List
{
coordenada.X,
coordenada.Y
}
);
}
/*
* Esta igualdade é proposital.
*
* A partir da arquitetura nova existe apenas
* UM ID canônico para a rua.
*/
string id =
rua.Id;
mapa.features.Add(
new MapaFeatureModel
{
type =
"Feature",
properties =
new MapaFeaturePropertiesModel
{
Id =
id,
Name =
string.IsNullOrWhiteSpace(
rua.Nome
)
? $"Rua {id}"
: rua.Nome,
Length =
rua.ComprimentoMetros,
Dist1 =
rua.Dist1,
Dist2 =
rua.Dist2
},
geometry =
new MapaFeatureGeometryModel
{
id =
id,
type =
"LineString",
coordinates =
coordenadas
}
}
);
}
return mapa;
}
///
/// Persiste o documento canônico como GeoJSON operacional.
///
/// O arquivo contém somente o mapa consolidado:
/// ruas, IDs, atributos e geometrias já editadas.
///
/// Estado de UI, seleção da missão, Undo/Redo, borda GNSS
/// e rota de retorno não fazem parte deste arquivo.
///
public void SalvarGeoJson(
MapaDocumento documento,
string caminhoArquivo)
{
if (documento == null)
{
throw new ArgumentNullException(
nameof(documento)
);
}
if (string.IsNullOrWhiteSpace(
caminhoArquivo))
{
throw new ArgumentException(
"O caminho do arquivo GeoJSON não foi informado.",
nameof(caminhoArquivo)
);
}
string caminhoCompleto =
Path.GetFullPath(
caminhoArquivo
);
string extensao =
Path.GetExtension(
caminhoCompleto
);
if (
!string.Equals(
extensao,
".geojson",
StringComparison.OrdinalIgnoreCase
) &&
!string.Equals(
extensao,
".json",
StringComparison.OrdinalIgnoreCase
)
)
{
throw new InvalidOperationException(
"O mapa operacional deve ser salvo como .geojson ou .json."
);
}
string? diretorio =
Path.GetDirectoryName(
caminhoCompleto
);
if (
string.IsNullOrWhiteSpace(
diretorio
) ||
!Directory.Exists(
diretorio
)
)
{
throw new DirectoryNotFoundException(
"A pasta escolhida para salvar o mapa não existe."
);
}
/*
* CriarMapaOperacional valida novamente o documento
* e garante o contrato canônico usado pelo rover:
*
* properties.Id == geometry.id
* coordinates = [longitude, latitude]
*/
MapaFeatureCollectionModel mapa =
CriarMapaOperacional(
documento
);
string json =
JsonConvert.SerializeObject(
mapa,
Formatting.Indented
);
/*
* Escrita transacional simples:
* grava primeiro um temporário na mesma pasta e somente
* depois cria/substitui o arquivo final.
*/
string caminhoTemporario =
Path.Combine(
diretorio,
"." +
Path.GetFileName(
caminhoCompleto
) +
"." +
Guid.NewGuid().ToString("N") +
".tmp"
);
try
{
File.WriteAllText(
caminhoTemporario,
json,
new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false
)
);
File.Move(
caminhoTemporario,
caminhoCompleto,
overwrite: true
);
}
finally
{
if (File.Exists(
caminhoTemporario))
{
try
{
File.Delete(
caminhoTemporario
);
}
catch
{
// O arquivo final já salvo continua válido.
}
}
}
}
}
}