1594 lines
57 KiB
C#
1594 lines
57 KiB
C#
using AgroBase.Models;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
using NetTopologySuite.Features;
|
|
using NetTopologySuite.Geometries;
|
|
using NetTopologySuite.IO.Esri;
|
|
using ProjNet.CoordinateSystems;
|
|
using ProjNet.CoordinateSystems.Transformations;
|
|
|
|
namespace AgroBase.Services
|
|
{
|
|
public static class MapasVariaveisModel
|
|
{
|
|
public static string NomeArquivoHtml = "mapa_interativo_webview2.html";
|
|
public static string NomePastaLeaflet = "folium";
|
|
public static string NomeLeafletJs = "leaflet.js";
|
|
public static string NomeLeafletCss = "leaflet.css";
|
|
public static int IDMarcadorBase = 1;
|
|
public static int IDMarcadorRover = 2;
|
|
}
|
|
|
|
public class MapasService
|
|
{
|
|
private readonly string _caminhoSaida;
|
|
|
|
public string NomeArquivos { get; private set; }
|
|
public string CaminhoArquivo { get; private set; }
|
|
public string CaminhoHtml { get; private set; }
|
|
|
|
public MapaFeatureCollectionModel DadosMapa
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool Carregado
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public MapasService()
|
|
{
|
|
_caminhoSaida = Path.Combine(Variaveis.CaminhoSistema, PythonService.CaminhoGeral, PythonService.CaminhoLeitura);
|
|
Directory.CreateDirectory(_caminhoSaida);
|
|
CaminhoHtml = Path.Combine(_caminhoSaida, MapasVariaveisModel.NomeArquivoHtml);
|
|
}
|
|
|
|
public bool SelecionarECarregarMapa(IWin32Window owner = null)
|
|
{
|
|
using (OpenFileDialog dialog = new OpenFileDialog())
|
|
{
|
|
dialog.DefaultExt = ".json";
|
|
dialog.Filter =
|
|
"Arquivos de mapa (*.json;*.geojson;*.shp)|*.json;*.geojson;*.shp|" +
|
|
"GeoJSON (*.json;*.geojson)|*.json;*.geojson|" +
|
|
"Shapefile (*.shp)|*.shp";
|
|
dialog.FilterIndex = 1;
|
|
dialog.Title = "Selecionar arquivo de mapa";
|
|
|
|
DialogResult resultado =
|
|
owner != null
|
|
? dialog.ShowDialog(owner)
|
|
: dialog.ShowDialog();
|
|
|
|
if (resultado != DialogResult.OK)
|
|
return false;
|
|
|
|
return CarregarMapa(dialog.FileName);
|
|
}
|
|
}
|
|
|
|
public bool CarregarMapa(string caminhoArquivo)
|
|
{
|
|
Carregado = false;
|
|
DadosMapa = null;
|
|
|
|
if (string.IsNullOrWhiteSpace(caminhoArquivo))
|
|
return false;
|
|
|
|
if (!File.Exists(caminhoArquivo))
|
|
{
|
|
throw new FileNotFoundException("O arquivo de mapa não foi encontrado.", caminhoArquivo);
|
|
}
|
|
|
|
string extensao = Path.GetExtension(caminhoArquivo).ToLowerInvariant();
|
|
|
|
MapaFeatureCollectionModel dados;
|
|
|
|
switch (extensao)
|
|
{
|
|
case ".json":
|
|
case ".geojson":
|
|
dados = CarregarGeoJson(caminhoArquivo);
|
|
break;
|
|
|
|
case ".shp":
|
|
dados = CarregarShapefile(caminhoArquivo);
|
|
break;
|
|
|
|
default:
|
|
throw new InvalidOperationException(
|
|
$"Formato de mapa não suportado: {extensao}."
|
|
);
|
|
}
|
|
|
|
ValidarMapa(dados);
|
|
NormalizarMapa(dados);
|
|
|
|
CaminhoArquivo = caminhoArquivo;
|
|
NomeArquivos = Path.GetFileNameWithoutExtension(caminhoArquivo);
|
|
|
|
DadosMapa = dados;
|
|
|
|
CriarArquivoHtml();
|
|
|
|
Carregado = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
private static MapaFeatureCollectionModel CarregarGeoJson(string caminhoArquivo)
|
|
{
|
|
string json = File.ReadAllText(caminhoArquivo, Encoding.UTF8);
|
|
|
|
var dados = JsonConvert.DeserializeObject<MapaFeatureCollectionModel>(json);
|
|
|
|
if (dados == null)
|
|
{
|
|
throw new InvalidOperationException("Não foi possível interpretar o JSON do mapa.");
|
|
}
|
|
|
|
return dados;
|
|
}
|
|
|
|
private static MapaFeatureCollectionModel CarregarShapefile(string caminhoShp)
|
|
{
|
|
ValidarArquivosShapefile(caminhoShp);
|
|
|
|
var featuresShp = Shapefile
|
|
.ReadAllFeatures(caminhoShp)
|
|
.ToList();
|
|
|
|
if (featuresShp.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O Shapefile não possui features válidas."
|
|
);
|
|
}
|
|
|
|
Func<double, double, double[]> transformar =
|
|
CriarTransformacaoParaWgs84(
|
|
caminhoShp,
|
|
featuresShp
|
|
);
|
|
|
|
var dados = new MapaFeatureCollectionModel
|
|
{
|
|
type = "FeatureCollection",
|
|
features = new List<MapaFeatureModel>()
|
|
};
|
|
|
|
int indiceRua = 1;
|
|
|
|
foreach (var featureShp in featuresShp)
|
|
{
|
|
if (featureShp?.Geometry == null ||
|
|
featureShp.Geometry.IsEmpty)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
IEnumerable<LineString> linhas =
|
|
ExtrairLineStrings(featureShp.Geometry);
|
|
|
|
foreach (LineString linha in linhas)
|
|
{
|
|
if (linha == null ||
|
|
linha.IsEmpty ||
|
|
linha.NumPoints < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var coordenadas = new List<List<double>>();
|
|
|
|
foreach (Coordinate coordenada in linha.Coordinates)
|
|
{
|
|
double[] lonLat =
|
|
transformar(
|
|
coordenada.X,
|
|
coordenada.Y
|
|
);
|
|
|
|
double longitude = lonLat[0];
|
|
double latitude = lonLat[1];
|
|
|
|
if (
|
|
double.IsNaN(longitude) ||
|
|
double.IsInfinity(longitude) ||
|
|
double.IsNaN(latitude) ||
|
|
double.IsInfinity(latitude) ||
|
|
longitude < -180 ||
|
|
longitude > 180 ||
|
|
latitude < -90 ||
|
|
latitude > 90
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O Shapefile produziu uma coordenada " +
|
|
$"inválida após transformação: " +
|
|
$"{longitude}, {latitude}."
|
|
);
|
|
}
|
|
|
|
coordenadas.Add(
|
|
new List<double>
|
|
{
|
|
longitude,
|
|
latitude
|
|
}
|
|
);
|
|
}
|
|
|
|
if (coordenadas.Count < 2)
|
|
continue;
|
|
|
|
string id =
|
|
indiceRua.ToString(
|
|
CultureInfo.InvariantCulture
|
|
);
|
|
|
|
var properties =
|
|
new MapaFeaturePropertiesModel
|
|
{
|
|
Id = id,
|
|
|
|
Name =
|
|
ObterAtributoTexto(
|
|
featureShp.Attributes,
|
|
"Name",
|
|
"NAME",
|
|
"Nome",
|
|
"NOME"
|
|
) ?? $"Rua {id}",
|
|
|
|
Dist1 =
|
|
ObterAtributoDouble(
|
|
featureShp.Attributes,
|
|
"Dist1",
|
|
"DIST1"
|
|
),
|
|
|
|
Dist2 =
|
|
ObterAtributoDouble(
|
|
featureShp.Attributes,
|
|
"Dist2",
|
|
"DIST2"
|
|
),
|
|
|
|
Length =
|
|
ObterAtributoDouble(
|
|
featureShp.Attributes,
|
|
"Length",
|
|
"LENGTH",
|
|
"Comprimento",
|
|
"COMPRIMENT"
|
|
)
|
|
};
|
|
|
|
dados.features.Add(
|
|
new MapaFeatureModel
|
|
{
|
|
type = "Feature",
|
|
|
|
geometry =
|
|
new MapaFeatureGeometryModel
|
|
{
|
|
id = id,
|
|
type = "LineString",
|
|
coordinates = coordenadas
|
|
},
|
|
|
|
properties = properties
|
|
}
|
|
);
|
|
|
|
indiceRua++;
|
|
}
|
|
}
|
|
|
|
if (dados.features.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O Shapefile não possui linhas válidas."
|
|
);
|
|
}
|
|
|
|
return dados;
|
|
}
|
|
private static IEnumerable<LineString> ExtrairLineStrings(Geometry geometria)
|
|
{
|
|
if (geometria is LineString linha)
|
|
{
|
|
yield return linha;
|
|
yield break;
|
|
}
|
|
|
|
if (geometria is MultiLineString variasLinhas)
|
|
{
|
|
for (int i = 0; i < variasLinhas.NumGeometries; i++)
|
|
{
|
|
if (variasLinhas.GetGeometryN(i) is LineString parte)
|
|
{
|
|
yield return parte;
|
|
}
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
if (geometria is GeometryCollection colecao)
|
|
{
|
|
for (int i = 0; i < colecao.NumGeometries; i++)
|
|
{
|
|
foreach (LineString parte in ExtrairLineStrings(colecao.GetGeometryN(i)))
|
|
{
|
|
yield return parte;
|
|
}
|
|
}
|
|
|
|
yield break;
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
"O Shapefile contém uma geometria não suportada: " +
|
|
geometria.GeometryType +
|
|
". São aceitas somente linhas."
|
|
);
|
|
}
|
|
private static Func<double, double, double[]> CriarTransformacaoParaWgs84(string caminhoShp, IEnumerable<IFeature> features)
|
|
{
|
|
string caminhoPrj =
|
|
Path.ChangeExtension(caminhoShp, ".prj");
|
|
|
|
bool coordenadasParecemGeograficas =
|
|
CoordenadasParecemLongitudeLatitude(features);
|
|
|
|
/*
|
|
* ==========================================================
|
|
* 1. SHAPEFILE SEM .PRJ
|
|
* ==========================================================
|
|
*
|
|
* Se as próprias coordenadas já estão dentro dos limites
|
|
* longitude/latitude, aceitamos como coordenadas geográficas.
|
|
*/
|
|
if (!File.Exists(caminhoPrj))
|
|
{
|
|
if (coordenadasParecemGeograficas)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MapasService] Shapefile sem arquivo .prj. " +
|
|
"As coordenadas já parecem estar em " +
|
|
"longitude/latitude. Nenhuma reprojeção será aplicada."
|
|
);
|
|
|
|
return (x, y) => new[] { x, y };
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
"O Shapefile não possui arquivo .prj e suas " +
|
|
"coordenadas não parecem estar em longitude/latitude. " +
|
|
"Não é possível determinar com segurança o sistema " +
|
|
"de coordenadas do mapa."
|
|
);
|
|
}
|
|
|
|
string wkt =
|
|
File.ReadAllText(
|
|
caminhoPrj,
|
|
Encoding.UTF8
|
|
);
|
|
|
|
/*
|
|
* ==========================================================
|
|
* 2. .PRJ VAZIO
|
|
* ==========================================================
|
|
*/
|
|
if (string.IsNullOrWhiteSpace(wkt))
|
|
{
|
|
if (coordenadasParecemGeograficas)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MapasService] Arquivo .prj vazio. " +
|
|
"As coordenadas parecem longitude/latitude; " +
|
|
"o mapa será carregado sem reprojeção."
|
|
);
|
|
|
|
return (x, y) => new[] { x, y };
|
|
}
|
|
|
|
throw new InvalidOperationException(
|
|
"O arquivo .prj está vazio e as coordenadas " +
|
|
"do Shapefile não parecem longitude/latitude."
|
|
);
|
|
}
|
|
|
|
/*
|
|
* ==========================================================
|
|
* 3. TENTA INTERPRETAR O .PRJ NORMALMENTE
|
|
* ==========================================================
|
|
*/
|
|
try
|
|
{
|
|
var fabricaSistemas =
|
|
new CoordinateSystemFactory();
|
|
|
|
var sistemaOrigem =
|
|
fabricaSistemas.CreateFromWkt(wkt);
|
|
|
|
if (sistemaOrigem == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O sistema de coordenadas do .prj não pôde ser criado."
|
|
);
|
|
}
|
|
|
|
var sistemaDestino =
|
|
GeographicCoordinateSystem.WGS84;
|
|
|
|
var fabricaTransformacao =
|
|
new CoordinateTransformationFactory();
|
|
|
|
var transformacao =
|
|
fabricaTransformacao
|
|
.CreateFromCoordinateSystems(
|
|
sistemaOrigem,
|
|
sistemaDestino
|
|
);
|
|
|
|
if (transformacao?.MathTransform == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Não foi possível criar a transformação para WGS84."
|
|
);
|
|
}
|
|
|
|
/*
|
|
* O PRJ é válido.
|
|
* Usa a transformação normalmente.
|
|
*/
|
|
return (x, y) =>
|
|
{
|
|
double[] resultado =
|
|
transformacao.MathTransform.Transform(
|
|
new[] { x, y }
|
|
);
|
|
|
|
return resultado;
|
|
};
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
/*
|
|
* ======================================================
|
|
* 4. .PRJ EXISTE, MAS ESTÁ QUEBRADO
|
|
* ======================================================
|
|
*
|
|
* Se as coordenadas já são compatíveis com longitude /
|
|
* latitude, podemos ignorar o PRJ defeituoso.
|
|
*
|
|
* Esse é exatamente o caso do mapa atual.
|
|
*/
|
|
if (coordenadasParecemGeograficas)
|
|
{
|
|
Variaveis.MostrarLog(
|
|
"[MapasService] AVISO: o arquivo .prj existe, " +
|
|
"mas não pôde ser interpretado. " +
|
|
"Como as coordenadas do Shapefile já parecem estar " +
|
|
"em longitude/latitude, o mapa será carregado " +
|
|
"sem reprojeção. Erro do PRJ: " +
|
|
ex.Message
|
|
);
|
|
|
|
return (x, y) => new[] { x, y };
|
|
}
|
|
|
|
/*
|
|
* Temos um PRJ quebrado e coordenadas aparentemente
|
|
* projetadas (UTM, por exemplo).
|
|
*
|
|
* Aqui NÃO devemos adivinhar.
|
|
*/
|
|
throw new InvalidOperationException(
|
|
"O arquivo .prj do Shapefile não pôde ser interpretado " +
|
|
"e as coordenadas do mapa não parecem estar em " +
|
|
"longitude/latitude. Não é possível determinar com " +
|
|
"segurança a projeção utilizada.",
|
|
ex
|
|
);
|
|
}
|
|
}
|
|
private static bool CoordenadasParecemLongitudeLatitude(IEnumerable<IFeature> features)
|
|
{
|
|
if (features == null)
|
|
return false;
|
|
|
|
int quantidade = 0;
|
|
|
|
foreach (IFeature feature in features)
|
|
{
|
|
if (feature?.Geometry == null ||
|
|
feature.Geometry.IsEmpty)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (Coordinate coordenada in feature.Geometry.Coordinates)
|
|
{
|
|
if (coordenada == null)
|
|
continue;
|
|
|
|
double x = coordenada.X;
|
|
double y = coordenada.Y;
|
|
|
|
if (
|
|
double.IsNaN(x) ||
|
|
double.IsInfinity(x) ||
|
|
double.IsNaN(y) ||
|
|
double.IsInfinity(y)
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Limites físicos de longitude e latitude.
|
|
*/
|
|
if (
|
|
x < -180.0 ||
|
|
x > 180.0 ||
|
|
y < -90.0 ||
|
|
y > 90.0
|
|
)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
quantidade++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Só consideramos válido se realmente encontramos
|
|
* coordenadas para analisar.
|
|
*/
|
|
return quantidade > 0;
|
|
}
|
|
private static void ValidarArquivosShapefile(string caminhoShp)
|
|
{
|
|
string caminhoDbf = Path.ChangeExtension(caminhoShp, ".dbf");
|
|
|
|
if (!File.Exists(caminhoDbf))
|
|
{
|
|
throw new FileNotFoundException(
|
|
"O arquivo .dbf correspondente ao Shapefile " +
|
|
"não foi encontrado.",
|
|
caminhoDbf
|
|
);
|
|
}
|
|
|
|
/*
|
|
* O .prj é tratado no método de transformação:
|
|
* sem ele, somente coordenadas que já estejam dentro
|
|
* dos limites de longitude/latitude serão aceitas.
|
|
*/
|
|
}
|
|
private static object ObterAtributo(IAttributesTable atributos, params string[] nomes)
|
|
{
|
|
if (atributos == null)
|
|
return null;
|
|
|
|
string[] nomesExistentes = atributos.GetNames();
|
|
|
|
foreach (string nomeDesejado in nomes)
|
|
{
|
|
string nomeEncontrado =
|
|
nomesExistentes.FirstOrDefault(
|
|
x => string.Equals(
|
|
x,
|
|
nomeDesejado,
|
|
StringComparison.OrdinalIgnoreCase
|
|
)
|
|
);
|
|
|
|
if (!string.IsNullOrWhiteSpace(nomeEncontrado))
|
|
{
|
|
object valor = atributos[nomeEncontrado];
|
|
|
|
if (valor != null && valor != DBNull.Value)
|
|
{
|
|
return valor;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ObterAtributoTexto(IAttributesTable atributos, params string[] nomes)
|
|
{
|
|
return ObterAtributo(atributos, nomes)?.ToString();
|
|
}
|
|
|
|
private static double ObterAtributoDouble(IAttributesTable atributos, params string[] nomes)
|
|
{
|
|
object valor = ObterAtributo(atributos, nomes);
|
|
|
|
if (valor == null)
|
|
return 0;
|
|
|
|
if (valor is IConvertible)
|
|
{
|
|
try
|
|
{
|
|
return Convert.ToDouble(valor, CultureInfo.InvariantCulture);
|
|
}
|
|
catch
|
|
{
|
|
// Tenta interpretar como texto abaixo.
|
|
}
|
|
}
|
|
|
|
string texto = valor.ToString();
|
|
|
|
if (double.TryParse(texto, NumberStyles.Any, CultureInfo.InvariantCulture, out double resultado))
|
|
{
|
|
return resultado;
|
|
}
|
|
|
|
if (double.TryParse(texto, NumberStyles.Any, CultureInfo.GetCultureInfo("pt-BR"), out resultado))
|
|
{
|
|
return resultado;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
public void DefinirMapa(MapaFeatureCollectionModel dados, string nomeMapa = "Mapa")
|
|
{
|
|
Carregado = false;
|
|
DadosMapa = null;
|
|
|
|
ValidarMapa(dados);
|
|
NormalizarMapa(dados);
|
|
|
|
DadosMapa = dados;
|
|
NomeArquivos = nomeMapa;
|
|
CaminhoArquivo = null;
|
|
|
|
CriarArquivoHtml();
|
|
|
|
Carregado = true;
|
|
}
|
|
|
|
public string ObterGeoJson()
|
|
{
|
|
if (DadosMapa == null)
|
|
return null;
|
|
|
|
return JsonConvert.SerializeObject(DadosMapa, Formatting.None);
|
|
}
|
|
|
|
public void Limpar()
|
|
{
|
|
NomeArquivos = null;
|
|
CaminhoArquivo = null;
|
|
DadosMapa = null;
|
|
Carregado = false;
|
|
}
|
|
|
|
private static void ValidarMapa(MapaFeatureCollectionModel dados)
|
|
{
|
|
if (dados == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O conteúdo do mapa é inválido."
|
|
);
|
|
}
|
|
|
|
if (dados.features == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O mapa não possui uma lista de features."
|
|
);
|
|
}
|
|
|
|
if (dados.features.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O mapa não possui ruas."
|
|
);
|
|
}
|
|
|
|
for (int i = 0; i < dados.features.Count; i++)
|
|
{
|
|
MapaFeatureModel feature = dados.features[i];
|
|
|
|
if (feature == null || feature.geometry == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"A rua " +
|
|
(i + 1) +
|
|
" possui geometria inválida."
|
|
);
|
|
}
|
|
|
|
if (!string.Equals(feature.geometry.type, "LineString", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"A rua " +
|
|
(i + 1) +
|
|
" não é do tipo LineString."
|
|
);
|
|
}
|
|
|
|
if (feature.geometry.coordinates == null || feature.geometry.coordinates.Count < 2)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"A rua " +
|
|
(i + 1) +
|
|
" não possui pontos suficientes."
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void NormalizarMapa(MapaFeatureCollectionModel dados)
|
|
{
|
|
dados.type = string.IsNullOrWhiteSpace(dados.type)
|
|
? "FeatureCollection"
|
|
: dados.type;
|
|
|
|
var idsUtilizados = new HashSet<string>(StringComparer.Ordinal);
|
|
|
|
for (int i = 0; i < dados.features.Count; i++)
|
|
{
|
|
MapaFeatureModel feature = dados.features[i];
|
|
|
|
feature.type = string.IsNullOrWhiteSpace(feature.type)
|
|
? "Feature"
|
|
: feature.type;
|
|
|
|
if (feature.properties == null)
|
|
feature.properties = new MapaFeaturePropertiesModel();
|
|
|
|
string id = feature.properties.Id?.Trim();
|
|
|
|
// Mapa sem ID explícito:
|
|
// gera um ID numérico sequencial.
|
|
if (string.IsNullOrWhiteSpace(id))
|
|
{
|
|
id = (i + 1).ToString(
|
|
CultureInfo.InvariantCulture
|
|
);
|
|
}
|
|
|
|
if (!idsUtilizados.Add(id))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"O mapa possui ID de rua duplicado: '{id}'."
|
|
);
|
|
}
|
|
|
|
// Uma única identidade em todo o sistema.
|
|
feature.properties.Id = id;
|
|
feature.geometry.id = id;
|
|
}
|
|
}
|
|
|
|
public void CriarArquivoHtml()
|
|
{
|
|
string html = ObterHtmlMapa();
|
|
File.WriteAllText(CaminhoHtml, html, new UTF8Encoding(false));
|
|
}
|
|
|
|
private static string ObterHtmlMapa()
|
|
{
|
|
return @"<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset='utf-8' />
|
|
|
|
<meta
|
|
name='viewport'
|
|
content='width=device-width,
|
|
initial-scale=1.0,
|
|
maximum-scale=1.0,
|
|
user-scalable=no' />
|
|
|
|
<link
|
|
rel='stylesheet'
|
|
href='folium/leaflet.css' />
|
|
|
|
<script src='folium/leaflet.js'></script>
|
|
|
|
<style>
|
|
html,
|
|
body,
|
|
#mapa {
|
|
width: 100%;
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
background: #121d16;
|
|
}
|
|
|
|
.leaflet-container {
|
|
background: #121d16;
|
|
font-family: Segoe UI, Arial, sans-serif;
|
|
}
|
|
|
|
.leaflet-control-zoom a {
|
|
background: #1b2a20;
|
|
color: #d7e5da;
|
|
border-color: #314538;
|
|
}
|
|
|
|
.leaflet-control-attribution {
|
|
display: none;
|
|
}
|
|
|
|
.rua-tooltip {
|
|
background: #17251c;
|
|
color: #d7e5da;
|
|
border: 1px solid #42664e;
|
|
box-shadow: none;
|
|
font-size: 11px;
|
|
font-weight: bold;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id='mapa'></div>
|
|
|
|
<script>
|
|
const COR_NORMAL = '#5b9bd5';
|
|
const COR_SELECIONADA = '#26c65a';
|
|
|
|
let mapa = null;
|
|
let camadaRuas = null;
|
|
let ruasSelecionadas = [];
|
|
let marcadorRover = null;
|
|
let marcadorBase = null;
|
|
let ultimaPosicaoRover = null;
|
|
|
|
let camadaTrajetoriaPlanejada = null;
|
|
let camadaRastroRover = null;
|
|
let camadaRastroBase = null;
|
|
let camadaTrajetoGps = null;
|
|
let pontosTrajetoGps = [];
|
|
let pontosRastroRover = [];
|
|
let pontosRastroBase = [];
|
|
let ultimaPosicaoBase = null;
|
|
|
|
let selecaoRuasHabilitada = false;
|
|
|
|
function inicializarMapa() {
|
|
if (mapa !== null) {
|
|
return;
|
|
}
|
|
|
|
mapa = L.map('mapa', {
|
|
zoomControl: true,
|
|
attributionControl: false,
|
|
preferCanvas: true
|
|
});
|
|
|
|
mapa.setView([0, 0], 18);
|
|
}
|
|
|
|
function definirSelecaoRuasHabilitada(habilitada) {
|
|
selecaoRuasHabilitada = habilitada === true;
|
|
|
|
if (!camadaRuas) {
|
|
return;
|
|
}
|
|
|
|
camadaRuas.eachLayer(function(layer) {
|
|
if (selecaoRuasHabilitada) {
|
|
layer.options.interactive = true;
|
|
|
|
if (layer._path) {
|
|
layer._path.style.cursor = 'pointer';
|
|
}
|
|
}
|
|
else {
|
|
layer.options.interactive = false;
|
|
|
|
if (layer._path) {
|
|
layer._path.style.cursor = 'default';
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function obterIdRua(feature, indice) {
|
|
if (
|
|
feature &&
|
|
feature.properties &&
|
|
feature.properties.Id !== undefined &&
|
|
feature.properties.Id !== null
|
|
) {
|
|
return String(feature.properties.Id);
|
|
}
|
|
|
|
if (
|
|
feature &&
|
|
feature.geometry &&
|
|
feature.geometry.id !== undefined &&
|
|
feature.geometry.id !== null
|
|
) {
|
|
return String(feature.geometry.id);
|
|
}
|
|
|
|
if (
|
|
feature &&
|
|
feature.id !== undefined &&
|
|
feature.id !== null
|
|
) {
|
|
return String(feature.id);
|
|
}
|
|
|
|
return String(indice + 1);
|
|
}
|
|
|
|
function ruaSelecionada(id) {
|
|
return ruasSelecionadas.indexOf(String(id)) >= 0;
|
|
}
|
|
|
|
function obterEstiloRua(id) {
|
|
const selecionada = ruaSelecionada(id);
|
|
|
|
return {
|
|
color: selecionada
|
|
? COR_SELECIONADA
|
|
: COR_NORMAL,
|
|
|
|
weight: selecionada
|
|
? 8
|
|
: 6,
|
|
|
|
opacity: selecaoRuasHabilitada
|
|
? 1
|
|
: 0.8,
|
|
|
|
lineCap: 'round',
|
|
lineJoin: 'round'
|
|
};
|
|
}
|
|
|
|
function atualizarEstilos() {
|
|
if (!camadaRuas) {
|
|
return;
|
|
}
|
|
|
|
camadaRuas.eachLayer(function(layer) {
|
|
const id = layer.__ruaId;
|
|
|
|
if (id === undefined) {
|
|
return;
|
|
}
|
|
|
|
layer.setStyle(
|
|
obterEstiloRua(id)
|
|
);
|
|
});
|
|
}
|
|
|
|
function alternarRua(id) {
|
|
id = String(id);
|
|
|
|
const indice =
|
|
ruasSelecionadas.indexOf(id);
|
|
|
|
if (indice >= 0) {
|
|
ruasSelecionadas.splice(
|
|
indice,
|
|
1
|
|
);
|
|
}
|
|
else {
|
|
ruasSelecionadas.push(id);
|
|
}
|
|
|
|
atualizarEstilos();
|
|
enviarSelecaoParaCSharp();
|
|
}
|
|
|
|
function enviarSelecaoParaCSharp() {
|
|
if (
|
|
window.chrome &&
|
|
window.chrome.webview
|
|
) {
|
|
window.chrome.webview.postMessage({
|
|
tipo: 'selecaoRuas',
|
|
ruas: ruasSelecionadas
|
|
});
|
|
}
|
|
}
|
|
|
|
function carregarMapa(
|
|
dadosMapa,
|
|
selecaoInicial,
|
|
permitirSelecao
|
|
) {
|
|
inicializarMapa();
|
|
|
|
selecaoRuasHabilitada = permitirSelecao === true;
|
|
|
|
if (typeof dadosMapa === 'string') {
|
|
dadosMapa =
|
|
JSON.parse(dadosMapa);
|
|
}
|
|
|
|
ruasSelecionadas =
|
|
Array.isArray(selecaoInicial)
|
|
? selecaoInicial.map(String)
|
|
: [];
|
|
|
|
if (camadaRuas !== null) {
|
|
mapa.removeLayer(camadaRuas);
|
|
camadaRuas = null;
|
|
}
|
|
|
|
let indiceFeature = 0;
|
|
|
|
camadaRuas = L.geoJSON(
|
|
dadosMapa,
|
|
{
|
|
style: function(feature) {
|
|
const id =
|
|
obterIdRua(
|
|
feature,
|
|
indiceFeature
|
|
);
|
|
|
|
indiceFeature++;
|
|
|
|
return obterEstiloRua(id);
|
|
},
|
|
|
|
onEachFeature:
|
|
function(feature, layer) {
|
|
const indice =
|
|
camadaRuas
|
|
? camadaRuas
|
|
.getLayers()
|
|
.length
|
|
: 0;
|
|
|
|
const id =
|
|
obterIdRua(
|
|
feature,
|
|
indice
|
|
);
|
|
|
|
layer.__ruaId =
|
|
String(id);
|
|
|
|
let titulo =
|
|
'Rua ' +
|
|
(
|
|
feature.properties &&
|
|
feature.properties.Id
|
|
? feature.properties.Id
|
|
: id
|
|
);
|
|
|
|
if (
|
|
feature.properties &&
|
|
feature.properties.Length
|
|
) {
|
|
titulo +=
|
|
' · ' +
|
|
Number(
|
|
feature.properties.Length
|
|
).toFixed(1) +
|
|
' m';
|
|
}
|
|
|
|
layer.bindTooltip(
|
|
titulo,
|
|
{
|
|
sticky: true,
|
|
className:
|
|
'rua-tooltip'
|
|
}
|
|
);
|
|
|
|
layer.on(
|
|
'click',
|
|
function() {
|
|
if (!selecaoRuasHabilitada) {
|
|
return;
|
|
}
|
|
|
|
alternarRua(id);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
);
|
|
|
|
camadaRuas.addTo(mapa);
|
|
|
|
atualizarEstilos();
|
|
|
|
const limites =
|
|
camadaRuas.getBounds();
|
|
|
|
if (limites.isValid()) {
|
|
mapa.fitBounds(
|
|
limites,
|
|
{
|
|
padding: [18, 18],
|
|
maxZoom: 21
|
|
}
|
|
);
|
|
}
|
|
|
|
setTimeout(function() {
|
|
mapa.invalidateSize();
|
|
}, 50);
|
|
|
|
enviarMensagemMapaCarregado();
|
|
}
|
|
|
|
function definirSelecaoRuas(ids) {
|
|
ruasSelecionadas =
|
|
Array.isArray(ids)
|
|
? ids.map(String)
|
|
: [];
|
|
|
|
atualizarEstilos();
|
|
}
|
|
|
|
function definirTrajetoriaPlanejada(pontos) {
|
|
inicializarMapa();
|
|
|
|
if (camadaTrajetoriaPlanejada !== null) {
|
|
mapa.removeLayer(camadaTrajetoriaPlanejada);
|
|
camadaTrajetoriaPlanejada = null;
|
|
}
|
|
|
|
if (!Array.isArray(pontos) || pontos.length < 2) {
|
|
return;
|
|
}
|
|
|
|
const coordenadas = pontos
|
|
.map(function(ponto) {
|
|
return [
|
|
Number(ponto.latitude),
|
|
Number(ponto.longitude)
|
|
];
|
|
})
|
|
.filter(function(ponto) {
|
|
return (
|
|
Number.isFinite(ponto[0]) &&
|
|
Number.isFinite(ponto[1])
|
|
);
|
|
});
|
|
|
|
if (coordenadas.length < 2) {
|
|
return;
|
|
}
|
|
|
|
camadaTrajetoriaPlanejada = L.polyline(
|
|
coordenadas,
|
|
{
|
|
color: '#f5c542',
|
|
weight: 5,
|
|
opacity: 0.95,
|
|
dashArray: '10, 7',
|
|
lineCap: 'round',
|
|
lineJoin: 'round',
|
|
interactive: false
|
|
}
|
|
).addTo(mapa);
|
|
}
|
|
|
|
function obterSelecaoRuas() {
|
|
return ruasSelecionadas.slice();
|
|
}
|
|
|
|
function limparMapa() {
|
|
ruasSelecionadas = [];
|
|
|
|
if (
|
|
mapa !== null &&
|
|
camadaRuas !== null
|
|
) {
|
|
mapa.removeLayer(camadaRuas);
|
|
}
|
|
|
|
camadaRuas = null;
|
|
}
|
|
|
|
function redimensionarMapa() {
|
|
if (mapa !== null) {
|
|
mapa.invalidateSize();
|
|
}
|
|
}
|
|
|
|
function enviarMensagemMapaCarregado() {
|
|
if (
|
|
window.chrome &&
|
|
window.chrome.webview
|
|
) {
|
|
window.chrome.webview.postMessage({
|
|
tipo: 'mapaCarregado'
|
|
});
|
|
}
|
|
}
|
|
|
|
function criarIconeRover(orientacao) {
|
|
return L.divIcon({
|
|
className: '',
|
|
iconSize: [30, 30],
|
|
iconAnchor: [15, 15],
|
|
|
|
html:
|
|
'<div style=""' +
|
|
'width:30px;' +
|
|
'height:30px;' +
|
|
'display:flex;' +
|
|
'align-items:center;' +
|
|
'justify-content:center;' +
|
|
'transform:rotate(' + orientacao + 'deg);' +
|
|
'transform-origin:center;' +
|
|
'font-size:25px;' +
|
|
'color:#26c65a;' +
|
|
'text-shadow:0 0 3px #000;' +
|
|
'"">' +
|
|
'▲' +
|
|
'</div>'
|
|
});
|
|
}
|
|
|
|
function criarIconeBase() {
|
|
return L.divIcon({
|
|
className: '',
|
|
iconSize: [24, 24],
|
|
iconAnchor: [12, 12],
|
|
|
|
html:
|
|
'<div style=""' +
|
|
'width:18px;' +
|
|
'height:18px;' +
|
|
'border-radius:50%;' +
|
|
'background:#e34b4b;' +
|
|
'border:3px solid white;' +
|
|
'box-shadow:0 0 4px #000;' +
|
|
'"">' +
|
|
'</div>'
|
|
});
|
|
}
|
|
|
|
function inicializarModoGps() {
|
|
inicializarMapa();
|
|
|
|
pontosTrajetoGps = [];
|
|
ultimaPosicaoRover = null;
|
|
|
|
if (camadaRuas !== null) {
|
|
mapa.removeLayer(camadaRuas);
|
|
camadaRuas = null;
|
|
}
|
|
|
|
if (camadaTrajetoGps !== null) {
|
|
mapa.removeLayer(camadaTrajetoGps);
|
|
}
|
|
|
|
camadaTrajetoGps = L.polyline(
|
|
[],
|
|
{
|
|
color: '#ef5350',
|
|
weight: 4,
|
|
opacity: 0.9,
|
|
lineCap: 'round',
|
|
lineJoin: 'round',
|
|
interactive: false
|
|
}
|
|
).addTo(mapa);
|
|
|
|
if (marcadorRover !== null) {
|
|
mapa.removeLayer(marcadorRover);
|
|
marcadorRover = null;
|
|
}
|
|
|
|
if (marcadorBase !== null) {
|
|
mapa.removeLayer(marcadorBase);
|
|
marcadorBase = null;
|
|
}
|
|
}
|
|
|
|
function coordenadasDiferentes(a, b) {
|
|
if (!a || !b) {
|
|
return true;
|
|
}
|
|
|
|
const tolerancia = 0.000001;
|
|
|
|
return (
|
|
Math.abs(a[0] - b[0]) > tolerancia ||
|
|
Math.abs(a[1] - b[1]) > tolerancia
|
|
);
|
|
}
|
|
|
|
function garantirCamadaRastroRover() {
|
|
if (camadaRastroRover !== null) {
|
|
return;
|
|
}
|
|
|
|
camadaRastroRover = L.polyline(
|
|
[],
|
|
{
|
|
color: '#ef5350',
|
|
weight: 4,
|
|
opacity: 0.9,
|
|
interactive: false
|
|
}
|
|
).addTo(mapa);
|
|
}
|
|
|
|
function garantirCamadaRastroBase() {
|
|
if (camadaRastroBase !== null) {
|
|
return;
|
|
}
|
|
|
|
camadaRastroBase = L.polyline(
|
|
[],
|
|
{
|
|
color: '#b36cff',
|
|
weight: 3,
|
|
opacity: 0.85,
|
|
interactive: false
|
|
}
|
|
).addTo(mapa);
|
|
}
|
|
|
|
function atualizarPosicaoGps(
|
|
id,
|
|
latitude,
|
|
longitude,
|
|
orientacao,
|
|
foco
|
|
) {
|
|
inicializarMapa();
|
|
|
|
const posicao = [
|
|
Number(latitude),
|
|
Number(longitude)
|
|
];
|
|
|
|
if (id === " + MapasVariaveisModel.IDMarcadorBase + @") {
|
|
|
|
garantirCamadaRastroBase();
|
|
if (coordenadasDiferentes(ultimaPosicaoBase, posicao)) {
|
|
pontosRastroBase.push([posicao[0], posicao[1]]);
|
|
camadaRastroBase.setLatLngs(pontosRastroBase);
|
|
ultimaPosicaoBase = [posicao[0], posicao[1]];
|
|
}
|
|
|
|
if (marcadorBase === null) {
|
|
marcadorBase = L.marker(
|
|
posicao,
|
|
{
|
|
icon: criarIconeBase()
|
|
}
|
|
).addTo(mapa);
|
|
}
|
|
else {
|
|
marcadorBase.setLatLng(posicao);
|
|
}
|
|
}
|
|
else {
|
|
garantirCamadaRastroRover();
|
|
if (coordenadasDiferentes(ultimaPosicaoRover, posicao)) {
|
|
pontosRastroRover.push([posicao[0], posicao[1]]);
|
|
camadaRastroRover.setLatLngs(pontosRastroRover);
|
|
ultimaPosicaoRover =[posicao[0], posicao[1]];
|
|
}
|
|
|
|
if (marcadorRover === null) {
|
|
marcadorRover = L.marker(
|
|
posicao,
|
|
{
|
|
icon:
|
|
criarIconeRover(
|
|
Number(orientacao)
|
|
)
|
|
}
|
|
).addTo(mapa);
|
|
}
|
|
else {
|
|
marcadorRover.setLatLng(posicao);
|
|
|
|
marcadorRover.setIcon(
|
|
criarIconeRover(
|
|
Number(orientacao)
|
|
)
|
|
);
|
|
}
|
|
|
|
if (
|
|
coordenadasDiferentes(
|
|
ultimaPosicaoRover,
|
|
posicao
|
|
)
|
|
) {
|
|
pontosTrajetoGps.push(posicao);
|
|
|
|
camadaTrajetoGps.setLatLngs(
|
|
pontosTrajetoGps
|
|
);
|
|
|
|
ultimaPosicaoRover =
|
|
posicao;
|
|
}
|
|
}
|
|
|
|
if (foco) {
|
|
mapa.setView(
|
|
posicao,
|
|
mapa.getZoom() < 18
|
|
? 18
|
|
: mapa.getZoom()
|
|
);
|
|
}
|
|
}
|
|
|
|
function limparRastroRover() {
|
|
pontosRastroRover = [];
|
|
ultimaPosicaoRover = null;
|
|
|
|
if (camadaRastroRover !== null) {
|
|
camadaRastroRover.setLatLngs([]);
|
|
}
|
|
}
|
|
|
|
function limparRastroBase() {
|
|
pontosRastroBase = [];
|
|
ultimaPosicaoBase = null;
|
|
|
|
if (camadaRastroBase !== null) {
|
|
camadaRastroBase.setLatLngs([]);
|
|
}
|
|
}
|
|
|
|
function limparTodosOsRastros() {
|
|
limparRastroRover();
|
|
limparRastroBase();
|
|
}
|
|
|
|
function limparTrajetoriaPlanejada() {
|
|
if (
|
|
mapa !== null &&
|
|
camadaTrajetoriaPlanejada !== null
|
|
) {
|
|
mapa.removeLayer(
|
|
camadaTrajetoriaPlanejada
|
|
);
|
|
}
|
|
|
|
camadaTrajetoriaPlanejada = null;
|
|
}
|
|
|
|
inicializarMapa();
|
|
</script>
|
|
</body>
|
|
</html>";
|
|
}
|
|
|
|
|
|
|
|
public static void CriarArquivoMapa(List<List<GPSModel>> trajetoria, int id = 1517, string name = "Projeto", double dist1 = 0.0, double dist2 = 0.0)
|
|
{
|
|
MapaFeatureCollectionModel featureCollection = ConverterPontosParaMapa(trajetoria, id, name, dist1, dist2);
|
|
|
|
string json = JsonConvert.SerializeObject(featureCollection, Formatting.None);
|
|
|
|
if (!Directory.Exists(Variaveis.CaminhoMapasConvertidos))
|
|
{
|
|
Directory.CreateDirectory(Variaveis.CaminhoMapasConvertidos);
|
|
}
|
|
|
|
string caminho = Path.Combine(Variaveis.CaminhoMapasConvertidos, name + ".json");
|
|
|
|
File.WriteAllText(caminho, json, new UTF8Encoding(false));
|
|
}
|
|
|
|
public static MapaFeatureCollectionModel ConverterPontosParaMapa(List<List<GPSModel>> trajetoria, int id = 1517, string name = "Projeto", double dist1 = 0.0, double dist2 = 0.0)
|
|
{
|
|
MapaFeatureCollectionModel featureCollection =
|
|
new MapaFeatureCollectionModel
|
|
{
|
|
type = "FeatureCollection",
|
|
features = new List<MapaFeatureModel>()
|
|
};
|
|
|
|
if (trajetoria == null)
|
|
return featureCollection;
|
|
|
|
for (int i = 0; i < trajetoria.Count; i++)
|
|
{
|
|
List<GPSModel> rua = trajetoria[i];
|
|
|
|
if (rua == null || rua.Count == 0)
|
|
continue;
|
|
|
|
double comprimento = GPSUtils.DistanciaDoTrecho(rua);
|
|
|
|
MapaFeatureModel feature =
|
|
new MapaFeatureModel
|
|
{
|
|
type = "Feature",
|
|
|
|
properties =
|
|
new MapaFeaturePropertiesModel
|
|
{
|
|
Id = (i + 1).ToString(),
|
|
Name = name,
|
|
Length = comprimento,
|
|
Dist1 = dist1,
|
|
Dist2 = dist2
|
|
},
|
|
|
|
geometry =
|
|
new MapaFeatureGeometryModel
|
|
{
|
|
id = i.ToString(),
|
|
type = "LineString",
|
|
coordinates =
|
|
new List<List<double>>()
|
|
}
|
|
};
|
|
|
|
foreach (GPSModel ponto in rua)
|
|
{
|
|
if (ponto == null)
|
|
continue;
|
|
|
|
feature.geometry.coordinates.Add(
|
|
new List<double>
|
|
{
|
|
ponto.Longitude,
|
|
ponto.Latitude
|
|
}
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Uma LineString precisa de pelo menos dois pontos.
|
|
*/
|
|
if (feature.geometry.coordinates.Count >= 2)
|
|
{
|
|
featureCollection.features.Add(feature);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Normaliza os IDs internos depois de ignorar ruas inválidas.
|
|
*/
|
|
for (int i = 0; i < featureCollection.features.Count; i++)
|
|
{
|
|
featureCollection.features[i].geometry.id = i.ToString();
|
|
}
|
|
|
|
return featureCollection;
|
|
}
|
|
|
|
}
|
|
} |