314 lines
8.5 KiB
C#
314 lines
8.5 KiB
C#
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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<MapaFeatureModel>()
|
|
};
|
|
|
|
|
|
foreach (
|
|
MapaRua rua
|
|
in documento.Ruas)
|
|
{
|
|
var coordenadas =
|
|
new List<List<double>>();
|
|
|
|
|
|
foreach (
|
|
var coordenada
|
|
in rua.Geometria.Coordinates)
|
|
{
|
|
/*
|
|
* Contrato GeoJSON / rover:
|
|
*
|
|
* [longitude, latitude]
|
|
*/
|
|
coordenadas.Add(
|
|
new List<double>
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |