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