Merge branch 'produto' of http://zendioninc.com.br:3000/Zendion/agrobot_base into produto
This commit is contained in:
commit
bef19294e5
|
|
@ -1001,6 +1001,228 @@ namespace OperationControl.Mapas
|
|||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sugere um nome previsível para a versão persistida do
|
||||
/// DocumentoTrabalho sem alterar o documento nem a sessão.
|
||||
/// </summary>
|
||||
public string ObterNomeArquivoSugeridoMapaEditado()
|
||||
{
|
||||
MapaDocumento? documento =
|
||||
_estadoEdicao
|
||||
.DocumentoTrabalho;
|
||||
|
||||
|
||||
string nomeBase =
|
||||
string.Empty;
|
||||
|
||||
|
||||
if (
|
||||
documento != null &&
|
||||
!string.IsNullOrWhiteSpace(
|
||||
documento.CaminhoOrigem
|
||||
)
|
||||
)
|
||||
{
|
||||
nomeBase =
|
||||
System.IO.Path
|
||||
.GetFileNameWithoutExtension(
|
||||
documento.CaminhoOrigem
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
string.IsNullOrWhiteSpace(
|
||||
nomeBase
|
||||
)
|
||||
)
|
||||
{
|
||||
nomeBase =
|
||||
documento?.Nome;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
string.IsNullOrWhiteSpace(
|
||||
nomeBase
|
||||
)
|
||||
)
|
||||
{
|
||||
nomeBase =
|
||||
"mapa";
|
||||
}
|
||||
|
||||
|
||||
char[] invalidos =
|
||||
System.IO.Path
|
||||
.GetInvalidFileNameChars();
|
||||
|
||||
|
||||
nomeBase =
|
||||
new string(
|
||||
nomeBase
|
||||
.Select(
|
||||
c =>
|
||||
invalidos.Contains(c)
|
||||
? '_'
|
||||
: c
|
||||
)
|
||||
.ToArray()
|
||||
)
|
||||
.Trim();
|
||||
|
||||
|
||||
if (
|
||||
!nomeBase.EndsWith(
|
||||
"_operacional",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
)
|
||||
)
|
||||
{
|
||||
nomeBase +=
|
||||
"_operacional";
|
||||
}
|
||||
|
||||
|
||||
return
|
||||
nomeBase +
|
||||
".geojson";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Retorna a pasta do arquivo que originou o mapa somente
|
||||
/// como conveniência para o SaveFileDialog.
|
||||
/// </summary>
|
||||
public string? ObterDiretorioOrigemMapaEdicao()
|
||||
{
|
||||
string? caminhoOrigem =
|
||||
_estadoEdicao
|
||||
.DocumentoTrabalho?
|
||||
.CaminhoOrigem;
|
||||
|
||||
|
||||
if (
|
||||
string.IsNullOrWhiteSpace(
|
||||
caminhoOrigem
|
||||
)
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
string? diretorio =
|
||||
System.IO.Path
|
||||
.GetDirectoryName(
|
||||
caminhoOrigem
|
||||
);
|
||||
|
||||
|
||||
return
|
||||
!string.IsNullOrWhiteSpace(
|
||||
diretorio
|
||||
) &&
|
||||
System.IO.Directory.Exists(
|
||||
diretorio
|
||||
)
|
||||
? diretorio
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Salva uma fotografia independente do DocumentoTrabalho atual.
|
||||
///
|
||||
/// Não executa Apply, não fecha a sessão, não altera DocumentoFonte,
|
||||
/// não altera o documento oficial, não limpa Undo/Redo e não toca
|
||||
/// na seleção operacional.
|
||||
/// </summary>
|
||||
public bool SalvarMapaEditadoGeoJson(
|
||||
string caminhoArquivo)
|
||||
{
|
||||
if (
|
||||
!_estadoEdicao.EmEdicao ||
|
||||
_estadoEdicao.DocumentoTrabalho == null
|
||||
)
|
||||
{
|
||||
Log(
|
||||
"[MAPA][EDICAO][SALVAR] " +
|
||||
"Não existe sessão de edição ativa."
|
||||
);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
string.IsNullOrWhiteSpace(
|
||||
caminhoArquivo
|
||||
)
|
||||
)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"O caminho para salvar o mapa não foi informado.",
|
||||
nameof(caminhoArquivo)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ObterResultado devolve uma cópia profunda.
|
||||
* O exportador jamais recebe DocumentoTrabalho diretamente.
|
||||
*/
|
||||
MapaDocumento documentoSalvar =
|
||||
_edicaoService
|
||||
.ObterResultado(
|
||||
_estadoEdicao
|
||||
);
|
||||
|
||||
|
||||
documentoSalvar.Modificado =
|
||||
true;
|
||||
|
||||
|
||||
/*
|
||||
* Reaproveita a mesma fronteira que protege o Apply.
|
||||
* Se a validação falhar, nenhum arquivo é gravado.
|
||||
*/
|
||||
if (
|
||||
!ValidarDocumentoAntesDoApply(
|
||||
documentoSalvar
|
||||
)
|
||||
)
|
||||
{
|
||||
Log(
|
||||
"[MAPA][EDICAO][SALVAR] " +
|
||||
"Cancelado por falha de validação."
|
||||
);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
_exportService
|
||||
.SalvarGeoJson(
|
||||
documentoSalvar,
|
||||
caminhoArquivo
|
||||
);
|
||||
|
||||
|
||||
Log(
|
||||
"[MAPA][EDICAO][SALVAR] " +
|
||||
$"OK | " +
|
||||
$"Ruas={documentoSalvar.QuantidadeRuas} | " +
|
||||
$"Arquivo={System.IO.Path.GetFileName(caminhoArquivo)}"
|
||||
);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public MapaDocumento ObterResultadoEdicao()
|
||||
{
|
||||
return _edicaoService
|
||||
|
|
|
|||
|
|
@ -723,6 +723,16 @@
|
|||
ToolTip="Restaura no editor o mapa que entrou originalmente no sistema. Ctrl+Z desfaz."
|
||||
Click="btnRestaurarMapaFonte_Click"/>
|
||||
|
||||
<Button
|
||||
x:Name="btnSalvarMapaEditado"
|
||||
Content="💾 Salvar versão editada"
|
||||
Height="40"
|
||||
Margin="0,8,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Style="{StaticResource ApplyButtonStyle}"
|
||||
ToolTip="Salva a geometria atual do editor em GeoJSON sem aplicar nem encerrar a edição."
|
||||
Click="btnSalvarMapaEditado_Click"/>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<!-- STATUS -->
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace OperationControl.Mapas.Editing.Views
|
|||
public event Action<MapaEscopoRecorte>? EscopoRecorteAlterado;
|
||||
public event Action<MapaAcaoBordaGnss>? BordaGnssAcaoSolicitada;
|
||||
public event Action? RestaurarMapaFonteSolicitado;
|
||||
public event Action? SalvarMapaEditadoSolicitado;
|
||||
|
||||
private bool _atualizandoEstado;
|
||||
|
||||
|
|
@ -490,6 +491,12 @@ namespace OperationControl.Mapas.Editing.Views
|
|||
}
|
||||
|
||||
|
||||
private void btnSalvarMapaEditado_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SalvarMapaEditadoSolicitado?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
private void rbGnssManual_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_atualizandoEstado)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using AgroBase.Models;
|
||||
using Newtonsoft.Json;
|
||||
using OperationControl.Mapas.Models;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace OperationControl.Mapas.Services
|
||||
{
|
||||
|
|
@ -149,5 +152,163 @@ namespace OperationControl.Mapas.Services
|
|||
|
||||
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.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,6 +74,17 @@ namespace OperationControl.ViewModels
|
|||
_viewOperacaoCenter = new OperacaoCenterView();
|
||||
_viewOperacaoCenter._vm.RoverSelecionadoChanged += OnRoverSelecionadoChanged;
|
||||
|
||||
/*
|
||||
* Salvar arquivo é uma intenção de UI.
|
||||
* O cockpit só dispara o evento, o Dock abre o diálogo
|
||||
* e o Controller continua dono do documento e da exportação.
|
||||
*/
|
||||
_viewOperacaoCenter
|
||||
.Mapa
|
||||
.EditorOverlay
|
||||
.SalvarMapaEditadoSolicitado +=
|
||||
OnSalvarMapaEditadoSolicitado;
|
||||
|
||||
_viewOperacaoLeft = new OperacaoLeftView();
|
||||
|
||||
_viewOperacaoRight = new OperacaoRightView();
|
||||
|
|
@ -1152,7 +1163,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
|
||||
string mensagem =
|
||||
"As alterações não foram aplicadas porque " +
|
||||
"A ação foi cancelada porque " +
|
||||
"o mapa editado não passou na validação." +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
|
|
@ -1173,6 +1184,121 @@ namespace OperationControl.ViewModels
|
|||
}
|
||||
|
||||
|
||||
private void OnSalvarMapaEditadoSolicitado()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mapas.EdicaoAtiva)
|
||||
return;
|
||||
|
||||
|
||||
string nomeSugerido =
|
||||
_mapas
|
||||
.ObterNomeArquivoSugeridoMapaEditado();
|
||||
|
||||
|
||||
string? diretorioOrigem =
|
||||
_mapas
|
||||
.ObterDiretorioOrigemMapaEdicao();
|
||||
|
||||
|
||||
var dialog =
|
||||
new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Title =
|
||||
"Salvar versão editada do mapa",
|
||||
|
||||
Filter =
|
||||
"GeoJSON (*.geojson)|*.geojson",
|
||||
|
||||
DefaultExt =
|
||||
".geojson",
|
||||
|
||||
AddExtension =
|
||||
true,
|
||||
|
||||
FileName =
|
||||
nomeSugerido,
|
||||
|
||||
OverwritePrompt =
|
||||
true
|
||||
};
|
||||
|
||||
|
||||
if (
|
||||
!string.IsNullOrWhiteSpace(
|
||||
diretorioOrigem
|
||||
) &&
|
||||
System.IO.Directory.Exists(
|
||||
diretorioOrigem
|
||||
)
|
||||
)
|
||||
{
|
||||
dialog.InitialDirectory =
|
||||
diretorioOrigem;
|
||||
}
|
||||
|
||||
|
||||
bool? resultado =
|
||||
dialog.ShowDialog();
|
||||
|
||||
|
||||
if (resultado != true)
|
||||
return;
|
||||
|
||||
|
||||
bool salvo =
|
||||
_mapas
|
||||
.SalvarMapaEditadoGeoJson(
|
||||
dialog.FileName
|
||||
);
|
||||
|
||||
|
||||
if (!salvo)
|
||||
return;
|
||||
|
||||
|
||||
Models.Variaveis.MostrarLog(
|
||||
"[MAPA][EDICAO][SALVAR][UI] " +
|
||||
dialog.FileName
|
||||
);
|
||||
|
||||
|
||||
MessageBox.Show(
|
||||
"Mapa operacional salvo com sucesso." +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
dialog.FileName +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
"A edição continua aberta e o histórico " +
|
||||
"Desfazer/Refazer foi preservado.",
|
||||
"Mapa salvo",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Models.Variaveis.MostrarLog(
|
||||
"[MAPA][EDICAO][SALVAR][ERRO] " +
|
||||
ex
|
||||
);
|
||||
|
||||
|
||||
MessageBox.Show(
|
||||
"Não foi possível salvar o mapa editado." +
|
||||
Environment.NewLine +
|
||||
Environment.NewLine +
|
||||
ex.Message,
|
||||
"Erro ao salvar mapa",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Wrappers públicos mantidos porque existem consumidores
|
||||
* externos ao Dock que já chamam essas APIs.
|
||||
|
|
|
|||
Loading…
Reference in New Issue