agrobot_base/AgroBase/OperationControl/Mapas/Controller/MapaOperacaoController.Cria...

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();
}
}
}