1515 lines
36 KiB
C#
1515 lines
36 KiB
C#
using AgroBase.Models;
|
|
using Mapsui.Layers;
|
|
using OperationControl.Controls;
|
|
using OperationControl.Mapas.Creation;
|
|
using OperationControl.Mapas.Interaction;
|
|
using OperationControl.Mapas.Models;
|
|
using OperationControl.Mapas.Rendering;
|
|
using System.Threading;
|
|
using static AgroBase.Models.Enums;
|
|
|
|
namespace OperationControl.Mapas
|
|
{
|
|
public sealed partial class MapaOperacaoController
|
|
{
|
|
/*
|
|
* =========================================================
|
|
* CRIAÇÃO DE MAPA
|
|
* =========================================================
|
|
*
|
|
* Este partial é deliberadamente separado do editor.
|
|
*
|
|
* Editor:
|
|
* MapaDocumento existente -> DocumentoTrabalho.
|
|
*
|
|
* Criação:
|
|
* levantamento GNSS -> estado transitório -> MapaDocumento.
|
|
*
|
|
* Durante este degrau o resultado ainda NÃO é salvo nem
|
|
* instalado no Workspace. Isso entra no cockpit/salvamento.
|
|
* =========================================================
|
|
*/
|
|
|
|
|
|
public event Action?
|
|
EstadoCriacaoAlterado;
|
|
|
|
|
|
private readonly
|
|
MapaEstadoCriacao
|
|
_estadoCriacao =
|
|
new();
|
|
|
|
|
|
private readonly
|
|
MapaCriacaoService
|
|
_criacaoService =
|
|
new();
|
|
|
|
|
|
private readonly
|
|
MapaCriacaoRenderService
|
|
_criacaoRenderService =
|
|
new();
|
|
|
|
|
|
private readonly Dictionary<
|
|
MapViewControl,
|
|
MemoryLayer>
|
|
_layersCriacao =
|
|
new();
|
|
|
|
|
|
private readonly HashSet<
|
|
MapViewControl>
|
|
_mapasEmCriacao =
|
|
new();
|
|
|
|
|
|
private readonly Dictionary<
|
|
MapViewControl,
|
|
MapaModoInteracao>
|
|
_modoInteracaoAntesCriacao =
|
|
new();
|
|
|
|
|
|
private int
|
|
_capturaAutomaticaCriacaoUiAgendada;
|
|
|
|
|
|
private int
|
|
_atualizacaoCriacaoGnssUiAgendada;
|
|
|
|
|
|
private long
|
|
_ultimaAtualizacaoCriacaoGnssUiMs;
|
|
|
|
|
|
public bool CriacaoMapaAtiva =>
|
|
_estadoCriacao
|
|
.EmCriacao;
|
|
|
|
|
|
public bool CapturaCriacaoAutomaticaAtiva =>
|
|
_estadoCriacao.EmCriacao &&
|
|
_estadoCriacao.GravacaoAutomaticaAtiva;
|
|
|
|
|
|
public MapaEstadoCriacao
|
|
EstadoCriacaoMapa =>
|
|
_estadoCriacao;
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* ENTRADA GNSS LOCAL
|
|
* =========================================================
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Entrada explícita do GNSS local para as ferramentas
|
|
/// de levantamento do mapa.
|
|
///
|
|
/// O Dock chama este método com o mesmo GPSModel que já
|
|
/// entrega à representação da Base.
|
|
///
|
|
/// AtualizarSnapshotGnssLocal pertence ao partial de
|
|
/// Borda GNSS e já faz Clone(), portanto não retemos a
|
|
/// referência viva recebida da camada de serviço.
|
|
/// </summary>
|
|
public void AtualizarGnssLocal(
|
|
GPSModel? dados)
|
|
{
|
|
if (dados == null)
|
|
return;
|
|
|
|
|
|
AtualizarSnapshotGnssLocal(
|
|
dados
|
|
);
|
|
|
|
|
|
AgendarCapturaAutomaticaCriacaoSeNecessario();
|
|
AgendarAtualizacaoCockpitCriacaoGnss();
|
|
}
|
|
|
|
|
|
private void AgendarAtualizacaoCockpitCriacaoGnss()
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
return;
|
|
|
|
long agoraMs = Environment.TickCount64;
|
|
long ultimaMs = Interlocked.Read(
|
|
ref _ultimaAtualizacaoCriacaoGnssUiMs
|
|
);
|
|
|
|
// Status visual limitado a aproximadamente 4 Hz.
|
|
if (agoraMs - ultimaMs < 250)
|
|
return;
|
|
|
|
if (Interlocked.Exchange(
|
|
ref _atualizacaoCriacaoGnssUiAgendada,
|
|
1
|
|
) != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var dispatcher =
|
|
System.Windows.Application.Current?.Dispatcher;
|
|
|
|
if (dispatcher == null)
|
|
{
|
|
Interlocked.Exchange(
|
|
ref _atualizacaoCriacaoGnssUiAgendada,
|
|
0
|
|
);
|
|
return;
|
|
}
|
|
|
|
dispatcher.BeginInvoke(
|
|
new Action(() =>
|
|
{
|
|
try
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
return;
|
|
|
|
Interlocked.Exchange(
|
|
ref _ultimaAtualizacaoCriacaoGnssUiMs,
|
|
Environment.TickCount64
|
|
);
|
|
|
|
EstadoCriacaoAlterado?.Invoke();
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Exchange(
|
|
ref _atualizacaoCriacaoGnssUiAgendada,
|
|
0
|
|
);
|
|
}
|
|
}),
|
|
System.Windows.Threading.DispatcherPriority.Background
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* SESSÃO
|
|
* =========================================================
|
|
*/
|
|
|
|
public void IniciarCriacaoMapa(
|
|
MapViewControl? mapaControl,
|
|
string? nome,
|
|
TipoMapaOperacao tipo,
|
|
double distanciaMinimaAutomaticaMetros = 0.50)
|
|
{
|
|
if (
|
|
mapaControl?.Mapa?.Map == null
|
|
)
|
|
{
|
|
throw new ArgumentNullException(
|
|
nameof(mapaControl)
|
|
);
|
|
}
|
|
|
|
|
|
if (_estadoEdicao.EmEdicao)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Finalize ou cancele a edição atual antes de criar um novo mapa."
|
|
);
|
|
}
|
|
|
|
|
|
if (_estadoCriacao.EmCriacao)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Já existe uma sessão de criação de mapa ativa."
|
|
);
|
|
}
|
|
|
|
|
|
_criacaoService
|
|
.Iniciar(
|
|
_estadoCriacao,
|
|
nome,
|
|
tipo,
|
|
distanciaMinimaAutomaticaMetros
|
|
);
|
|
|
|
|
|
RegistrarMapa(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
_modoInteracaoAntesCriacao[
|
|
mapaControl
|
|
] =
|
|
mapaControl
|
|
.ModoInteracao;
|
|
|
|
|
|
_mapasEmCriacao
|
|
.Add(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
/*
|
|
* A criação não depende de clique geográfico.
|
|
* O operador continua podendo pan/zoom normalmente.
|
|
*/
|
|
DefinirModoInteracao(
|
|
mapaControl,
|
|
MapaModoInteracao
|
|
.Visualizacao
|
|
);
|
|
|
|
|
|
/*
|
|
* Se já existia um mapa oficial carregado,
|
|
* escondemos SOMENTE o render desta View.
|
|
*
|
|
* O Workspace permanece intacto e será restaurado
|
|
* se a criação for cancelada.
|
|
*/
|
|
OcultarWorkspaceDuranteCriacao(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
RenderizarCriacaoNoMapa(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
"Sessão iniciada | " +
|
|
$"Tipo={tipo} | " +
|
|
$"DistAuto={distanciaMinimaAutomaticaMetros:F2} m"
|
|
);
|
|
}
|
|
|
|
|
|
public void CancelarCriacaoMapa()
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
return;
|
|
|
|
|
|
MapViewControl[]
|
|
mapas =
|
|
_mapasEmCriacao
|
|
.ToArray();
|
|
|
|
|
|
/*
|
|
* Cancela primeiro o gravador para qualquer delegate
|
|
* de UI já enfileirado falhar nas guardas internas.
|
|
*/
|
|
_criacaoService
|
|
.PararGravacaoAutomatica(
|
|
_estadoCriacao,
|
|
out _
|
|
);
|
|
|
|
|
|
_criacaoService
|
|
.Cancelar(
|
|
_estadoCriacao
|
|
);
|
|
|
|
|
|
foreach (
|
|
MapViewControl mapa
|
|
in mapas)
|
|
{
|
|
RemoverRenderCriacao(
|
|
mapa
|
|
);
|
|
|
|
|
|
MapaModoInteracao
|
|
modoRestaurar =
|
|
_modoInteracaoAntesCriacao
|
|
.TryGetValue(
|
|
mapa,
|
|
out MapaModoInteracao anterior
|
|
)
|
|
? anterior
|
|
: MapaModoInteracao
|
|
.Visualizacao;
|
|
|
|
|
|
_mapasEmCriacao
|
|
.Remove(
|
|
mapa
|
|
);
|
|
|
|
|
|
_modoInteracaoAntesCriacao
|
|
.Remove(
|
|
mapa
|
|
);
|
|
|
|
|
|
PrepararMapa(
|
|
mapa,
|
|
modoRestaurar,
|
|
enquadrarDocumento:
|
|
false
|
|
);
|
|
}
|
|
|
|
|
|
_mapasEmCriacao
|
|
.Clear();
|
|
|
|
_modoInteracaoAntesCriacao
|
|
.Clear();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
"Sessão cancelada."
|
|
);
|
|
}
|
|
|
|
|
|
public void DefinirNomeCriacaoMapa(
|
|
string? nome)
|
|
{
|
|
_criacaoService
|
|
.DefinirNome(
|
|
_estadoCriacao,
|
|
nome
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* CAPTURA MANUAL
|
|
* =========================================================
|
|
*/
|
|
|
|
public bool RegistrarPontoCriacaoManual()
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
return false;
|
|
|
|
|
|
GPSModel?
|
|
leitura =
|
|
ObterSnapshotGnssLocal();
|
|
|
|
|
|
if (
|
|
!TentarValidarLeituraGnss(
|
|
leitura,
|
|
out double idade,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][PONTO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
MapaPontoCriacao
|
|
ponto =
|
|
CriarPontoCriacao(
|
|
leitura!
|
|
);
|
|
|
|
|
|
bool adicionado =
|
|
_criacaoService
|
|
.TentarAdicionarPontoManual(
|
|
_estadoCriacao,
|
|
ponto,
|
|
out motivo
|
|
);
|
|
|
|
|
|
if (!adicionado)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][PONTO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
if (_estadoCriacao.QuantidadePontosTotal == 1)
|
|
{
|
|
EnquadrarCriacaoNoMapa();
|
|
}
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][PONTO] " +
|
|
$"Rua={_estadoCriacao.RuaAtual?.Numero ?? 0} | " +
|
|
$"N={_estadoCriacao.RuaAtual?.QuantidadePontos ?? 0} | " +
|
|
$"Fix={leitura!.QualidadeFix} | " +
|
|
$"Idade={idade:F2}s"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public bool RemoverUltimoPontoCriacao()
|
|
{
|
|
if (
|
|
!_criacaoService
|
|
.TentarRemoverUltimoPonto(
|
|
_estadoCriacao,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][REMOVER-PONTO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
"Último ponto da rua atual removido."
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* RUAS
|
|
* =========================================================
|
|
*/
|
|
|
|
public bool CriarNovaRuaMapa()
|
|
{
|
|
if (
|
|
!_criacaoService
|
|
.TentarCriarNovaRua(
|
|
_estadoCriacao,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][NOVA-RUA][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
$"Nova rua | Atual={_estadoCriacao.RuaAtual?.Numero ?? 0}"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Número da rua é 1-based para ficar natural para a UI.
|
|
/// </summary>
|
|
public bool SelecionarRuaCriacao(
|
|
int numeroRua)
|
|
{
|
|
int indice =
|
|
numeroRua - 1;
|
|
|
|
|
|
if (
|
|
!_criacaoService
|
|
.TentarSelecionarRua(
|
|
_estadoCriacao,
|
|
indice,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][SELECIONAR-RUA][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Número da rua é 1-based para ficar natural para a UI.
|
|
/// </summary>
|
|
public bool ExcluirRuaCriacao(
|
|
int numeroRua)
|
|
{
|
|
int indice =
|
|
numeroRua - 1;
|
|
|
|
|
|
if (
|
|
!_criacaoService
|
|
.TentarExcluirRua(
|
|
_estadoCriacao,
|
|
indice,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][EXCLUIR-RUA][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
$"Rua removida | Ruas={_estadoCriacao.QuantidadeRuas}"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public void LimparLevantamentoCriacao()
|
|
{
|
|
_criacaoService
|
|
.LimparPontos(
|
|
_estadoCriacao
|
|
);
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO] " +
|
|
"Levantamento limpo."
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* MODO AUTOMÁTICO
|
|
* =========================================================
|
|
*/
|
|
|
|
public bool DefinirModoCapturaCriacao(
|
|
MapaModoCapturaCriacao modo)
|
|
{
|
|
bool alterado =
|
|
_criacaoService
|
|
.DefinirModoCaptura(
|
|
_estadoCriacao,
|
|
modo,
|
|
out string motivo
|
|
);
|
|
|
|
|
|
if (!alterado)
|
|
{
|
|
if (
|
|
!string.IsNullOrWhiteSpace(
|
|
motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][MODO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][MODO] " +
|
|
$"Atual={modo}"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public bool IniciarGravacaoAutomaticaCriacao()
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
return false;
|
|
|
|
|
|
GPSModel?
|
|
leitura =
|
|
ObterSnapshotGnssLocal();
|
|
|
|
|
|
if (
|
|
!TentarValidarLeituraGnss(
|
|
leitura,
|
|
out double idade,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO][INICIO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
if (
|
|
!_criacaoService
|
|
.IniciarGravacaoAutomatica(
|
|
_estadoCriacao,
|
|
out motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO][INICIO][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Mesmo comportamento já validado na borda GNSS:
|
|
* a posição do clique em "Iniciar" entra imediatamente.
|
|
*/
|
|
ProcessarPontoAutomaticoCriacao(
|
|
leitura!,
|
|
forcarPrimeiroPonto:
|
|
true
|
|
);
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO] " +
|
|
"Gravação iniciada | " +
|
|
$"Rua={_estadoCriacao.RuaAtual?.Numero ?? 0} | " +
|
|
$"DistMin={_estadoCriacao.DistanciaMinimaCapturaAutomaticaMetros:F2} m | " +
|
|
$"Fix={leitura!.QualidadeFix} | " +
|
|
$"Idade={idade:F2}s"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public bool FinalizarGravacaoAutomaticaCriacao()
|
|
{
|
|
if (
|
|
!_criacaoService
|
|
.PararGravacaoAutomatica(
|
|
_estadoCriacao,
|
|
out string motivo
|
|
)
|
|
)
|
|
{
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO][FIM][IGNORADO] " +
|
|
motivo
|
|
);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO] " +
|
|
$"Gravação finalizada | " +
|
|
$"Rua={_estadoCriacao.RuaAtual?.Numero ?? 0} | " +
|
|
$"Pontos={_estadoCriacao.RuaAtual?.QuantidadePontos ?? 0}"
|
|
);
|
|
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
private void ProcessarPontoAutomaticoCriacao(
|
|
GPSModel leitura,
|
|
bool forcarPrimeiroPonto = false)
|
|
{
|
|
if (
|
|
leitura == null ||
|
|
!_estadoCriacao.EmCriacao ||
|
|
!_estadoCriacao.GravacaoAutomaticaAtiva
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
!TentarValidarLeituraGnss(
|
|
leitura,
|
|
out double idade,
|
|
out _
|
|
)
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
MapaPontoCriacao
|
|
ponto =
|
|
CriarPontoCriacao(
|
|
leitura
|
|
);
|
|
|
|
|
|
bool adicionado =
|
|
_criacaoService
|
|
.TentarAdicionarPontoAutomatico(
|
|
_estadoCriacao,
|
|
ponto,
|
|
forcarPrimeiroPonto,
|
|
out _
|
|
);
|
|
|
|
|
|
if (!adicionado)
|
|
return;
|
|
|
|
|
|
AtualizarVisualCriacao();
|
|
|
|
|
|
if (_estadoCriacao.QuantidadePontosTotal == 1)
|
|
{
|
|
EnquadrarCriacaoNoMapa();
|
|
}
|
|
|
|
|
|
EstadoCriacaoAlterado?
|
|
.Invoke();
|
|
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][AUTO][PONTO] " +
|
|
$"Rua={_estadoCriacao.RuaAtual?.Numero ?? 0} | " +
|
|
$"N={_estadoCriacao.RuaAtual?.QuantidadePontos ?? 0} | " +
|
|
$"Fix={leitura.QualidadeFix} | " +
|
|
$"Idade={idade:F2}s"
|
|
);
|
|
}
|
|
|
|
|
|
private void AgendarCapturaAutomaticaCriacaoSeNecessario()
|
|
{
|
|
if (
|
|
!_estadoCriacao.EmCriacao ||
|
|
!_estadoCriacao.GravacaoAutomaticaAtiva
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
Interlocked.Exchange(
|
|
ref _capturaAutomaticaCriacaoUiAgendada,
|
|
1
|
|
) != 0
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
var dispatcher =
|
|
System.Windows
|
|
.Application
|
|
.Current?
|
|
.Dispatcher;
|
|
|
|
|
|
if (dispatcher == null)
|
|
{
|
|
Interlocked.Exchange(
|
|
ref _capturaAutomaticaCriacaoUiAgendada,
|
|
0
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
dispatcher.BeginInvoke(
|
|
new Action(
|
|
() =>
|
|
{
|
|
try
|
|
{
|
|
/*
|
|
* Guardas repetidas de propósito.
|
|
*
|
|
* Se o operador finalizou a gravação
|
|
* depois do agendamento, nenhum ponto
|
|
* fantasma pode aparecer.
|
|
*/
|
|
if (
|
|
!_estadoCriacao.EmCriacao ||
|
|
!_estadoCriacao
|
|
.GravacaoAutomaticaAtiva
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
GPSModel?
|
|
leitura =
|
|
ObterSnapshotGnssLocal();
|
|
|
|
|
|
if (leitura == null)
|
|
return;
|
|
|
|
|
|
if (
|
|
!_estadoCriacao.EmCriacao ||
|
|
!_estadoCriacao
|
|
.GravacaoAutomaticaAtiva
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
ProcessarPontoAutomaticoCriacao(
|
|
leitura
|
|
);
|
|
}
|
|
finally
|
|
{
|
|
Interlocked.Exchange(
|
|
ref _capturaAutomaticaCriacaoUiAgendada,
|
|
0
|
|
);
|
|
}
|
|
}
|
|
),
|
|
System.Windows.Threading
|
|
.DispatcherPriority
|
|
.Background
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* RESUMO / RESULTADO
|
|
* =========================================================
|
|
*/
|
|
|
|
public MapaResumoCriacao
|
|
ObterResumoCriacaoMapa()
|
|
{
|
|
MapaResumoCriacao
|
|
baseResumo =
|
|
_criacaoService
|
|
.CriarResumo(
|
|
_estadoCriacao
|
|
);
|
|
|
|
|
|
GPSModel?
|
|
leitura =
|
|
ObterSnapshotGnssLocal();
|
|
|
|
|
|
bool leituraValida =
|
|
TentarValidarLeituraGnss(
|
|
leitura,
|
|
out double idade,
|
|
out string motivo
|
|
);
|
|
|
|
|
|
bool qualidadeAlta =
|
|
leitura != null &&
|
|
(
|
|
leitura.QualidadeFix ==
|
|
TiposCorrecaoGPS.RTKFixo
|
|
||
|
|
leitura.QualidadeFix ==
|
|
TiposCorrecaoGPS.BaseFix
|
|
);
|
|
|
|
|
|
string status =
|
|
!leituraValida
|
|
? motivo
|
|
: $"{DescreverFix(leitura!.QualidadeFix)}" +
|
|
$" • {leitura.NumeroSatelites} sat" +
|
|
$" • {idade:F1} s" +
|
|
(
|
|
double.IsFinite(
|
|
leitura.PrecisaoCm
|
|
)
|
|
? $" • ~{leitura.PrecisaoCm:F1} cm"
|
|
: string.Empty
|
|
);
|
|
|
|
|
|
return new MapaResumoCriacao
|
|
{
|
|
EmCriacao =
|
|
baseResumo.EmCriacao,
|
|
|
|
Nome =
|
|
baseResumo.Nome,
|
|
|
|
RuaAtual =
|
|
baseResumo.RuaAtual,
|
|
|
|
QuantidadeRuas =
|
|
baseResumo.QuantidadeRuas,
|
|
|
|
QuantidadeRuasValidas =
|
|
baseResumo.QuantidadeRuasValidas,
|
|
|
|
PontosRuaAtual =
|
|
baseResumo.PontosRuaAtual,
|
|
|
|
PontosTotal =
|
|
baseResumo.PontosTotal,
|
|
|
|
ComprimentoRuaAtualMetros =
|
|
baseResumo.ComprimentoRuaAtualMetros,
|
|
|
|
ComprimentoTotalMetros =
|
|
baseResumo.ComprimentoTotalMetros,
|
|
|
|
ModoCaptura =
|
|
baseResumo.ModoCaptura,
|
|
|
|
GravacaoAutomaticaAtiva =
|
|
baseResumo.GravacaoAutomaticaAtiva,
|
|
|
|
DistanciaMinimaAutomaticaMetros =
|
|
baseResumo.DistanciaMinimaAutomaticaMetros,
|
|
|
|
PodeNovaRua =
|
|
baseResumo.PodeNovaRua,
|
|
|
|
PodeSalvar =
|
|
baseResumo.PodeSalvar,
|
|
|
|
Ruas =
|
|
baseResumo.Ruas,
|
|
|
|
LeituraGnssValida =
|
|
leituraValida,
|
|
|
|
QualidadeAlta =
|
|
qualidadeAlta,
|
|
|
|
StatusGnss =
|
|
status
|
|
};
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Constrói uma fotografia canônica do levantamento.
|
|
///
|
|
/// Ainda não altera o Workspace e ainda não salva arquivo.
|
|
/// </summary>
|
|
public MapaDocumento
|
|
ObterDocumentoCriadoMapa()
|
|
{
|
|
return
|
|
_criacaoService
|
|
.CriarDocumento(
|
|
_estadoCriacao
|
|
);
|
|
}
|
|
|
|
|
|
public string ObterNomeArquivoSugeridoMapaCriado()
|
|
{
|
|
string nome = _estadoCriacao.Nome;
|
|
|
|
if (string.IsNullOrWhiteSpace(nome))
|
|
nome = "NovoMapa";
|
|
|
|
char[] invalidos = System.IO.Path.GetInvalidFileNameChars();
|
|
|
|
nome = new string(
|
|
nome.Trim()
|
|
.Select(c => invalidos.Contains(c) ? '_' : c)
|
|
.ToArray()
|
|
);
|
|
|
|
return nome + ".geojson";
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// levantamento -> documento -> GeoJSON -> importação normal -> Workspace.
|
|
/// A sessão permanece aberta se validar/salvar/importar falhar.
|
|
/// </summary>
|
|
public MapaDocumento SalvarECarregarMapaCriado(string caminhoArquivo)
|
|
{
|
|
if (!_estadoCriacao.EmCriacao)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Não existe uma criação de mapa ativa."
|
|
);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(caminhoArquivo))
|
|
{
|
|
throw new ArgumentException(
|
|
"O caminho do arquivo não foi informado.",
|
|
nameof(caminhoArquivo)
|
|
);
|
|
}
|
|
|
|
MapaDocumento documentoCriado =
|
|
_criacaoService.CriarDocumento(_estadoCriacao);
|
|
|
|
// Primeiro persiste. Se falhar, o levantamento continua aberto.
|
|
_exportService.SalvarGeoJson(
|
|
documentoCriado,
|
|
caminhoArquivo
|
|
);
|
|
|
|
// O importador valida o arquivo antes de trocar o Workspace.
|
|
MapaDocumento carregado = CarregarMapaArquivo(
|
|
caminhoArquivo,
|
|
documentoCriado.Tipo
|
|
);
|
|
|
|
MapViewControl[] mapas = _mapasEmCriacao.ToArray();
|
|
|
|
var modosRestaurar = mapas.ToDictionary(
|
|
mapa => mapa,
|
|
mapa => _modoInteracaoAntesCriacao.TryGetValue(
|
|
mapa,
|
|
out MapaModoInteracao anterior
|
|
)
|
|
? anterior
|
|
: MapaModoInteracao.Visualizacao
|
|
);
|
|
|
|
_criacaoService.Cancelar(_estadoCriacao);
|
|
|
|
foreach (MapViewControl mapa in mapas)
|
|
{
|
|
RemoverRenderCriacao(mapa);
|
|
_mapasEmCriacao.Remove(mapa);
|
|
_modoInteracaoAntesCriacao.Remove(mapa);
|
|
|
|
PrepararMapa(
|
|
mapa,
|
|
modosRestaurar[mapa],
|
|
enquadrarDocumento: true
|
|
);
|
|
}
|
|
|
|
_mapasEmCriacao.Clear();
|
|
_modoInteracaoAntesCriacao.Clear();
|
|
|
|
EstadoCriacaoAlterado?.Invoke();
|
|
|
|
Log(
|
|
"[MAPA][CRIACAO][SALVAR] " +
|
|
$"OK | Ruas={carregado.QuantidadeRuas} | " +
|
|
$"Arquivo={System.IO.Path.GetFileName(caminhoArquivo)}"
|
|
);
|
|
|
|
return carregado;
|
|
}
|
|
|
|
|
|
private void EnquadrarCriacaoNoMapa()
|
|
{
|
|
List<MapaPontoCriacao> pontos =
|
|
_estadoCriacao.Ruas
|
|
.SelectMany(rua => rua.Pontos)
|
|
.ToList();
|
|
|
|
if (pontos.Count == 0)
|
|
return;
|
|
|
|
double minLat = pontos.Min(x => x.Latitude);
|
|
double maxLat = pontos.Max(x => x.Latitude);
|
|
double minLon = pontos.Min(x => x.Longitude);
|
|
double maxLon = pontos.Max(x => x.Longitude);
|
|
|
|
foreach (MapViewControl mapa in _mapasEmCriacao.ToArray())
|
|
{
|
|
mapa.EnquadrarAreaWgs84(
|
|
minLongitude: minLon,
|
|
minLatitude: minLat,
|
|
maxLongitude: maxLon,
|
|
maxLatitude: maxLat,
|
|
margem: 1.35
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
private static MapaPontoCriacao
|
|
CriarPontoCriacao(
|
|
GPSModel leitura)
|
|
{
|
|
return new MapaPontoCriacao
|
|
{
|
|
Latitude =
|
|
leitura.Latitude,
|
|
|
|
Longitude =
|
|
leitura.Longitude,
|
|
|
|
Altitude =
|
|
leitura.Altitude,
|
|
|
|
PrecisaoCm =
|
|
leitura.PrecisaoCm,
|
|
|
|
NumeroSatelites =
|
|
leitura.NumeroSatelites,
|
|
|
|
QualidadeFix =
|
|
leitura.QualidadeFix,
|
|
|
|
Momento =
|
|
leitura.Momento
|
|
};
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* RENDER
|
|
* =========================================================
|
|
*/
|
|
|
|
private void AtualizarVisualCriacao()
|
|
{
|
|
foreach (
|
|
MapViewControl mapa
|
|
in _mapasEmCriacao
|
|
.ToArray())
|
|
{
|
|
RenderizarCriacaoNoMapa(
|
|
mapa
|
|
);
|
|
|
|
|
|
mapa
|
|
.Mapa
|
|
.Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
private void RenderizarCriacaoNoMapa(
|
|
MapViewControl mapaControl)
|
|
{
|
|
if (
|
|
mapaControl?.Mapa?.Map == null
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
!_layersCriacao
|
|
.TryGetValue(
|
|
mapaControl,
|
|
out MemoryLayer? camada
|
|
)
|
|
)
|
|
{
|
|
camada =
|
|
_criacaoRenderService
|
|
.CriarCamada(
|
|
_estadoCriacao
|
|
);
|
|
|
|
|
|
mapaControl
|
|
.Mapa
|
|
.Map
|
|
.Layers
|
|
.Add(
|
|
camada
|
|
);
|
|
|
|
|
|
_layersCriacao[
|
|
mapaControl
|
|
] =
|
|
camada;
|
|
}
|
|
else
|
|
{
|
|
_criacaoRenderService
|
|
.AtualizarCamada(
|
|
camada,
|
|
_estadoCriacao
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Mantém preview acima das ruas operacionais.
|
|
*/
|
|
var layers =
|
|
mapaControl
|
|
.Mapa
|
|
.Map
|
|
.Layers;
|
|
|
|
|
|
if (
|
|
layers.Contains(
|
|
camada
|
|
)
|
|
)
|
|
{
|
|
layers.Remove(
|
|
camada
|
|
);
|
|
|
|
layers.Add(
|
|
camada
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* Base/Rover continuam visualmente no topo.
|
|
*/
|
|
GarantirEntidadesNoTopo(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
mapaControl
|
|
.Mapa
|
|
.Refresh();
|
|
}
|
|
|
|
|
|
private void RemoverRenderCriacao(
|
|
MapViewControl mapaControl)
|
|
{
|
|
if (
|
|
mapaControl?.Mapa?.Map == null
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
_layersCriacao
|
|
.TryGetValue(
|
|
mapaControl,
|
|
out MemoryLayer? camada
|
|
)
|
|
)
|
|
{
|
|
mapaControl
|
|
.Mapa
|
|
.Map
|
|
.Layers
|
|
.Remove(
|
|
camada
|
|
);
|
|
|
|
|
|
_layersCriacao
|
|
.Remove(
|
|
mapaControl
|
|
);
|
|
}
|
|
|
|
|
|
mapaControl
|
|
.Mapa
|
|
.Refresh();
|
|
}
|
|
|
|
|
|
private void OcultarWorkspaceDuranteCriacao(
|
|
MapViewControl mapaControl)
|
|
{
|
|
if (
|
|
mapaControl?.Mapa?.Map == null
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
_rendersMapa
|
|
.TryGetValue(
|
|
mapaControl,
|
|
out MapaRenderResult? render
|
|
)
|
|
)
|
|
{
|
|
mapaControl
|
|
.Mapa
|
|
.Map
|
|
.Layers
|
|
.Remove(
|
|
render.Layer
|
|
);
|
|
|
|
|
|
_rendersMapa
|
|
.Remove(
|
|
mapaControl
|
|
);
|
|
|
|
|
|
mapaControl
|
|
.DefinirCamadaMapaNovo(
|
|
null
|
|
);
|
|
}
|
|
|
|
|
|
mapaControl
|
|
.Mapa
|
|
.Refresh();
|
|
}
|
|
}
|
|
}
|