adicionado opção de salvar um mapa editado
This commit is contained in:
parent
437acdb56a
commit
8e4a0b4dc8
|
|
@ -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()
|
public MapaDocumento ObterResultadoEdicao()
|
||||||
{
|
{
|
||||||
return _edicaoService
|
return _edicaoService
|
||||||
|
|
|
||||||
|
|
@ -723,6 +723,16 @@
|
||||||
ToolTip="Restaura no editor o mapa que entrou originalmente no sistema. Ctrl+Z desfaz."
|
ToolTip="Restaura no editor o mapa que entrou originalmente no sistema. Ctrl+Z desfaz."
|
||||||
Click="btnRestaurarMapaFonte_Click"/>
|
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>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- STATUS -->
|
<!-- STATUS -->
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ namespace OperationControl.Mapas.Editing.Views
|
||||||
public event Action<MapaEscopoRecorte>? EscopoRecorteAlterado;
|
public event Action<MapaEscopoRecorte>? EscopoRecorteAlterado;
|
||||||
public event Action<MapaAcaoBordaGnss>? BordaGnssAcaoSolicitada;
|
public event Action<MapaAcaoBordaGnss>? BordaGnssAcaoSolicitada;
|
||||||
public event Action? RestaurarMapaFonteSolicitado;
|
public event Action? RestaurarMapaFonteSolicitado;
|
||||||
|
public event Action? SalvarMapaEditadoSolicitado;
|
||||||
|
|
||||||
private bool _atualizandoEstado;
|
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)
|
private void rbGnssManual_Checked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_atualizandoEstado)
|
if (_atualizandoEstado)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
using AgroBase.Models;
|
using AgroBase.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using OperationControl.Mapas.Models;
|
using OperationControl.Mapas.Models;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace OperationControl.Mapas.Services
|
namespace OperationControl.Mapas.Services
|
||||||
{
|
{
|
||||||
|
|
@ -149,5 +152,163 @@ namespace OperationControl.Mapas.Services
|
||||||
|
|
||||||
return mapa;
|
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 = new OperacaoCenterView();
|
||||||
_viewOperacaoCenter._vm.RoverSelecionadoChanged += OnRoverSelecionadoChanged;
|
_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();
|
_viewOperacaoLeft = new OperacaoLeftView();
|
||||||
|
|
||||||
_viewOperacaoRight = new OperacaoRightView();
|
_viewOperacaoRight = new OperacaoRightView();
|
||||||
|
|
@ -1152,7 +1163,7 @@ namespace OperationControl.ViewModels
|
||||||
|
|
||||||
|
|
||||||
string mensagem =
|
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." +
|
"o mapa editado não passou na validação." +
|
||||||
Environment.NewLine +
|
Environment.NewLine +
|
||||||
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
|
* Wrappers públicos mantidos porque existem consumidores
|
||||||
* externos ao Dock que já chamam essas APIs.
|
* externos ao Dock que já chamam essas APIs.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue