using AgroBase.Models;
using AgroBase.Models.Operacoes;
using Mapsui.Layers;
using OperationControl.Controls;
using OperationControl.Mapas.Editing;
using OperationControl.Mapas.Interaction;
using OperationControl.Mapas.Models;
using OperationControl.Mapas.Rendering;
using OperationControl.Mapas.Services;
using static AgroBase.Models.Enums;
namespace OperationControl.Mapas
{
public sealed partial class MapaOperacaoController
{
/*
* =========================================================
* EVENTOS
* =========================================================
*/
///
/// Informa à aplicação que uma entidade dinâmica
/// foi clicada no mapa.
///
/// O Controller NÃO decide qual Rover deve ser
/// selecionado na aplicação.
///
public event Action? EntidadeClicada;
///
/// Permite ao Dock encaminhar mensagens do
/// subsistema de mapas para o logger da aplicação.
///
public event Action? LogGerado;
public event Action? EstadoEdicaoAlterado;
///
/// Disparado quando algum estado estrutural do mapa
/// muda e uma interface externa pode precisar
/// atualizar seus indicadores.
///
/// Exemplos:
/// mapa carregado,
/// mapa limpo,
/// seleção operacional alterada.
///
public event Action? EstadoMapaAlterado;
public event Action? ValidacaoEdicaoFalhou;
/*
* =========================================================
* ESTADOS
* =========================================================
*/
private readonly MapaWorkspace _workspace = new();
private readonly MapaEstadoDinamico _estadoDinamico = new();
private readonly MapaEstadoMovimento _estadoMovimento = new();
private readonly MapaEstadoEdicao _estadoEdicao = new();
private readonly MapaEstadoRecorte _estadoRecorte = new();
private MapaFerramentaEdicao _ferramentaEdicaoAtual = MapaFerramentaEdicao.Nenhuma;
/*
* =========================================================
* SERVICES
* =========================================================
*/
private readonly MapaImportService _importService = new();
private readonly MapaSelectionService _selectionService = new();
private readonly MapaExportService _exportService = new();
private readonly MapaContratoImportService _contratoImportService = new();
private readonly MapaRotaRetornoService _rotaRetornoService = new();
private readonly MapaEntidadeDinamicaService _entidadeService = new();
private readonly MapaMovimentoService _movimentoService = new();
private readonly MapaEdicaoService _edicaoService = new();
private readonly MapaDocumentoCloneService _documentoCloneService = new();
private readonly MapaEstimativaOperacaoService _estimativaOperacaoService = new();
private readonly MapaValidationService _validationService = new();
/*
* =========================================================
* RENDERERS
* =========================================================
*/
private readonly MapaRenderService _renderService = new();
private readonly MapaRotaRetornoRenderService _rotaRenderService = new();
private readonly MapaEntidadeDinamicaRenderService _entidadeRenderService = new();
private readonly MapaRastroRenderService _rastroRenderService = new();
private readonly MapaPredicaoMpcRenderService _predicaoRenderService = new();
private readonly MapaReferenciaGnssRenderService _referenciaGnssRenderService = new();
private readonly MapaEdicaoRenderService _edicaoRenderService = new();
/*
* =========================================================
* ESTADO DE RENDER POR MAPVIEW
* =========================================================
*/
private readonly Dictionary<
MapViewControl,
MapaRenderResult>
_rendersMapa =
new();
private readonly Dictionary<
MapViewControl,
MemoryLayer>
_layersRetorno =
new();
private readonly Dictionary<
MapViewControl,
MemoryLayer>
_layersEntidades =
new();
private readonly Dictionary<
MapViewControl,
MemoryLayer>
_layersRastro =
new();
private readonly Dictionary<
MapViewControl,
MemoryLayer>
_layersPredicao =
new();
private readonly Dictionary<
MapViewControl,
MemoryLayer>
_layersReferenciaGnss =
new();
/*
* MapViews que já passaram pelo Controller.
*
* Não significa que estão visíveis neste instante.
*/
private readonly HashSet
_mapasRegistrados =
new();
private readonly HashSet _mapasEmEdicao = new();
/*
* Modo anterior ao início da edição.
*
* Permite ao Controller restaurar exatamente o contexto
* da View quando a sessão de edição for cancelada.
*/
private readonly Dictionary<
MapViewControl,
MapaModoInteracao>
_modoInteracaoAntesEdicao =
new();
/*
* =========================================================
* PREFERÊNCIAS VISUAIS
* =========================================================
*/
private bool
_mostrarReferenciasGnss =
true;
/*
* =========================================================
* PROPRIEDADES PÚBLICAS
* =========================================================
*/
public bool PossuiDocumentoFonte => _workspace.PossuiDocumentoFonte;
public int QuantidadeRuasFonte => _workspace.QuantidadeRuasFonte;
public bool PossuiMapa =>
_workspace.PossuiMapa;
public MapaDocumento? Documento =>
_workspace.Documento;
public TipoMapaOperacao TipoMapa =>
_workspace.Documento?.Tipo
?? TipoMapaOperacao.Indefinido;
public int QuantidadeRuas =>
_workspace.Documento?
.QuantidadeRuas
?? 0;
public int QuantidadeRuasSelecionadas =>
_workspace.Selecao?
.Quantidade
?? 0;
public bool PossuiPontosRetorno =>
_workspace.RotaRetorno?
.PossuiPontos
?? false;
public int QuantidadePontosRetorno =>
_workspace.RotaRetorno?
.Quantidade
?? 0;
public bool MostrarReferenciasGnss =>
_mostrarReferenciasGnss;
public MapaFerramentaEdicao FerramentaEdicaoAtual => _ferramentaEdicaoAtual;
/*
* =========================================================
* EDIÇÃO
* =========================================================
*/
public bool EdicaoAtiva =>
_estadoEdicao.EmEdicao;
public bool EdicaoModificada =>
_estadoEdicao.Modificado;
public int QuantidadeRuasSelecionadasEdicao =>
_estadoEdicao.QuantidadeSelecionadas;
public bool CapturaBordaGnssAutomaticaAtiva => _estadoRecorte.GravacaoAutomaticaAtiva;
public MapaDocumento? DocumentoEmEdicao => _estadoEdicao.DocumentoTrabalho;
public IReadOnlyCollection RuasSelecionadasEdicao => _estadoEdicao.RuasSelecionadas;
/*
* =========================================================
* CARREGAMENTO
* =========================================================
*/
public MapaDocumento CarregarMapaArquivo(string caminhoArquivo, TipoMapaOperacao tipo)
{
if (
string.IsNullOrWhiteSpace(
caminhoArquivo
)
)
{
throw new ArgumentException(
"Caminho do mapa inválido.",
nameof(caminhoArquivo)
);
}
/*
* Importa primeiro.
*
* Se falhar, o Workspace atual permanece intacto.
*/
var documentoImportado =
_importService.Importar(
caminhoArquivo,
tipo
);
/*
* O objeto retornado pelo importador é apenas
* matéria-prima desta transação.
*
* Nenhuma das duas cópias persistentes
* compartilha estado mutável.
*/
var documentoFonte =
_documentoCloneService
.Clonar(
documentoImportado
);
var documentoAtivo =
_documentoCloneService
.Clonar(
documentoImportado
);
/*
* Um mapa novo invalida qualquer sessão
* de edição anterior.
*/
CancelarEdicao();
InvalidarRendersWorkspace();
InvalidarRendersRotaRetorno();
_workspace.DefinirNovoDocumento(
documentoFonte,
documentoAtivo
);
EstadoMapaAlterado?.Invoke();
Log(
"[MAPA] " +
$"Arquivo carregado | " +
$"Fonte={documentoFonte.QuantidadeRuas} | " +
$"Oficial={documentoAtivo.QuantidadeRuas} | " +
$"Nome={documentoAtivo.Nome}"
);
return documentoAtivo;
}
///
/// Reconstrói o Workspace a partir do contrato
/// operacional recebido do rover.
///
/// Retorna null quando o rover não possui mapa.
///
public MapaDocumento?
CarregarMapaDoRover(
OperacaoParametrosModel dados)
{
if (dados == null)
{
throw new ArgumentNullException(
nameof(dados)
);
}
/*
* Rover sem mapa.
*/
if (
dados.Mapa?.features?.Any()
!= true
)
{
CancelarEdicao();
InvalidarRendersWorkspace();
InvalidarRendersRotaRetorno();
_workspace.Limpar();
EstadoMapaAlterado?.Invoke();
Log(
"[MAPA][ROVER] " +
"Rover não possui mapa operacional."
);
return null;
}
/*
* Toda reconstrução acontece fora
* do estado oficial primeiro.
*/
var documentoImportado =
_contratoImportService.Importar(
dados.Mapa,
dados.TipoMapa,
$"Mapa rover {dados.RoverId}"
);
var documentoFonte =
_documentoCloneService
.Clonar(
documentoImportado
);
var novoDocumento =
_documentoCloneService
.Clonar(
documentoImportado
);
var novaSelecao =
new MapaSelecaoOperacao();
_selectionService.DefinirSelecao(
novoDocumento,
novaSelecao,
dados.RuasPercorrer
?? Enumerable.Empty()
);
var novaRotaRetorno =
new MapaRotaRetorno();
_rotaRetornoService
.DefinirPeloContrato(
novaRotaRetorno,
dados.PontosRetorno
);
/*
* Só agora alteramos o estado oficial.
*/
CancelarEdicao();
InvalidarRendersWorkspace();
InvalidarRendersRotaRetorno();
_workspace.DefinirNovoEstado(
documentoFonte,
novoDocumento,
novaSelecao,
novaRotaRetorno
);
EstadoMapaAlterado?.Invoke();
Log(
"[MAPA][ROVER] " +
$"Mapa recebido | " +
$"Fonte={documentoFonte.QuantidadeRuas} | " +
$"Oficial={novoDocumento.QuantidadeRuas} | " +
$"Selecionadas={novaSelecao.Quantidade} | " +
$"Percurso=[" +
$"{string.Join(" -> ", novaSelecao.RuasOrdenadas)}]"
);
return novoDocumento;
}
public void LimparMapa()
{
CancelarEdicao();
InvalidarRendersWorkspace();
InvalidarRendersRotaRetorno();
_workspace.Limpar();
EstadoMapaAlterado?.Invoke();
/*
* Os estados de Base/Rover e movimento
* NÃO pertencem ao mapa operacional.
*
* Portanto não são apagados aqui.
*/
Log(
"[MAPA] Workspace limpo."
);
}
/*
* =========================================================
* CONTRATO OPERACIONAL
* =========================================================
*/
public MapaFeatureCollectionModel?
CriarMapaOperacional()
{
if (
_workspace.Documento == null
)
{
return null;
}
return _exportService
.CriarMapaOperacional(
_workspace.Documento
);
}
public List
ObterRuasPercorrer()
{
if (
_workspace.Documento == null
)
{
return new List();
}
return _selectionService
.ObterRuasOrdenadas(
_workspace.Selecao
)
.ToList();
}
/*
* =========================================================
* SELEÇÃO OPERACIONAL
* =========================================================
*/
public bool AlternarRuaOperacao(
string ruaId)
{
if (
_workspace.Documento == null
)
{
throw new InvalidOperationException(
"Não existe mapa operacional carregado."
);
}
bool selecionada =
_selectionService
.AlternarRua(
_workspace.Documento,
_workspace.Selecao,
ruaId
);
AtualizarEstiloSelecaoRua(
ruaId
);
EstadoMapaAlterado?.Invoke();
int? ordem =
_selectionService
.ObterOrdem(
_workspace.Selecao,
ruaId
);
Log(
"[MAPA][SELECAO] " +
$"Rua={ruaId} | " +
$"Selecionada={selecionada} | " +
$"Ordem={(ordem?.ToString() ?? "-")}"
);
return selecionada;
}
public void LimparSelecaoOperacao()
{
_selectionService.Limpar(
_workspace.Selecao
);
foreach (
var item
in _rendersMapa.ToArray())
{
_renderService
.AplicarSelecao(
item.Value,
_workspace.Selecao
);
item.Key.Mapa.Refresh();
}
EstadoMapaAlterado?.Invoke();
Log(
"[MAPA][SELECAO] Seleção limpa."
);
}
/*
* =========================================================
* ROTA DE RETORNO
* =========================================================
*/
public bool AlternarPontoRetorno(
double latitude,
double longitude)
{
bool adicionado =
_rotaRetornoService
.AlternarPonto(
_workspace.RotaRetorno,
latitude,
longitude
);
RenderizarRotasRegistradas();
Log(
"[MAPA][RETORNO] " +
$"Lat={latitude:F8} | " +
$"Lon={longitude:F8} | " +
$"Acao={(adicionado ? "ADICIONADO" : "REMOVIDO")} | " +
$"Pontos={QuantidadePontosRetorno}"
);
return adicionado;
}
public void LimparRotaRetorno()
{
_rotaRetornoService.Limpar(
_workspace.RotaRetorno
);
RenderizarRotasRegistradas();
Log(
"[MAPA][RETORNO] Rota limpa."
);
}
public double
ObterDistanciaRotaRetornoMetros()
{
return _rotaRetornoService
.ObterDistanciaTotalMetros(
_workspace.RotaRetorno
);
}
public List
ExportarRotaRetorno()
{
return _rotaRetornoService
.ExportarContrato(
_workspace.RotaRetorno
)
.ToList();
}
public MapaEstimativaOperacao CalcularEstimativaOperacao(double? velocidadeSemErvasMs = null, double? velocidadeComErvasMs = null, double percentualErvas = 0)
{
try
{
return _estimativaOperacaoService
.Calcular(
_workspace.Documento,
_workspace.Selecao,
velocidadeSemErvasMs,
velocidadeComErvasMs,
percentualErvas
);
}
catch (Exception ex)
{
Log(
"[MAPA][ESTIMATIVA][ERRO] " +
ex.Message
);
return new MapaEstimativaOperacao
{
QuantidadeRuasSelecionadas =
_workspace
.Selecao
.Quantidade,
MotivoIndisponivel =
ex.Message
};
}
}
/*
* =========================================================
* LOG
* =========================================================
*/
private void Log(
string mensagem)
{
LogGerado?.Invoke(
mensagem
);
}
}
}