Merge branch 'produto' of http://zendioninc.com.br:3000/Zendion/agrobot_base into produto
This commit is contained in:
commit
19392738f7
|
|
@ -3,6 +3,7 @@
|
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:m="clr-namespace:Mapsui.UI.Wpf;assembly=Mapsui.UI.Wpf"
|
||||
xmlns:editing="clr-namespace:OperationControl.Mapas.Editing.Views"
|
||||
xmlns:creation="clr-namespace:OperationControl.Mapas.Creation.Views"
|
||||
x:Name="Root"
|
||||
MinHeight="100"
|
||||
MinWidth="300"
|
||||
|
|
@ -91,5 +92,8 @@
|
|||
<!-- COCKPIT DO EDITOR DE MAPAS -->
|
||||
<editing:MapaEditorOverlay x:Name="EditorOverlay" Panel.ZIndex="100"/>
|
||||
|
||||
<!-- COCKPIT DA CRIAÇÃO DE MAPAS -->
|
||||
<creation:MapaCriacaoOverlay x:Name="CriacaoOverlay" Panel.ZIndex="110"/>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,844 @@
|
|||
using NetTopologySuite.Geometries;
|
||||
using OperationControl.Mapas.Models;
|
||||
using OperationControl.Mapas.Services;
|
||||
using System.Globalization;
|
||||
using static AgroBase.Models.Enums;
|
||||
|
||||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Backend puro da criação de mapas por levantamento GNSS.
|
||||
///
|
||||
/// Não conhece WPF, Mapsui, Dock, Rover ou GpsService.
|
||||
/// A futura camada Controller entrega snapshots GNSS válidos
|
||||
/// e pede operações a este serviço.
|
||||
/// </summary>
|
||||
public sealed class MapaCriacaoService
|
||||
{
|
||||
private const double DistanciaMinimaPontoManualMetros = 0.02;
|
||||
|
||||
private readonly MapaValidationService _validationService;
|
||||
|
||||
private readonly GeometryFactory _geometryFactory =
|
||||
new(
|
||||
new PrecisionModel(),
|
||||
4326
|
||||
);
|
||||
|
||||
public MapaCriacaoService(
|
||||
MapaValidationService? validationService = null)
|
||||
{
|
||||
_validationService =
|
||||
validationService
|
||||
?? new MapaValidationService();
|
||||
}
|
||||
|
||||
|
||||
public void Iniciar(
|
||||
MapaEstadoCriacao estado,
|
||||
string? nome,
|
||||
TipoMapaOperacao tipo,
|
||||
double distanciaMinimaAutomaticaMetros = 0.50)
|
||||
{
|
||||
ValidarEstado(estado);
|
||||
|
||||
if (estado.EmCriacao)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Já existe uma sessão de criação de mapa ativa."
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!double.IsFinite(distanciaMinimaAutomaticaMetros) ||
|
||||
distanciaMinimaAutomaticaMetros <= 0
|
||||
)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(distanciaMinimaAutomaticaMetros),
|
||||
"A distância mínima automática deve ser maior que zero."
|
||||
);
|
||||
}
|
||||
|
||||
estado.Limpar();
|
||||
|
||||
estado.EmCriacao = true;
|
||||
estado.Nome = NormalizarNome(nome);
|
||||
estado.Tipo = tipo;
|
||||
estado.DistanciaMinimaCapturaAutomaticaMetros =
|
||||
distanciaMinimaAutomaticaMetros;
|
||||
estado.ModoCaptura = MapaModoCapturaCriacao.Manual;
|
||||
estado.GravacaoAutomaticaAtiva = false;
|
||||
|
||||
estado.RuasMutaveis.Add(
|
||||
new MapaRuaCriacao
|
||||
{
|
||||
Numero = 1
|
||||
}
|
||||
);
|
||||
|
||||
estado.IndiceRuaAtual = 0;
|
||||
}
|
||||
|
||||
|
||||
public void Cancelar(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
ValidarEstado(estado);
|
||||
estado.Limpar();
|
||||
}
|
||||
|
||||
|
||||
public void DefinirNome(
|
||||
MapaEstadoCriacao estado,
|
||||
string? nome)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
estado.Nome = NormalizarNome(nome);
|
||||
}
|
||||
|
||||
|
||||
public bool DefinirModoCaptura(
|
||||
MapaEstadoCriacao estado,
|
||||
MapaModoCapturaCriacao modo,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de trocar o modo.";
|
||||
return false;
|
||||
}
|
||||
|
||||
estado.ModoCaptura = modo;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool IniciarGravacaoAutomatica(
|
||||
MapaEstadoCriacao estado,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (
|
||||
estado.ModoCaptura !=
|
||||
MapaModoCapturaCriacao.Automatico
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A captura atual não está no modo automático.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
return true;
|
||||
|
||||
if (estado.RuaAtual == null)
|
||||
{
|
||||
motivo =
|
||||
"Não existe rua atual para receber pontos.";
|
||||
return false;
|
||||
}
|
||||
|
||||
estado.GravacaoAutomaticaAtiva = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool PararGravacaoAutomatica(
|
||||
MapaEstadoCriacao estado,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
estado.GravacaoAutomaticaAtiva = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TentarAdicionarPontoManual(
|
||||
MapaEstadoCriacao estado,
|
||||
MapaPontoCriacao ponto,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (
|
||||
estado.ModoCaptura !=
|
||||
MapaModoCapturaCriacao.Manual
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A captura atual está no modo automático.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de registrar um ponto manual.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return TentarAdicionarPonto(
|
||||
estado,
|
||||
ponto,
|
||||
DistanciaMinimaPontoManualMetros,
|
||||
out motivo
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public bool TentarAdicionarPontoAutomatico(
|
||||
MapaEstadoCriacao estado,
|
||||
MapaPontoCriacao ponto,
|
||||
bool forcarPrimeiroPonto,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (
|
||||
estado.ModoCaptura !=
|
||||
MapaModoCapturaCriacao.Automatico ||
|
||||
!estado.GravacaoAutomaticaAtiva
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A gravação automática não está ativa.";
|
||||
return false;
|
||||
}
|
||||
|
||||
MapaRuaCriacao? rua = estado.RuaAtual;
|
||||
|
||||
if (rua == null)
|
||||
{
|
||||
motivo =
|
||||
"Não existe rua atual para receber pontos.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
forcarPrimeiroPonto &&
|
||||
rua.QuantidadePontos == 0
|
||||
)
|
||||
{
|
||||
return TentarAdicionarPonto(
|
||||
estado,
|
||||
ponto,
|
||||
distanciaMinimaMetros: 0,
|
||||
out motivo
|
||||
);
|
||||
}
|
||||
|
||||
return TentarAdicionarPonto(
|
||||
estado,
|
||||
ponto,
|
||||
estado.DistanciaMinimaCapturaAutomaticaMetros,
|
||||
out motivo
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public bool TentarRemoverUltimoPonto(
|
||||
MapaEstadoCriacao estado,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de remover pontos.";
|
||||
return false;
|
||||
}
|
||||
|
||||
MapaRuaCriacao? rua = estado.RuaAtual;
|
||||
|
||||
if (
|
||||
rua == null ||
|
||||
rua.QuantidadePontos <= 0
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A rua atual não possui pontos.";
|
||||
return false;
|
||||
}
|
||||
|
||||
rua.PontosMutaveis.RemoveAt(
|
||||
rua.QuantidadePontos - 1
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TentarCriarNovaRua(
|
||||
MapaEstadoCriacao estado,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de criar outra rua.";
|
||||
return false;
|
||||
}
|
||||
|
||||
MapaRuaCriacao? atual = estado.RuaAtual;
|
||||
|
||||
if (
|
||||
atual == null ||
|
||||
atual.QuantidadePontos < 2
|
||||
)
|
||||
{
|
||||
int numero =
|
||||
atual?.Numero
|
||||
?? (estado.IndiceRuaAtual + 1);
|
||||
|
||||
motivo =
|
||||
$"Registre pelo menos 2 pontos na rua {numero} antes de criar uma nova rua.";
|
||||
return false;
|
||||
}
|
||||
|
||||
estado.RuasMutaveis.Add(
|
||||
new MapaRuaCriacao
|
||||
{
|
||||
Numero =
|
||||
estado.RuasMutaveis.Count + 1
|
||||
}
|
||||
);
|
||||
|
||||
estado.IndiceRuaAtual =
|
||||
estado.RuasMutaveis.Count - 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TentarSelecionarRua(
|
||||
MapaEstadoCriacao estado,
|
||||
int indiceRua,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de trocar de rua.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
indiceRua < 0 ||
|
||||
indiceRua >= estado.RuasMutaveis.Count
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A rua informada não existe.";
|
||||
return false;
|
||||
}
|
||||
|
||||
estado.IndiceRuaAtual = indiceRua;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public bool TentarExcluirRua(
|
||||
MapaEstadoCriacao estado,
|
||||
int indiceRua,
|
||||
out string motivo)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
motivo = string.Empty;
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
motivo =
|
||||
"Finalize a gravação automática antes de excluir uma rua.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
indiceRua < 0 ||
|
||||
indiceRua >= estado.RuasMutaveis.Count
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"A rua informada não existe.";
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* A sessão mantém pelo menos uma rua.
|
||||
* Se existe apenas uma, excluir significa limpar a Rua 1.
|
||||
*/
|
||||
if (estado.RuasMutaveis.Count == 1)
|
||||
{
|
||||
estado.RuasMutaveis[0]
|
||||
.PontosMutaveis
|
||||
.Clear();
|
||||
|
||||
estado.IndiceRuaAtual = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
estado.RuasMutaveis.RemoveAt(indiceRua);
|
||||
RenumerarRuas(estado);
|
||||
|
||||
if (
|
||||
estado.IndiceRuaAtual >=
|
||||
estado.RuasMutaveis.Count
|
||||
)
|
||||
{
|
||||
estado.IndiceRuaAtual =
|
||||
estado.RuasMutaveis.Count - 1;
|
||||
}
|
||||
else if (indiceRua < estado.IndiceRuaAtual)
|
||||
{
|
||||
estado.IndiceRuaAtual--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void LimparPontos(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Finalize a gravação automática antes de limpar o levantamento."
|
||||
);
|
||||
}
|
||||
|
||||
estado.RuasMutaveis.Clear();
|
||||
estado.RuasMutaveis.Add(
|
||||
new MapaRuaCriacao
|
||||
{
|
||||
Numero = 1
|
||||
}
|
||||
);
|
||||
estado.IndiceRuaAtual = 0;
|
||||
}
|
||||
|
||||
|
||||
public MapaResumoCriacao CriarResumo(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
ValidarEstado(estado);
|
||||
|
||||
MapaRuaCriacao? atual = estado.RuaAtual;
|
||||
|
||||
double comprimentoAtual =
|
||||
atual == null
|
||||
? 0
|
||||
: CalcularComprimentoMetros(
|
||||
atual.Pontos
|
||||
);
|
||||
|
||||
double comprimentoTotal =
|
||||
estado.Ruas.Sum(
|
||||
rua =>
|
||||
CalcularComprimentoMetros(
|
||||
rua.Pontos
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
var ruasResumo =
|
||||
estado.Ruas
|
||||
.Select(
|
||||
(rua, indice) =>
|
||||
new MapaRuaResumoCriacao
|
||||
{
|
||||
Numero = rua.Numero,
|
||||
QuantidadePontos = rua.QuantidadePontos,
|
||||
ComprimentoMetros =
|
||||
CalcularComprimentoMetros(rua.Pontos),
|
||||
Valida = rua.Valida,
|
||||
Atual = indice == estado.IndiceRuaAtual
|
||||
}
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return new MapaResumoCriacao
|
||||
{
|
||||
EmCriacao = estado.EmCriacao,
|
||||
Nome = estado.Nome,
|
||||
RuaAtual = atual?.Numero ?? 0,
|
||||
QuantidadeRuas = estado.QuantidadeRuas,
|
||||
QuantidadeRuasValidas = estado.QuantidadeRuasValidas,
|
||||
PontosRuaAtual = atual?.QuantidadePontos ?? 0,
|
||||
PontosTotal = estado.QuantidadePontosTotal,
|
||||
ComprimentoRuaAtualMetros = comprimentoAtual,
|
||||
ComprimentoTotalMetros = comprimentoTotal,
|
||||
ModoCaptura = estado.ModoCaptura,
|
||||
GravacaoAutomaticaAtiva = estado.GravacaoAutomaticaAtiva,
|
||||
DistanciaMinimaAutomaticaMetros =
|
||||
estado.DistanciaMinimaCapturaAutomaticaMetros,
|
||||
PodeNovaRua = estado.PodeCriarNovaRua,
|
||||
PodeSalvar =
|
||||
estado.PodeSalvar &&
|
||||
!string.IsNullOrWhiteSpace(estado.Nome),
|
||||
Ruas = ruasResumo
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converte o levantamento transitório para o domínio canônico.
|
||||
/// IDs finais são sempre strings numéricas sequenciais.
|
||||
/// </summary>
|
||||
public MapaDocumento CriarDocumento(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
ValidarEstadoAtivo(estado);
|
||||
|
||||
if (estado.GravacaoAutomaticaAtiva)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Finalize a gravação automática antes de gerar o mapa."
|
||||
);
|
||||
}
|
||||
|
||||
string nome = NormalizarNome(estado.Nome);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(nome))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Informe um nome para o novo mapa."
|
||||
);
|
||||
}
|
||||
|
||||
if (estado.Ruas.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"O levantamento não possui ruas."
|
||||
);
|
||||
}
|
||||
|
||||
for (int i = 0; i < estado.Ruas.Count; i++)
|
||||
{
|
||||
MapaRuaCriacao rua = estado.Ruas[i];
|
||||
|
||||
if (
|
||||
rua == null ||
|
||||
rua.QuantidadePontos < 2
|
||||
)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"A rua {i + 1} precisa possuir pelo menos 2 pontos."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var documento =
|
||||
new MapaDocumento
|
||||
{
|
||||
Nome = nome,
|
||||
CaminhoOrigem = null,
|
||||
FormatoOrigem = MapaFormatoOrigem.Desconhecido,
|
||||
Tipo = estado.Tipo,
|
||||
Modificado = true
|
||||
};
|
||||
|
||||
for (int i = 0; i < estado.Ruas.Count; i++)
|
||||
{
|
||||
MapaRuaCriacao ruaCriacao = estado.Ruas[i];
|
||||
|
||||
string id =
|
||||
(i + 1).ToString(
|
||||
CultureInfo.InvariantCulture
|
||||
);
|
||||
|
||||
Coordinate[] coordenadas =
|
||||
ruaCriacao.Pontos
|
||||
.Select(
|
||||
ponto =>
|
||||
new Coordinate(
|
||||
ponto.Longitude,
|
||||
ponto.Latitude
|
||||
)
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
LineString geometria =
|
||||
_geometryFactory.CreateLineString(
|
||||
coordenadas
|
||||
);
|
||||
|
||||
documento.Ruas.Add(
|
||||
new MapaRua
|
||||
{
|
||||
Id = id,
|
||||
IdOriginal = null,
|
||||
IdGeradoAutomaticamente = true,
|
||||
Nome = nome,
|
||||
Geometria = geometria,
|
||||
ComprimentoMetros =
|
||||
CalcularComprimentoMetros(
|
||||
ruaCriacao.Pontos
|
||||
),
|
||||
Dist1 = 0,
|
||||
Dist2 = 0,
|
||||
IndiceFeatureOrigem = i
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
MapaResultadoValidacao validacao =
|
||||
_validationService.Validar(
|
||||
documento
|
||||
);
|
||||
|
||||
if (!validacao.Valido)
|
||||
{
|
||||
string erros =
|
||||
string.Join(
|
||||
Environment.NewLine,
|
||||
validacao.Erros
|
||||
);
|
||||
|
||||
throw new InvalidOperationException(
|
||||
"O mapa criado não passou pela validação canônica:" +
|
||||
Environment.NewLine +
|
||||
erros
|
||||
);
|
||||
}
|
||||
|
||||
return documento;
|
||||
}
|
||||
|
||||
|
||||
public static double CalcularComprimentoMetros(
|
||||
IReadOnlyList<MapaPontoCriacao> pontos)
|
||||
{
|
||||
if (
|
||||
pontos == null ||
|
||||
pontos.Count < 2
|
||||
)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double total = 0;
|
||||
|
||||
for (int i = 1; i < pontos.Count; i++)
|
||||
{
|
||||
total += CalcularDistanciaMetros(
|
||||
pontos[i - 1],
|
||||
pontos[i]
|
||||
);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
public static double CalcularDistanciaMetros(
|
||||
MapaPontoCriacao a,
|
||||
MapaPontoCriacao b)
|
||||
{
|
||||
if (
|
||||
a == null ||
|
||||
b == null
|
||||
)
|
||||
{
|
||||
return double.PositiveInfinity;
|
||||
}
|
||||
|
||||
const double raioTerraMetros = 6378137.0;
|
||||
|
||||
double lat1 = GrausParaRadianos(a.Latitude);
|
||||
double lat2 = GrausParaRadianos(b.Latitude);
|
||||
double dLat = GrausParaRadianos(b.Latitude - a.Latitude);
|
||||
double dLon = GrausParaRadianos(b.Longitude - a.Longitude);
|
||||
|
||||
double h =
|
||||
Math.Sin(dLat / 2.0) * Math.Sin(dLat / 2.0) +
|
||||
Math.Cos(lat1) *
|
||||
Math.Cos(lat2) *
|
||||
Math.Sin(dLon / 2.0) * Math.Sin(dLon / 2.0);
|
||||
|
||||
h = Math.Clamp(h, 0.0, 1.0);
|
||||
|
||||
return
|
||||
2.0 *
|
||||
raioTerraMetros *
|
||||
Math.Atan2(
|
||||
Math.Sqrt(h),
|
||||
Math.Sqrt(1.0 - h)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static bool TentarAdicionarPonto(
|
||||
MapaEstadoCriacao estado,
|
||||
MapaPontoCriacao ponto,
|
||||
double distanciaMinimaMetros,
|
||||
out string motivo)
|
||||
{
|
||||
motivo = string.Empty;
|
||||
|
||||
if (ponto == null)
|
||||
{
|
||||
motivo =
|
||||
"O ponto informado é nulo.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!CoordenadaValida(
|
||||
ponto.Latitude,
|
||||
ponto.Longitude
|
||||
)
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"O ponto informado possui coordenada inválida.";
|
||||
return false;
|
||||
}
|
||||
|
||||
MapaRuaCriacao? rua = estado.RuaAtual;
|
||||
|
||||
if (rua == null)
|
||||
{
|
||||
motivo =
|
||||
"Não existe rua atual para receber pontos.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
rua.QuantidadePontos > 0 &&
|
||||
distanciaMinimaMetros > 0
|
||||
)
|
||||
{
|
||||
MapaPontoCriacao ultimo =
|
||||
rua.Pontos[
|
||||
rua.QuantidadePontos - 1
|
||||
];
|
||||
|
||||
double distancia =
|
||||
CalcularDistanciaMetros(
|
||||
ultimo,
|
||||
ponto
|
||||
);
|
||||
|
||||
if (
|
||||
!double.IsFinite(distancia) ||
|
||||
distancia < distanciaMinimaMetros
|
||||
)
|
||||
{
|
||||
motivo =
|
||||
"Ponto ignorado: distância do último ponto = " +
|
||||
$"{distancia:F3} m; mínimo = " +
|
||||
$"{distanciaMinimaMetros:F3} m.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* O estado guarda um snapshot próprio.
|
||||
* Nenhuma referência externa fica viva aqui dentro.
|
||||
*/
|
||||
rua.PontosMutaveis.Add(
|
||||
ponto.Clonar()
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private static bool CoordenadaValida(
|
||||
double latitude,
|
||||
double longitude)
|
||||
{
|
||||
return
|
||||
double.IsFinite(latitude) &&
|
||||
double.IsFinite(longitude) &&
|
||||
latitude >= -90.0 &&
|
||||
latitude <= 90.0 &&
|
||||
longitude >= -180.0 &&
|
||||
longitude <= 180.0 &&
|
||||
(
|
||||
Math.Abs(latitude) > 1e-12 ||
|
||||
Math.Abs(longitude) > 1e-12
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static void RenumerarRuas(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
for (
|
||||
int i = 0;
|
||||
i < estado.RuasMutaveis.Count;
|
||||
i++
|
||||
)
|
||||
{
|
||||
estado.RuasMutaveis[i].Numero = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static string NormalizarNome(
|
||||
string? nome)
|
||||
{
|
||||
return
|
||||
string.IsNullOrWhiteSpace(nome)
|
||||
? string.Empty
|
||||
: nome.Trim();
|
||||
}
|
||||
|
||||
|
||||
private static double GrausParaRadianos(
|
||||
double graus)
|
||||
{
|
||||
return
|
||||
graus *
|
||||
Math.PI /
|
||||
180.0;
|
||||
}
|
||||
|
||||
|
||||
private static void ValidarEstado(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
if (estado == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(estado)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void ValidarEstadoAtivo(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
ValidarEstado(estado);
|
||||
|
||||
if (!estado.EmCriacao)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Não existe uma sessão de criação de mapa ativa."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using static AgroBase.Models.Enums;
|
||||
|
||||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Estado transitório da criação de mapa.
|
||||
/// Não altera o Workspace até o operador explicitamente concluir.
|
||||
/// </summary>
|
||||
public sealed class MapaEstadoCriacao
|
||||
{
|
||||
private readonly List<MapaRuaCriacao> _ruas = new();
|
||||
|
||||
public bool EmCriacao { get; internal set; }
|
||||
public string Nome { get; internal set; } = string.Empty;
|
||||
public TipoMapaOperacao Tipo { get; internal set; } = TipoMapaOperacao.Indefinido;
|
||||
public MapaModoCapturaCriacao ModoCaptura { get; internal set; } = MapaModoCapturaCriacao.Manual;
|
||||
public bool GravacaoAutomaticaAtiva { get; internal set; }
|
||||
public double DistanciaMinimaCapturaAutomaticaMetros { get; internal set; } = 0.50;
|
||||
public int IndiceRuaAtual { get; internal set; }
|
||||
|
||||
public IReadOnlyList<MapaRuaCriacao> Ruas => _ruas;
|
||||
internal List<MapaRuaCriacao> RuasMutaveis => _ruas;
|
||||
|
||||
public int QuantidadeRuas => _ruas.Count;
|
||||
public int QuantidadeRuasValidas => _ruas.Count(x => x != null && x.Valida);
|
||||
public int QuantidadePontosTotal => _ruas.Sum(x => x?.QuantidadePontos ?? 0);
|
||||
|
||||
public MapaRuaCriacao? RuaAtual
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IndiceRuaAtual < 0 || IndiceRuaAtual >= _ruas.Count)
|
||||
return null;
|
||||
|
||||
return _ruas[IndiceRuaAtual];
|
||||
}
|
||||
}
|
||||
|
||||
public bool PodeCriarNovaRua =>
|
||||
EmCriacao &&
|
||||
!GravacaoAutomaticaAtiva &&
|
||||
RuaAtual?.Valida == true;
|
||||
|
||||
public bool PodeSalvar =>
|
||||
EmCriacao &&
|
||||
!GravacaoAutomaticaAtiva &&
|
||||
_ruas.Count > 0 &&
|
||||
_ruas.All(x => x != null && x.Valida);
|
||||
|
||||
internal void Limpar()
|
||||
{
|
||||
_ruas.Clear();
|
||||
EmCriacao = false;
|
||||
Nome = string.Empty;
|
||||
Tipo = TipoMapaOperacao.Indefinido;
|
||||
ModoCaptura = MapaModoCapturaCriacao.Manual;
|
||||
GravacaoAutomaticaAtiva = false;
|
||||
DistanciaMinimaCapturaAutomaticaMetros = 0.50;
|
||||
IndiceRuaAtual = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Forma como os pontos da rua atual são registrados.
|
||||
/// </summary>
|
||||
public enum MapaModoCapturaCriacao
|
||||
{
|
||||
Manual = 0,
|
||||
Automatico = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
using static AgroBase.Models.Enums;
|
||||
|
||||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Snapshot de um ponto coletado durante a criação de um mapa.
|
||||
/// Latitude/Longitude são sempre WGS84.
|
||||
/// </summary>
|
||||
public sealed class MapaPontoCriacao
|
||||
{
|
||||
public double Latitude { get; set; }
|
||||
public double Longitude { get; set; }
|
||||
public double Altitude { get; set; }
|
||||
public double PrecisaoCm { get; set; }
|
||||
public int NumeroSatelites { get; set; }
|
||||
public TiposCorrecaoGPS QualidadeFix { get; set; }
|
||||
public DateTime Momento { get; set; }
|
||||
|
||||
public MapaPontoCriacao Clonar()
|
||||
{
|
||||
return new MapaPontoCriacao
|
||||
{
|
||||
Latitude = Latitude,
|
||||
Longitude = Longitude,
|
||||
Altitude = Altitude,
|
||||
PrecisaoCm = PrecisaoCm,
|
||||
NumeroSatelites = NumeroSatelites,
|
||||
QualidadeFix = QualidadeFix,
|
||||
Momento = Momento
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// DTO pronto para alimentar o cockpit de criação.
|
||||
///
|
||||
/// O bloco GNSS é preenchido pelo Controller, pois o service
|
||||
/// de domínio não conhece GpsService/GPSModel.
|
||||
/// </summary>
|
||||
public sealed class MapaResumoCriacao
|
||||
{
|
||||
public bool EmCriacao { get; init; }
|
||||
|
||||
public string Nome { get; init; } =
|
||||
string.Empty;
|
||||
|
||||
public int RuaAtual { get; init; }
|
||||
|
||||
public int QuantidadeRuas { get; init; }
|
||||
|
||||
public int QuantidadeRuasValidas { get; init; }
|
||||
|
||||
public int PontosRuaAtual { get; init; }
|
||||
|
||||
public int PontosTotal { get; init; }
|
||||
|
||||
public double ComprimentoRuaAtualMetros { get; init; }
|
||||
|
||||
public double ComprimentoTotalMetros { get; init; }
|
||||
|
||||
public MapaModoCapturaCriacao ModoCaptura { get; init; }
|
||||
|
||||
public bool GravacaoAutomaticaAtiva { get; init; }
|
||||
|
||||
public double DistanciaMinimaAutomaticaMetros { get; init; }
|
||||
|
||||
public bool PodeNovaRua { get; init; }
|
||||
|
||||
public bool PodeSalvar { get; init; }
|
||||
|
||||
public IReadOnlyList<MapaRuaResumoCriacao> Ruas { get; init; } =
|
||||
Array.Empty<MapaRuaResumoCriacao>();
|
||||
|
||||
/*
|
||||
* Estado do GNSS local para o cockpit.
|
||||
*/
|
||||
public bool LeituraGnssValida { get; init; }
|
||||
|
||||
public bool QualidadeAlta { get; init; }
|
||||
|
||||
public string StatusGnss { get; init; } =
|
||||
"GNSS sem leitura.";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Uma rua ainda em levantamento.
|
||||
/// O ID canônico definitivo é gerado na conversão para MapaDocumento.
|
||||
/// </summary>
|
||||
public sealed class MapaRuaCriacao
|
||||
{
|
||||
private readonly List<MapaPontoCriacao> _pontos = new();
|
||||
|
||||
public int Numero { get; internal set; }
|
||||
public IReadOnlyList<MapaPontoCriacao> Pontos => _pontos;
|
||||
public int QuantidadePontos => _pontos.Count;
|
||||
public bool Valida => _pontos.Count >= 2;
|
||||
|
||||
internal List<MapaPontoCriacao> PontosMutaveis => _pontos;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
namespace OperationControl.Mapas.Creation
|
||||
{
|
||||
/// <summary>
|
||||
/// Linha resumida exibida no seletor de ruas do cockpit.
|
||||
/// </summary>
|
||||
public sealed class MapaRuaResumoCriacao
|
||||
{
|
||||
public int Numero { get; init; }
|
||||
|
||||
public int QuantidadePontos { get; init; }
|
||||
|
||||
public double ComprimentoMetros { get; init; }
|
||||
|
||||
public bool Valida { get; init; }
|
||||
|
||||
public bool Atual { get; init; }
|
||||
|
||||
|
||||
public string Descricao =>
|
||||
$"Rua {Numero} • " +
|
||||
$"{QuantidadePontos} pts • " +
|
||||
$"{ComprimentoMetros:F1} m" +
|
||||
(Atual ? " • atual" : string.Empty);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
<UserControl x:Class="OperationControl.Mapas.Creation.Views.MapaCriacaoOverlay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Visibility="Collapsed"
|
||||
IsHitTestVisible="False">
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="CreationButtonStyle" TargetType="Button">
|
||||
<Setter Property="Height" Value="36"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="Background" Value="#303030"/>
|
||||
<Setter Property="BorderBrush" Value="#505050"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Padding" Value="8,4"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Center"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CreationPrimaryButtonStyle"
|
||||
TargetType="Button"
|
||||
BasedOn="{StaticResource CreationButtonStyle}">
|
||||
<Setter Property="Background" Value="#18794E"/>
|
||||
<Setter Property="BorderBrush" Value="#2AA36B"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CreationDangerButtonStyle"
|
||||
TargetType="Button"
|
||||
BasedOn="{StaticResource CreationButtonStyle}">
|
||||
<Setter Property="Background" Value="#4A2626"/>
|
||||
<Setter Property="BorderBrush" Value="#704040"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CreationLabelStyle" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="#9E9E9E"/>
|
||||
<Setter Property="FontSize" Value="10"/>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid>
|
||||
<Border Width="344"
|
||||
MaxHeight="720"
|
||||
Margin="14"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Background="#F21C1C1C"
|
||||
BorderBrush="#474747"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12"
|
||||
Padding="0">
|
||||
|
||||
<Border.Effect>
|
||||
<DropShadowEffect BlurRadius="18"
|
||||
ShadowDepth="3"
|
||||
Opacity="0.35"/>
|
||||
</Border.Effect>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Border Grid.Row="0"
|
||||
Background="#252525"
|
||||
BorderBrush="#3B3B3B"
|
||||
BorderThickness="0,0,0,1"
|
||||
CornerRadius="12,12,0,0"
|
||||
Padding="14,12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="+ Criar novo mapa"
|
||||
Foreground="White"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"/>
|
||||
<TextBlock Text="Levantamento GNSS por ruas"
|
||||
Foreground="#909090"
|
||||
FontSize="10"
|
||||
Margin="0,3,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<ScrollViewer Grid.Row="1"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="14">
|
||||
<TextBlock Text="Nome do mapa"
|
||||
Style="{StaticResource CreationLabelStyle}"/>
|
||||
|
||||
<TextBox x:Name="txtNomeMapa"
|
||||
Height="34"
|
||||
Margin="0,5,0,12"
|
||||
Padding="8,4"
|
||||
Background="#161616"
|
||||
Foreground="White"
|
||||
BorderBrush="#464646"
|
||||
BorderThickness="1"
|
||||
VerticalContentAlignment="Center"
|
||||
TextChanged="txtNomeMapa_TextChanged"/>
|
||||
|
||||
<Border Background="#242424"
|
||||
BorderBrush="#3B3B3B"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="10"
|
||||
Margin="0,0,0,10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel Grid.Column="0">
|
||||
<TextBlock Text="Rua atual"
|
||||
Style="{StaticResource CreationLabelStyle}"/>
|
||||
<TextBlock x:Name="txtRuaAtual"
|
||||
Text="Rua 1"
|
||||
Foreground="#48D8FF"
|
||||
FontSize="18"
|
||||
FontWeight="Bold"
|
||||
Margin="0,2,0,0"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock Text="Pontos / comprimento"
|
||||
Style="{StaticResource CreationLabelStyle}"/>
|
||||
<TextBlock x:Name="txtResumoRua"
|
||||
Text="0 pts • 0,0 m"
|
||||
Foreground="White"
|
||||
FontSize="13"
|
||||
FontWeight="SemiBold"
|
||||
Margin="0,5,0,0"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<Border Background="#20272A"
|
||||
BorderBrush="#35505A"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="10"
|
||||
Margin="0,0,0,12">
|
||||
<StackPanel>
|
||||
<TextBlock Text="GNSS"
|
||||
Foreground="#65D8FF"
|
||||
FontSize="10"
|
||||
FontWeight="Bold"/>
|
||||
<TextBlock x:Name="txtStatusGnss"
|
||||
Text="Aguardando leitura..."
|
||||
Foreground="#D8D8D8"
|
||||
FontSize="10"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,4,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="Captura dos pontos"
|
||||
Style="{StaticResource CreationLabelStyle}"/>
|
||||
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="0,6,0,8">
|
||||
<RadioButton x:Name="rbManual"
|
||||
Content="Manual"
|
||||
Foreground="White"
|
||||
Margin="0,0,18,0"
|
||||
Checked="rbModo_Checked"/>
|
||||
<RadioButton x:Name="rbAutomatico"
|
||||
Content="Automático"
|
||||
Foreground="White"
|
||||
Checked="rbModo_Checked"/>
|
||||
</StackPanel>
|
||||
|
||||
<Button x:Name="btnRegistrarPonto"
|
||||
Content="+ Registrar ponto"
|
||||
Style="{StaticResource CreationPrimaryButtonStyle}"
|
||||
Click="btnRegistrarPonto_Click"/>
|
||||
|
||||
<Button x:Name="btnCapturaAutomatica"
|
||||
Content="▶ Iniciar captura automática"
|
||||
Style="{StaticResource CreationPrimaryButtonStyle}"
|
||||
Margin="0,7,0,0"
|
||||
Click="btnCapturaAutomatica_Click"/>
|
||||
|
||||
<Button x:Name="btnRemoverUltimoPonto"
|
||||
Content="↶ Remover último ponto"
|
||||
Style="{StaticResource CreationButtonStyle}"
|
||||
Margin="0,7,0,0"
|
||||
Click="btnRemoverUltimoPonto_Click"/>
|
||||
|
||||
<Separator Margin="0,14,0,12"
|
||||
Background="#404040"/>
|
||||
|
||||
<TextBlock Text="Ruas levantadas"
|
||||
Style="{StaticResource CreationLabelStyle}"/>
|
||||
|
||||
<ComboBox x:Name="cmbRuas"
|
||||
Height="34"
|
||||
Margin="0,5,0,8"
|
||||
DisplayMemberPath="Descricao"
|
||||
SelectedValuePath="Numero"
|
||||
SelectionChanged="cmbRuas_SelectionChanged"/>
|
||||
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button x:Name="btnNovaRua"
|
||||
Grid.Column="0"
|
||||
Content="+ Nova rua"
|
||||
Style="{StaticResource CreationButtonStyle}"
|
||||
Click="btnNovaRua_Click"/>
|
||||
|
||||
<Button x:Name="btnExcluirRua"
|
||||
Grid.Column="2"
|
||||
Content="🗑 Excluir rua"
|
||||
Style="{StaticResource CreationDangerButtonStyle}"
|
||||
Click="btnExcluirRua_Click"/>
|
||||
</Grid>
|
||||
|
||||
<Button x:Name="btnLimpar"
|
||||
Content="Limpar levantamento"
|
||||
Style="{StaticResource CreationDangerButtonStyle}"
|
||||
Margin="0,8,0,0"
|
||||
Click="btnLimpar_Click"/>
|
||||
|
||||
<Border Background="#222222"
|
||||
BorderBrush="#373737"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="10"
|
||||
Margin="0,12,0,0">
|
||||
<TextBlock x:Name="txtResumoTotal"
|
||||
Text="1 rua • 0 pontos • 0,0 m"
|
||||
Foreground="#BDBDBD"
|
||||
FontSize="10"
|
||||
TextWrapping="Wrap"/>
|
||||
</Border>
|
||||
|
||||
<Grid Margin="0,14,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="8"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button x:Name="btnCancelar"
|
||||
Grid.Column="0"
|
||||
Content="✕ Cancelar"
|
||||
Style="{StaticResource CreationButtonStyle}"
|
||||
Click="btnCancelar_Click"/>
|
||||
|
||||
<Button x:Name="btnSalvar"
|
||||
Grid.Column="2"
|
||||
Content="💾 Salvar mapa"
|
||||
Style="{StaticResource CreationPrimaryButtonStyle}"
|
||||
Click="btnSalvar_Click"/>
|
||||
</Grid>
|
||||
|
||||
<TextBlock Text="Salvar gera o GeoJSON e já o carrega como mapa oficial."
|
||||
Foreground="#777777"
|
||||
FontSize="9"
|
||||
TextWrapping="Wrap"
|
||||
Margin="0,8,0,0"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
using OperationControl.Mapas.Creation;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace OperationControl.Mapas.Creation.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Cockpit passivo da criação de mapas.
|
||||
/// Exibe estado e emite intenções. Não altera mapa ou GNSS.
|
||||
/// </summary>
|
||||
public partial class MapaCriacaoOverlay : System.Windows.Controls.UserControl
|
||||
{
|
||||
public event Action<string>? NomeMapaAlterado;
|
||||
public event Action<MapaModoCapturaCriacao>? ModoCapturaAlterado;
|
||||
public event Action? RegistrarPontoSolicitado;
|
||||
public event Action? RemoverUltimoPontoSolicitado;
|
||||
public event Action? IniciarCapturaAutomaticaSolicitado;
|
||||
public event Action? FinalizarCapturaAutomaticaSolicitado;
|
||||
public event Action? NovaRuaSolicitada;
|
||||
public event Action<int>? RuaSelecionada;
|
||||
public event Action<int>? ExcluirRuaSolicitada;
|
||||
public event Action? LimparSolicitado;
|
||||
public event Action? SalvarSolicitado;
|
||||
public event Action? CancelarSolicitado;
|
||||
|
||||
private bool _atualizandoEstado;
|
||||
private MapaResumoCriacao? _ultimoResumo;
|
||||
|
||||
public MapaCriacaoOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public void AtualizarEstado(MapaResumoCriacao? resumo)
|
||||
{
|
||||
_atualizandoEstado = true;
|
||||
|
||||
try
|
||||
{
|
||||
_ultimoResumo = resumo;
|
||||
|
||||
bool ativo = resumo?.EmCriacao == true;
|
||||
Visibility = ativo ? Visibility.Visible : Visibility.Collapsed;
|
||||
IsHitTestVisible = ativo;
|
||||
|
||||
if (!ativo)
|
||||
return;
|
||||
|
||||
string nome = resumo!.Nome ?? string.Empty;
|
||||
|
||||
if (!string.Equals(txtNomeMapa.Text, nome, StringComparison.Ordinal))
|
||||
{
|
||||
int caret = txtNomeMapa.CaretIndex;
|
||||
txtNomeMapa.Text = nome;
|
||||
txtNomeMapa.CaretIndex = Math.Min(caret, txtNomeMapa.Text.Length);
|
||||
}
|
||||
|
||||
txtRuaAtual.Text = resumo.RuaAtual > 0
|
||||
? $"Rua {resumo.RuaAtual}"
|
||||
: "Sem rua";
|
||||
|
||||
txtResumoRua.Text =
|
||||
$"{resumo.PontosRuaAtual} pts • {resumo.ComprimentoRuaAtualMetros:F1} m";
|
||||
|
||||
txtStatusGnss.Text = string.IsNullOrWhiteSpace(resumo.StatusGnss)
|
||||
? "GNSS sem leitura."
|
||||
: resumo.StatusGnss;
|
||||
|
||||
rbManual.IsChecked = resumo.ModoCaptura == MapaModoCapturaCriacao.Manual;
|
||||
rbAutomatico.IsChecked = resumo.ModoCaptura == MapaModoCapturaCriacao.Automatico;
|
||||
|
||||
cmbRuas.ItemsSource = resumo.Ruas;
|
||||
cmbRuas.SelectedValue = resumo.RuaAtual;
|
||||
|
||||
bool gravando = resumo.GravacaoAutomaticaAtiva;
|
||||
|
||||
btnRegistrarPonto.Visibility =
|
||||
resumo.ModoCaptura == MapaModoCapturaCriacao.Manual
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
btnCapturaAutomatica.Visibility =
|
||||
resumo.ModoCaptura == MapaModoCapturaCriacao.Automatico
|
||||
? Visibility.Visible
|
||||
: Visibility.Collapsed;
|
||||
|
||||
btnRegistrarPonto.IsEnabled = !gravando && resumo.LeituraGnssValida;
|
||||
|
||||
btnCapturaAutomatica.Content = gravando
|
||||
? "■ Finalizar captura automática"
|
||||
: "▶ Iniciar captura automática";
|
||||
|
||||
btnCapturaAutomatica.IsEnabled = gravando || resumo.LeituraGnssValida;
|
||||
btnRemoverUltimoPonto.IsEnabled = !gravando && resumo.PontosRuaAtual > 0;
|
||||
btnNovaRua.IsEnabled = resumo.PodeNovaRua;
|
||||
btnExcluirRua.IsEnabled = !gravando && resumo.QuantidadeRuas > 0;
|
||||
btnLimpar.IsEnabled = !gravando && resumo.PontosTotal > 0;
|
||||
cmbRuas.IsEnabled = !gravando && resumo.QuantidadeRuas > 0;
|
||||
rbManual.IsEnabled = !gravando;
|
||||
rbAutomatico.IsEnabled = !gravando;
|
||||
txtNomeMapa.IsEnabled = !gravando;
|
||||
btnSalvar.IsEnabled = resumo.PodeSalvar;
|
||||
|
||||
txtResumoTotal.Text =
|
||||
$"{resumo.QuantidadeRuas} {(resumo.QuantidadeRuas == 1 ? "rua" : "ruas")} • " +
|
||||
$"{resumo.PontosTotal} {(resumo.PontosTotal == 1 ? "ponto" : "pontos")} • " +
|
||||
$"{resumo.ComprimentoTotalMetros:F1} m" +
|
||||
Environment.NewLine +
|
||||
$"{resumo.QuantidadeRuasValidas}/{resumo.QuantidadeRuas} ruas válidas";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_atualizandoEstado = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void txtNomeMapa_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_atualizandoEstado)
|
||||
return;
|
||||
|
||||
NomeMapaAlterado?.Invoke(txtNomeMapa.Text);
|
||||
}
|
||||
|
||||
private void rbModo_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_atualizandoEstado)
|
||||
return;
|
||||
|
||||
if (rbAutomatico.IsChecked == true)
|
||||
ModoCapturaAlterado?.Invoke(MapaModoCapturaCriacao.Automatico);
|
||||
else if (rbManual.IsChecked == true)
|
||||
ModoCapturaAlterado?.Invoke(MapaModoCapturaCriacao.Manual);
|
||||
}
|
||||
|
||||
private void btnRegistrarPonto_Click(object sender, RoutedEventArgs e) =>
|
||||
RegistrarPontoSolicitado?.Invoke();
|
||||
|
||||
private void btnRemoverUltimoPonto_Click(object sender, RoutedEventArgs e) =>
|
||||
RemoverUltimoPontoSolicitado?.Invoke();
|
||||
|
||||
private void btnCapturaAutomatica_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_ultimoResumo?.GravacaoAutomaticaAtiva == true)
|
||||
FinalizarCapturaAutomaticaSolicitado?.Invoke();
|
||||
else
|
||||
IniciarCapturaAutomaticaSolicitado?.Invoke();
|
||||
}
|
||||
|
||||
private void btnNovaRua_Click(object sender, RoutedEventArgs e) =>
|
||||
NovaRuaSolicitada?.Invoke();
|
||||
|
||||
private void cmbRuas_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_atualizandoEstado)
|
||||
return;
|
||||
|
||||
if (cmbRuas.SelectedValue is int numeroRua)
|
||||
RuaSelecionada?.Invoke(numeroRua);
|
||||
}
|
||||
|
||||
private void btnExcluirRua_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int numeroRua = _ultimoResumo?.RuaAtual ?? 0;
|
||||
if (numeroRua > 0)
|
||||
ExcluirRuaSolicitada?.Invoke(numeroRua);
|
||||
}
|
||||
|
||||
private void btnLimpar_Click(object sender, RoutedEventArgs e) =>
|
||||
LimparSolicitado?.Invoke();
|
||||
|
||||
private void btnSalvar_Click(object sender, RoutedEventArgs e) =>
|
||||
SalvarSolicitado?.Invoke();
|
||||
|
||||
private void btnCancelar_Click(object sender, RoutedEventArgs e) =>
|
||||
CancelarSolicitado?.Invoke();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,412 @@
|
|||
using Mapsui;
|
||||
using Mapsui.Layers;
|
||||
using Mapsui.Nts;
|
||||
using Mapsui.Projections;
|
||||
using Mapsui.Styles;
|
||||
using NetTopologySuite.Geometries;
|
||||
using OperationControl.Mapas.Creation;
|
||||
using Brush = Mapsui.Styles.Brush;
|
||||
using Color = Mapsui.Styles.Color;
|
||||
using Font = Mapsui.Styles.Font;
|
||||
using Pen = Mapsui.Styles.Pen;
|
||||
|
||||
namespace OperationControl.Mapas.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Preview georreferenciado do mapa ainda em levantamento.
|
||||
///
|
||||
/// Regras visuais:
|
||||
/// - ruas concluídas: verde;
|
||||
/// - rua atual: ciano e mais espessa;
|
||||
/// - pontos da rua atual: marcadores numerados;
|
||||
/// - primeira posição de cada rua: etiqueta R1, R2...
|
||||
///
|
||||
/// Nada aqui altera o MapaWorkspace.
|
||||
/// </summary>
|
||||
public sealed class MapaCriacaoRenderService
|
||||
{
|
||||
public const string NomeCamada =
|
||||
"MapaCriacaoPreview";
|
||||
|
||||
|
||||
public MemoryLayer CriarCamada(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
if (estado == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(estado)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return new MemoryLayer
|
||||
{
|
||||
Name =
|
||||
NomeCamada,
|
||||
|
||||
Features =
|
||||
CriarFeatures(
|
||||
estado
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public void AtualizarCamada(
|
||||
MemoryLayer camada,
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
if (camada == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(camada)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (estado == null)
|
||||
{
|
||||
throw new ArgumentNullException(
|
||||
nameof(estado)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
camada.Features =
|
||||
CriarFeatures(
|
||||
estado
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static IEnumerable<IFeature>
|
||||
CriarFeatures(
|
||||
MapaEstadoCriacao estado)
|
||||
{
|
||||
var features =
|
||||
new List<IFeature>();
|
||||
|
||||
|
||||
if (!estado.EmCriacao)
|
||||
{
|
||||
return features;
|
||||
}
|
||||
|
||||
|
||||
for (
|
||||
int indiceRua = 0;
|
||||
indiceRua < estado.Ruas.Count;
|
||||
indiceRua++
|
||||
)
|
||||
{
|
||||
MapaRuaCriacao rua =
|
||||
estado.Ruas[indiceRua];
|
||||
|
||||
|
||||
if (
|
||||
rua == null ||
|
||||
rua.Pontos.Count == 0
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
bool atual =
|
||||
indiceRua ==
|
||||
estado.IndiceRuaAtual;
|
||||
|
||||
|
||||
Color corLinha =
|
||||
atual
|
||||
? new Color(
|
||||
0,
|
||||
210,
|
||||
255,
|
||||
255
|
||||
)
|
||||
: new Color(
|
||||
95,
|
||||
207,
|
||||
128,
|
||||
255
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* LINHA DA RUA
|
||||
*/
|
||||
if (
|
||||
rua.Pontos.Count >= 2
|
||||
)
|
||||
{
|
||||
Coordinate[] coordenadas =
|
||||
rua
|
||||
.Pontos
|
||||
.Select(
|
||||
ponto =>
|
||||
{
|
||||
var mercator =
|
||||
SphericalMercator
|
||||
.FromLonLat(
|
||||
ponto.Longitude,
|
||||
ponto.Latitude
|
||||
);
|
||||
|
||||
|
||||
return new Coordinate(
|
||||
mercator.x,
|
||||
mercator.y
|
||||
);
|
||||
}
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
|
||||
var featureLinha =
|
||||
new GeometryFeature
|
||||
{
|
||||
Geometry =
|
||||
new LineString(
|
||||
coordenadas
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
featureLinha["Tipo"] =
|
||||
"MapaCriacaoRua";
|
||||
|
||||
featureLinha["Rua"] =
|
||||
rua.Numero;
|
||||
|
||||
|
||||
featureLinha.Styles.Add(
|
||||
new VectorStyle
|
||||
{
|
||||
Line =
|
||||
new Pen(
|
||||
corLinha,
|
||||
atual ? 5 : 3
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
features.Add(
|
||||
featureLinha
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* MARCADOR DO INÍCIO DA RUA
|
||||
*/
|
||||
MapaPontoCriacao primeiro =
|
||||
rua.Pontos[0];
|
||||
|
||||
|
||||
var inicioMercator =
|
||||
SphericalMercator
|
||||
.FromLonLat(
|
||||
primeiro.Longitude,
|
||||
primeiro.Latitude
|
||||
);
|
||||
|
||||
|
||||
var marcadorRua =
|
||||
new PointFeature(
|
||||
inicioMercator.x,
|
||||
inicioMercator.y
|
||||
);
|
||||
|
||||
|
||||
marcadorRua["Tipo"] =
|
||||
"MapaCriacaoInicioRua";
|
||||
|
||||
marcadorRua["label"] =
|
||||
$"R{rua.Numero}";
|
||||
|
||||
|
||||
marcadorRua.Styles.Add(
|
||||
new SymbolStyle
|
||||
{
|
||||
SymbolType =
|
||||
SymbolType.Ellipse,
|
||||
|
||||
SymbolScale =
|
||||
atual
|
||||
? 1.25
|
||||
: 1.0,
|
||||
|
||||
Fill =
|
||||
new Brush(
|
||||
corLinha
|
||||
),
|
||||
|
||||
Outline =
|
||||
new Pen(
|
||||
Color.White,
|
||||
2
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
marcadorRua.Styles.Add(
|
||||
new LabelStyle
|
||||
{
|
||||
LabelColumn =
|
||||
"label",
|
||||
|
||||
ForeColor =
|
||||
Color.White,
|
||||
|
||||
BackColor =
|
||||
null,
|
||||
|
||||
HorizontalAlignment =
|
||||
LabelStyle
|
||||
.HorizontalAlignmentEnum
|
||||
.Center,
|
||||
|
||||
VerticalAlignment =
|
||||
LabelStyle
|
||||
.VerticalAlignmentEnum
|
||||
.Center,
|
||||
|
||||
Font =
|
||||
new Font
|
||||
{
|
||||
Size = 10
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
features.Add(
|
||||
marcadorRua
|
||||
);
|
||||
|
||||
|
||||
/*
|
||||
* PONTOS DA RUA ATUAL
|
||||
*
|
||||
* Nas ruas concluídas evitamos centenas de marcadores
|
||||
* para manter o mapa limpo e leve.
|
||||
*/
|
||||
if (!atual)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for (
|
||||
int indicePonto = 0;
|
||||
indicePonto < rua.Pontos.Count;
|
||||
indicePonto++
|
||||
)
|
||||
{
|
||||
MapaPontoCriacao ponto =
|
||||
rua.Pontos[indicePonto];
|
||||
|
||||
|
||||
var mercator =
|
||||
SphericalMercator
|
||||
.FromLonLat(
|
||||
ponto.Longitude,
|
||||
ponto.Latitude
|
||||
);
|
||||
|
||||
|
||||
var featurePonto =
|
||||
new PointFeature(
|
||||
mercator.x,
|
||||
mercator.y
|
||||
);
|
||||
|
||||
|
||||
featurePonto["Tipo"] =
|
||||
"MapaCriacaoPonto";
|
||||
|
||||
featurePonto["Rua"] =
|
||||
rua.Numero;
|
||||
|
||||
featurePonto["Ordem"] =
|
||||
indicePonto + 1;
|
||||
|
||||
featurePonto["label"] =
|
||||
(indicePonto + 1)
|
||||
.ToString();
|
||||
|
||||
|
||||
featurePonto.Styles.Add(
|
||||
new SymbolStyle
|
||||
{
|
||||
SymbolType =
|
||||
SymbolType.Ellipse,
|
||||
|
||||
SymbolScale =
|
||||
0.95,
|
||||
|
||||
Fill =
|
||||
new Brush(
|
||||
new Color(
|
||||
0,
|
||||
210,
|
||||
255,
|
||||
255
|
||||
)
|
||||
),
|
||||
|
||||
Outline =
|
||||
new Pen(
|
||||
Color.White,
|
||||
2
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
featurePonto.Styles.Add(
|
||||
new LabelStyle
|
||||
{
|
||||
LabelColumn =
|
||||
"label",
|
||||
|
||||
ForeColor =
|
||||
Color.White,
|
||||
|
||||
BackColor =
|
||||
null,
|
||||
|
||||
HorizontalAlignment =
|
||||
LabelStyle
|
||||
.HorizontalAlignmentEnum
|
||||
.Center,
|
||||
|
||||
VerticalAlignment =
|
||||
LabelStyle
|
||||
.VerticalAlignmentEnum
|
||||
.Center,
|
||||
|
||||
Font =
|
||||
new Font
|
||||
{
|
||||
Size = 9
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
features.Add(
|
||||
featurePonto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return features;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ namespace OperationControl.ViewModels
|
|||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public event Action<string, AgroBase.Models.Enums.TipoMapaOperacao>? CarregarMapaSolicitado;
|
||||
public event Action? CriarMapaSolicitado;
|
||||
public event Action? EditarMapaSolicitado;
|
||||
|
||||
// Método, latitude, longitude, altitude, offset frontal cm, offset lateral cm
|
||||
|
|
@ -63,6 +64,7 @@ namespace OperationControl.ViewModels
|
|||
};
|
||||
|
||||
public ICommand CarregarMapaCommand { get; }
|
||||
public ICommand CriarMapaCommand { get; }
|
||||
public ICommand EditarMapaCommand { get; }
|
||||
public ICommand FixarBaseCommand { get; }
|
||||
public ICommand AplicarOffsetCommand { get; }
|
||||
|
|
@ -73,6 +75,7 @@ namespace OperationControl.ViewModels
|
|||
public PreparacaoMapaBottomViewModel()
|
||||
{
|
||||
CarregarMapaCommand = new RelayCommand(_ => ExecutarCarregarMapa(), _ => PodeCarregarMapa);
|
||||
CriarMapaCommand = new RelayCommand(_ => CriarMapaSolicitado?.Invoke(), _ => PodeCriarMapa);
|
||||
EditarMapaCommand = new RelayCommand(_ => EditarMapaSolicitado?.Invoke(), _ => PodeEditarMapa);
|
||||
FixarBaseCommand = new RelayCommand(_ => ExecutarFixarBase(), _ => PodeFixarBase);
|
||||
AplicarOffsetCommand = new RelayCommand(_ => ExecutarAplicarOffset(), _ => PodeEditarOffsetBase);
|
||||
|
|
@ -124,6 +127,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
private bool _mapaCarregado;
|
||||
private bool _edicaoMapaAtiva;
|
||||
private bool _criacaoMapaAtiva;
|
||||
|
||||
/// <summary>
|
||||
/// Enquanto o editor estiver aberto nesta etapa:
|
||||
|
|
@ -137,20 +141,32 @@ namespace OperationControl.ViewModels
|
|||
public bool EdicaoMapaAtiva =>
|
||||
_edicaoMapaAtiva;
|
||||
|
||||
public bool CriacaoMapaAtiva =>
|
||||
_criacaoMapaAtiva;
|
||||
|
||||
public bool PodeCriarMapa =>
|
||||
!_edicaoMapaAtiva &&
|
||||
!_criacaoMapaAtiva &&
|
||||
!FixacaoEmAndamento &&
|
||||
!AlteracaoPapelGnssEmAndamento;
|
||||
|
||||
public bool PodeEditarMapa =>
|
||||
_mapaCarregado &&
|
||||
!_edicaoMapaAtiva &&
|
||||
!_criacaoMapaAtiva &&
|
||||
!FixacaoEmAndamento &&
|
||||
!AlteracaoPapelGnssEmAndamento;
|
||||
|
||||
public bool PodeContinuar =>
|
||||
_mapaCarregado &&
|
||||
!_edicaoMapaAtiva &&
|
||||
!_criacaoMapaAtiva &&
|
||||
!FixacaoEmAndamento &&
|
||||
!AlteracaoPapelGnssEmAndamento;
|
||||
|
||||
public bool PodeVoltar =>
|
||||
!_edicaoMapaAtiva &&
|
||||
!_criacaoMapaAtiva &&
|
||||
!FixacaoEmAndamento &&
|
||||
!AlteracaoPapelGnssEmAndamento;
|
||||
|
||||
|
|
@ -201,6 +217,10 @@ namespace OperationControl.ViewModels
|
|||
nameof(PodeEditarMapa)
|
||||
);
|
||||
|
||||
OnPropertyChanged(
|
||||
nameof(PodeCriarMapa)
|
||||
);
|
||||
|
||||
OnPropertyChanged(
|
||||
nameof(PodeCarregarMapa)
|
||||
);
|
||||
|
|
@ -214,6 +234,22 @@ namespace OperationControl.ViewModels
|
|||
);
|
||||
}
|
||||
|
||||
public void DefinirCriacaoMapaAtiva(bool ativa)
|
||||
{
|
||||
if (_criacaoMapaAtiva == ativa)
|
||||
return;
|
||||
|
||||
_criacaoMapaAtiva = ativa;
|
||||
|
||||
OnPropertyChanged(nameof(CriacaoMapaAtiva));
|
||||
OnPropertyChanged(nameof(PodeCriarMapa));
|
||||
OnPropertyChanged(nameof(PodeEditarMapa));
|
||||
OnPropertyChanged(nameof(PodeCarregarMapa));
|
||||
OnPropertyChanged(nameof(PodeContinuar));
|
||||
OnPropertyChanged(nameof(PodeVoltar));
|
||||
}
|
||||
|
||||
|
||||
private GnssPapelEstacao _papelGnssAtual = GnssPapelEstacao.Indefinido;
|
||||
public GnssPapelEstacao PapelGnssAtual
|
||||
{
|
||||
|
|
@ -316,6 +352,10 @@ namespace OperationControl.ViewModels
|
|||
nameof(PodeEditarMapa)
|
||||
);
|
||||
|
||||
OnPropertyChanged(
|
||||
nameof(PodeCriarMapa)
|
||||
);
|
||||
|
||||
OnPropertyChanged(
|
||||
nameof(PodeEditarMetodoFixacao)
|
||||
);
|
||||
|
|
@ -451,6 +491,7 @@ namespace OperationControl.ViewModels
|
|||
OnPropertyChanged(nameof(FixacaoEmAndamento));
|
||||
OnPropertyChanged(nameof(PodeCarregarMapa));
|
||||
OnPropertyChanged(nameof(PodeEditarMapa));
|
||||
OnPropertyChanged(nameof(PodeCriarMapa));
|
||||
OnPropertyChanged(nameof(PodeEditarMetodoFixacao));
|
||||
OnPropertyChanged(nameof(PodeEditarCoordenadasManuais));
|
||||
OnPropertyChanged(nameof(PodeEditarOffsetBase));
|
||||
|
|
@ -480,6 +521,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
public bool PodeCarregarMapa =>
|
||||
!_edicaoMapaAtiva &&
|
||||
!_criacaoMapaAtiva &&
|
||||
!FixacaoEmAndamento &&
|
||||
!AlteracaoPapelGnssEmAndamento;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Newtonsoft.Json.Linq;
|
|||
using OperationControl.Controls;
|
||||
using OperationControl.Helpers;
|
||||
using OperationControl.Mapas;
|
||||
using OperationControl.Mapas.Creation;
|
||||
using OperationControl.Mapas.Editing;
|
||||
using OperationControl.Mapas.Interaction;
|
||||
using OperationControl.Mapas.Models;
|
||||
|
|
@ -58,6 +59,7 @@ namespace OperationControl.ViewModels
|
|||
|
||||
_viewPreparacaoMapaBottom = new PreparacaoMapaBottomView();
|
||||
_viewPreparacaoMapaBottom._vm.CarregarMapaSolicitado += OnCarregarMapaSolicitado;
|
||||
_viewPreparacaoMapaBottom._vm.CriarMapaSolicitado += OnCriarMapaSolicitado;
|
||||
_viewPreparacaoMapaBottom._vm.EditarMapaSolicitado += OnEditarMapaPreparacaoSolicitado;
|
||||
_viewPreparacaoMapaBottom._vm.FixarBaseSolicitado += OnFixarBaseSolicitado;
|
||||
_viewPreparacaoMapaBottom._vm.AplicarOffsetSolicitado += OnAplicarOffsetSolicitado;
|
||||
|
|
@ -97,6 +99,47 @@ namespace OperationControl.ViewModels
|
|||
.SalvarMapaEditadoSolicitado +=
|
||||
OnSalvarMapaEditadoSolicitado;
|
||||
|
||||
var criacaoOverlay =
|
||||
_viewPreparacaoMapa
|
||||
.MapaPreparacao
|
||||
.CriacaoOverlay;
|
||||
|
||||
criacaoOverlay.NomeMapaAlterado +=
|
||||
nome => _mapas.DefinirNomeCriacaoMapa(nome);
|
||||
|
||||
criacaoOverlay.ModoCapturaAlterado +=
|
||||
modo => _mapas.DefinirModoCapturaCriacao(modo);
|
||||
|
||||
criacaoOverlay.RegistrarPontoSolicitado +=
|
||||
() => _mapas.RegistrarPontoCriacaoManual();
|
||||
|
||||
criacaoOverlay.RemoverUltimoPontoSolicitado +=
|
||||
() => _mapas.RemoverUltimoPontoCriacao();
|
||||
|
||||
criacaoOverlay.IniciarCapturaAutomaticaSolicitado +=
|
||||
() => _mapas.IniciarGravacaoAutomaticaCriacao();
|
||||
|
||||
criacaoOverlay.FinalizarCapturaAutomaticaSolicitado +=
|
||||
() => _mapas.FinalizarGravacaoAutomaticaCriacao();
|
||||
|
||||
criacaoOverlay.NovaRuaSolicitada +=
|
||||
() => _mapas.CriarNovaRuaMapa();
|
||||
|
||||
criacaoOverlay.RuaSelecionada +=
|
||||
numeroRua => _mapas.SelecionarRuaCriacao(numeroRua);
|
||||
|
||||
criacaoOverlay.ExcluirRuaSolicitada +=
|
||||
OnExcluirRuaCriacaoSolicitado;
|
||||
|
||||
criacaoOverlay.LimparSolicitado +=
|
||||
OnLimparCriacaoSolicitado;
|
||||
|
||||
criacaoOverlay.SalvarSolicitado +=
|
||||
OnSalvarMapaCriadoSolicitado;
|
||||
|
||||
criacaoOverlay.CancelarSolicitado +=
|
||||
OnCancelarCriacaoMapaSolicitado;
|
||||
|
||||
_viewOperacaoLeft = new OperacaoLeftView();
|
||||
|
||||
_viewOperacaoRight = new OperacaoRightView();
|
||||
|
|
@ -118,6 +161,7 @@ namespace OperationControl.ViewModels
|
|||
_mapas.ValidacaoEdicaoFalhou += OnMapaValidacaoEdicaoFalhou;
|
||||
_mapas.EstadoMapaAlterado += OnEstadoMapaAlterado;
|
||||
_mapas.EstadoEdicaoAlterado += OnEstadoMapaAlterado;
|
||||
_mapas.EstadoCriacaoAlterado += OnEstadoCriacaoAlterado;
|
||||
|
||||
// Commands
|
||||
BackCommand = new RelayCommand(_ => GoBack(), _ => CanGoBack);
|
||||
|
|
@ -240,7 +284,10 @@ namespace OperationControl.ViewModels
|
|||
CurrentStep != DockStep.ConfigConexao &&
|
||||
!(
|
||||
CurrentStep == DockStep.PreparacaoMapa &&
|
||||
_mapas.EdicaoAtiva
|
||||
(
|
||||
_mapas.EdicaoAtiva ||
|
||||
_mapas.CriacaoMapaAtiva
|
||||
)
|
||||
);
|
||||
|
||||
public bool CanGoNext
|
||||
|
|
@ -254,7 +301,8 @@ namespace OperationControl.ViewModels
|
|||
|
||||
DockStep.PreparacaoMapa =>
|
||||
MapaCarregado &&
|
||||
!_mapas.EdicaoAtiva,
|
||||
!_mapas.EdicaoAtiva &&
|
||||
!_mapas.CriacaoMapaAtiva,
|
||||
|
||||
_ =>
|
||||
false
|
||||
|
|
@ -1315,6 +1363,21 @@ namespace OperationControl.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Snapshot GNSS compartilhado pelas ferramentas de mapa
|
||||
* que fazem levantamento em campo:
|
||||
*
|
||||
* - borda GNSS do editor;
|
||||
* - criação de mapa por ruas.
|
||||
*
|
||||
* O Controller faz Clone() internamente e aplica a mesma
|
||||
* validação de freshness já usada pela borda.
|
||||
*/
|
||||
_mapas.AtualizarGnssLocal(
|
||||
dados
|
||||
);
|
||||
|
||||
|
||||
_mapas.AtualizarBase(
|
||||
dados,
|
||||
mapaAtual
|
||||
|
|
@ -1724,6 +1787,8 @@ namespace OperationControl.ViewModels
|
|||
|
||||
private void OnEstadoMapaAlterado()
|
||||
{
|
||||
MapaCarregado = _mapas.PossuiMapa;
|
||||
|
||||
_viewPreparacaoMapaBottom?
|
||||
._vm?
|
||||
.DefinirEdicaoMapaAtiva(
|
||||
|
|
@ -1744,6 +1809,27 @@ namespace OperationControl.ViewModels
|
|||
AtualizarEstadoMapaParametrizacao();
|
||||
}
|
||||
|
||||
private void OnEstadoCriacaoAlterado()
|
||||
{
|
||||
bool ativa = _mapas.CriacaoMapaAtiva;
|
||||
|
||||
_viewPreparacaoMapaBottom?
|
||||
._vm?
|
||||
.DefinirCriacaoMapaAtiva(ativa);
|
||||
|
||||
_viewPreparacaoMapa?
|
||||
.MapaPreparacao?
|
||||
.CriacaoOverlay?
|
||||
.AtualizarEstado(
|
||||
_mapas.ObterResumoCriacaoMapa()
|
||||
);
|
||||
|
||||
OnPropertyChanged(nameof(CanGoBack));
|
||||
OnPropertyChanged(nameof(CanGoNext));
|
||||
CommandManager.InvalidateRequerySuggested();
|
||||
}
|
||||
|
||||
|
||||
private void AtualizarEstadoMapaParametrizacao()
|
||||
{
|
||||
void Atualizar()
|
||||
|
|
@ -1907,6 +1993,206 @@ namespace OperationControl.ViewModels
|
|||
);
|
||||
}
|
||||
|
||||
private void OnCriarMapaSolicitado()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CurrentStep != DockStep.PreparacaoMapa)
|
||||
return;
|
||||
|
||||
if (_mapas.EdicaoAtiva || _mapas.CriacaoMapaAtiva)
|
||||
return;
|
||||
|
||||
var dlgTipo = new OperationControl.Windows.SelecaoTipoMapa
|
||||
{
|
||||
Owner = Models.Variaveis.Dock
|
||||
};
|
||||
|
||||
if (dlgTipo.ShowDialog() != true || dlgTipo.Resultado == null)
|
||||
return;
|
||||
|
||||
MapViewControl? mapa = _viewPreparacaoMapa?.MapaPreparacao;
|
||||
if (mapa == null)
|
||||
return;
|
||||
|
||||
_mapas.IniciarCriacaoMapa(
|
||||
mapa,
|
||||
nome: "NovoMapa",
|
||||
tipo: dlgTipo.Resultado.Value,
|
||||
distanciaMinimaAutomaticaMetros: 0.50
|
||||
);
|
||||
|
||||
_viewPreparacaoMapaBottom?._vm?.DefinirCriacaoMapaAtiva(true);
|
||||
|
||||
if (_viewPreparacaoMapaBottom?._vm != null)
|
||||
{
|
||||
_viewPreparacaoMapaBottom._vm.StatusTexto =
|
||||
"Criação de mapa ativa. Registre os pontos das ruas pelo GNSS.";
|
||||
}
|
||||
|
||||
Models.Variaveis.MostrarLog(
|
||||
"[MAPA][CRIACAO][UI] Cockpit aberto na preparação."
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Models.Variaveis.MostrarLog(
|
||||
"[MAPA][CRIACAO][UI][ERRO] " + ex
|
||||
);
|
||||
|
||||
MessageBox.Show(
|
||||
"Não foi possível iniciar a criação do mapa." +
|
||||
Environment.NewLine + Environment.NewLine + ex.Message,
|
||||
"Criar mapa",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnExcluirRuaCriacaoSolicitado(int numeroRua)
|
||||
{
|
||||
var resposta = MessageBox.Show(
|
||||
$"Excluir a Rua {numeroRua} do levantamento?",
|
||||
"Excluir rua",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
);
|
||||
|
||||
if (resposta == MessageBoxResult.Yes)
|
||||
_mapas.ExcluirRuaCriacao(numeroRua);
|
||||
}
|
||||
|
||||
|
||||
private void OnLimparCriacaoSolicitado()
|
||||
{
|
||||
var resposta = MessageBox.Show(
|
||||
"Limpar todos os pontos e ruas levantados?",
|
||||
"Limpar levantamento",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
);
|
||||
|
||||
if (resposta == MessageBoxResult.Yes)
|
||||
_mapas.LimparLevantamentoCriacao();
|
||||
}
|
||||
|
||||
|
||||
private void OnCancelarCriacaoMapaSolicitado()
|
||||
{
|
||||
if (!_mapas.CriacaoMapaAtiva)
|
||||
return;
|
||||
|
||||
MapaResumoCriacao resumo = _mapas.ObterResumoCriacaoMapa();
|
||||
|
||||
if (resumo.PontosTotal > 0)
|
||||
{
|
||||
var resposta = MessageBox.Show(
|
||||
"Cancelar a criação do mapa?" +
|
||||
Environment.NewLine + Environment.NewLine +
|
||||
"Os pontos ainda não salvos serão descartados.",
|
||||
"Cancelar criação",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
);
|
||||
|
||||
if (resposta != MessageBoxResult.Yes)
|
||||
return;
|
||||
}
|
||||
|
||||
_mapas.CancelarCriacaoMapa();
|
||||
|
||||
if (_viewPreparacaoMapaBottom?._vm != null)
|
||||
{
|
||||
_viewPreparacaoMapaBottom._vm.StatusTexto =
|
||||
_mapas.PossuiMapa
|
||||
? "Criação cancelada. O mapa anterior foi restaurado."
|
||||
: "Criação cancelada.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnSalvarMapaCriadoSolicitado()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_mapas.CriacaoMapaAtiva)
|
||||
return;
|
||||
|
||||
MapaResumoCriacao resumo = _mapas.ObterResumoCriacaoMapa();
|
||||
|
||||
if (!resumo.PodeSalvar)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"Para salvar o mapa, informe um nome e garanta que " +
|
||||
"todas as ruas possuam pelo menos 2 pontos.",
|
||||
"Mapa incompleto",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var dialog = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Title = "Salvar novo mapa operacional",
|
||||
Filter = "GeoJSON (*.geojson)|*.geojson",
|
||||
DefaultExt = ".geojson",
|
||||
AddExtension = true,
|
||||
OverwritePrompt = true,
|
||||
FileName = _mapas.ObterNomeArquivoSugeridoMapaCriado()
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() != true)
|
||||
return;
|
||||
|
||||
MapaDocumento documento =
|
||||
_mapas.SalvarECarregarMapaCriado(dialog.FileName);
|
||||
|
||||
MapaCarregado = true;
|
||||
|
||||
_mapas.PrepararMapa(
|
||||
_viewPreparacaoMapa?.MapaPreparacao,
|
||||
modoInteracao: MapaModoInteracao.Visualizacao,
|
||||
enquadrarDocumento: true
|
||||
);
|
||||
|
||||
if (_viewPreparacaoMapaBottom?._vm != null)
|
||||
{
|
||||
_viewPreparacaoMapaBottom._vm.StatusTexto =
|
||||
$"Mapa criado e carregado: {System.IO.Path.GetFileName(dialog.FileName)}";
|
||||
}
|
||||
|
||||
MessageBox.Show(
|
||||
"Mapa criado com sucesso." + Environment.NewLine + Environment.NewLine +
|
||||
$"Ruas: {documento.QuantidadeRuas}" + Environment.NewLine +
|
||||
$"Comprimento total: {documento.ComprimentoTotalMetros:F1} m" +
|
||||
Environment.NewLine + Environment.NewLine +
|
||||
dialog.FileName + Environment.NewLine + Environment.NewLine +
|
||||
"O arquivo já está carregado como mapa oficial.",
|
||||
"Mapa criado",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Models.Variaveis.MostrarLog(
|
||||
"[MAPA][CRIACAO][SALVAR][ERRO] " + ex
|
||||
);
|
||||
|
||||
MessageBox.Show(
|
||||
"Não foi possível salvar/carregar o novo mapa." +
|
||||
Environment.NewLine + Environment.NewLine + ex.Message,
|
||||
"Erro ao criar mapa",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnEditarMapaPreparacaoSolicitado()
|
||||
{
|
||||
try
|
||||
|
|
@ -1933,6 +2219,17 @@ namespace OperationControl.ViewModels
|
|||
}
|
||||
|
||||
|
||||
if (_mapas.CriacaoMapaAtiva)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"Finalize ou cancele a criação do mapa antes de abrir o editor.",
|
||||
"Criação de mapa ativa",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_mapas.EdicaoAtiva)
|
||||
{
|
||||
/*
|
||||
|
|
@ -2155,6 +2452,16 @@ namespace OperationControl.ViewModels
|
|||
return;
|
||||
}
|
||||
|
||||
if (_mapas.CriacaoMapaAtiva)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"Finalize ou cancele a criação atual antes de carregar outro mapa.",
|
||||
"Criação de mapa ativa",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
_mapas.CarregarMapaArquivo(caminhoArquivo, tipo);
|
||||
|
||||
|
|
@ -2181,6 +2488,21 @@ namespace OperationControl.ViewModels
|
|||
{
|
||||
try
|
||||
{
|
||||
if (
|
||||
_mapas.CapturaBordaGnssAutomaticaAtiva ||
|
||||
_mapas.CapturaCriacaoAutomaticaAtiva
|
||||
)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"Finalize o levantamento GNSS automático antes de alterar " +
|
||||
"a configuração física da estação.",
|
||||
"Levantamento em andamento",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(Models.Variaveis.GpsService.BaseFix?.FixLiberado ?? false))
|
||||
{
|
||||
bool fixar = false;
|
||||
|
|
@ -2336,13 +2658,13 @@ namespace OperationControl.ViewModels
|
|||
* durante uma captura automática da borda.
|
||||
*/
|
||||
if (
|
||||
_mapas.CapturaBordaGnssAutomaticaAtiva
|
||||
_mapas.CapturaBordaGnssAutomaticaAtiva ||
|
||||
_mapas.CapturaCriacaoAutomaticaAtiva
|
||||
)
|
||||
{
|
||||
MessageBox.Show(
|
||||
"Existe um levantamento GNSS em andamento.\n\n" +
|
||||
"Finalize ou descarte a borda antes de alterar " +
|
||||
"o modo do receptor.",
|
||||
"Existe um levantamento GNSS automático em andamento.\n\n" +
|
||||
"Finalize a captura antes de alterar o modo do receptor.",
|
||||
"Levantamento em andamento",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Warning
|
||||
|
|
|
|||
|
|
@ -510,6 +510,14 @@
|
|||
Command="{Binding CarregarMapaCommand}"
|
||||
IsEnabled="{Binding PodeCarregarMapa}"/>
|
||||
|
||||
<Button
|
||||
Content="+ Criar novo mapa"
|
||||
Style="{StaticResource PanelButtonStyle}"
|
||||
Command="{Binding CriarMapaCommand}"
|
||||
IsEnabled="{Binding PodeCriarMapa}"
|
||||
Margin="0,8,0,0"
|
||||
ToolTip="Cria um mapa por levantamento GNSS, rua por rua, sem exigir Rover."/>
|
||||
|
||||
<Button
|
||||
Content="✎ Editar mapa"
|
||||
Style="{StaticResource PanelButtonStyle}"
|
||||
|
|
|
|||
Loading…
Reference in New Issue