1617 lines
38 KiB
C#
1617 lines
38 KiB
C#
using NetTopologySuite.Geometries;
|
|
using NetTopologySuite.LinearReferencing;
|
|
using OperationControl.Mapas.Editing;
|
|
using OperationControl.Mapas.Models;
|
|
using Point = NetTopologySuite.Geometries.Point;
|
|
|
|
namespace OperationControl.Mapas.Services
|
|
{
|
|
/// <summary>
|
|
/// Núcleo geométrico das operações de recorte.
|
|
///
|
|
/// Não conhece:
|
|
/// - Mapsui;
|
|
/// - WPF;
|
|
/// - GNSS;
|
|
/// - Controller;
|
|
/// - Undo/Redo.
|
|
///
|
|
/// Recebe geometrias WGS84 e altera somente
|
|
/// o MapaDocumento informado.
|
|
/// </summary>
|
|
public sealed class MapaRecorteService
|
|
{
|
|
private const double
|
|
ToleranciaFragmentoMetros =
|
|
0.05;
|
|
|
|
|
|
private const double
|
|
ToleranciaIntersecaoMetros =
|
|
0.02;
|
|
|
|
|
|
private readonly GeometryFactory
|
|
_geometryFactory =
|
|
new(
|
|
new PrecisionModel(),
|
|
4326
|
|
);
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* POLÍGONO
|
|
* =========================================================
|
|
*/
|
|
|
|
public MapaResultadoRecorte
|
|
RecortarPorPoligono(
|
|
MapaDocumento documento,
|
|
IEnumerable<Coordinate>
|
|
pontosPoligono,
|
|
IReadOnlyCollection<string>?
|
|
ruasPermitidas = null)
|
|
{
|
|
ValidarDocumento(
|
|
documento
|
|
);
|
|
|
|
|
|
Polygon poligono =
|
|
CriarPoligono(
|
|
pontosPoligono
|
|
);
|
|
|
|
|
|
HashSet<string>?
|
|
filtroIds =
|
|
CriarFiltroIds(
|
|
ruasPermitidas
|
|
);
|
|
|
|
|
|
var resultado =
|
|
new MapaResultadoRecorte();
|
|
|
|
|
|
var alteracoes =
|
|
new List<AlteracaoRua>();
|
|
|
|
|
|
foreach (
|
|
var rua
|
|
in documento.Ruas.ToArray())
|
|
{
|
|
if (
|
|
rua == null ||
|
|
rua.Geometria == null ||
|
|
rua.Geometria.IsEmpty ||
|
|
rua.Geometria.NumPoints < 2
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
if (
|
|
filtroIds != null &&
|
|
!filtroIds.Contains(
|
|
rua.Id
|
|
)
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
resultado.RuasAnalisadas++;
|
|
|
|
|
|
/*
|
|
* Filtro barato primeiro.
|
|
*/
|
|
if (
|
|
!rua.Geometria
|
|
.EnvelopeInternal
|
|
.Intersects(
|
|
poligono
|
|
.EnvelopeInternal
|
|
)
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
if (
|
|
!rua.Geometria
|
|
.Intersects(
|
|
poligono
|
|
)
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
double comprimentoAntes =
|
|
CalcularComprimentoMetros(
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
Geometry restante =
|
|
rua.Geometria
|
|
.Difference(
|
|
poligono
|
|
);
|
|
|
|
|
|
/*
|
|
* Rua totalmente coberta pelo
|
|
* polígono.
|
|
*/
|
|
if (
|
|
restante == null ||
|
|
restante.IsEmpty
|
|
)
|
|
{
|
|
alteracoes.Add(
|
|
AlteracaoRua.Remover(
|
|
rua,
|
|
comprimentoAntes
|
|
)
|
|
);
|
|
|
|
|
|
resultado
|
|
.RuasRemovidasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
|
|
resultado
|
|
.ComprimentoRemovidoMetros +=
|
|
comprimentoAntes;
|
|
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
List<LineString>
|
|
fragmentos =
|
|
ExtrairLineStrings(
|
|
restante
|
|
);
|
|
|
|
|
|
/*
|
|
* Nenhum segmento linear restou.
|
|
*/
|
|
if (
|
|
fragmentos.Count == 0
|
|
)
|
|
{
|
|
alteracoes.Add(
|
|
AlteracaoRua.Remover(
|
|
rua,
|
|
comprimentoAntes
|
|
)
|
|
);
|
|
|
|
|
|
resultado
|
|
.RuasRemovidasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
|
|
resultado
|
|
.ComprimentoRemovidoMetros +=
|
|
comprimentoAntes;
|
|
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* O polígono removeu uma parte
|
|
* central da rua.
|
|
*
|
|
* Exemplo:
|
|
*
|
|
* ----XXXXXXXX----
|
|
*
|
|
* Isso exigiria criar duas ruas.
|
|
*
|
|
* Nesta V1 deixamos intacta
|
|
* e marcamos como ambígua.
|
|
*/
|
|
if (
|
|
fragmentos.Count != 1
|
|
)
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
LineString novoTrecho =
|
|
NormalizarOrientacao(
|
|
fragmentos[0],
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
double comprimentoDepois =
|
|
CalcularComprimentoMetros(
|
|
novoTrecho
|
|
);
|
|
|
|
|
|
double removido =
|
|
comprimentoAntes -
|
|
comprimentoDepois;
|
|
|
|
|
|
/*
|
|
* Pode acontecer quando o polígono
|
|
* apenas encosta na rua.
|
|
*/
|
|
if (
|
|
removido <=
|
|
ToleranciaFragmentoMetros
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* Restou praticamente zero.
|
|
*/
|
|
if (
|
|
comprimentoDepois <=
|
|
ToleranciaFragmentoMetros
|
|
)
|
|
{
|
|
alteracoes.Add(
|
|
AlteracaoRua.Remover(
|
|
rua,
|
|
comprimentoAntes
|
|
)
|
|
);
|
|
|
|
|
|
resultado
|
|
.RuasRemovidasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
|
|
resultado
|
|
.ComprimentoRemovidoMetros +=
|
|
comprimentoAntes;
|
|
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
alteracoes.Add(
|
|
AlteracaoRua.Substituir(
|
|
rua,
|
|
novoTrecho,
|
|
comprimentoDepois
|
|
)
|
|
);
|
|
|
|
|
|
resultado
|
|
.RuasAlteradasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
|
|
resultado
|
|
.ComprimentoRemovidoMetros +=
|
|
removido;
|
|
}
|
|
catch (
|
|
TopologyException
|
|
)
|
|
{
|
|
/*
|
|
* Nunca fazemos uma decisão destrutiva
|
|
* quando a topologia é duvidosa.
|
|
*/
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
AplicarAlteracoes(
|
|
documento,
|
|
alteracoes
|
|
);
|
|
|
|
|
|
return resultado;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* LINHA DE BORDA
|
|
* =========================================================
|
|
*/
|
|
|
|
public MapaResultadoRecorte
|
|
ApararPorLinha(
|
|
MapaDocumento documento,
|
|
IEnumerable<Coordinate>
|
|
pontosLinha,
|
|
IReadOnlyCollection<string>?
|
|
ruasPermitidas = null)
|
|
{
|
|
ValidarDocumento(
|
|
documento
|
|
);
|
|
|
|
|
|
LineString linhaCorte =
|
|
CriarLinha(
|
|
pontosLinha
|
|
);
|
|
|
|
|
|
HashSet<string>?
|
|
filtroIds =
|
|
CriarFiltroIds(
|
|
ruasPermitidas
|
|
);
|
|
|
|
|
|
var resultado =
|
|
new MapaResultadoRecorte();
|
|
|
|
|
|
var alteracoes =
|
|
new List<AlteracaoRua>();
|
|
|
|
|
|
foreach (
|
|
var rua
|
|
in documento.Ruas.ToArray())
|
|
{
|
|
if (
|
|
rua == null ||
|
|
rua.Geometria == null ||
|
|
rua.Geometria.IsEmpty ||
|
|
rua.Geometria.NumPoints < 2
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
if (
|
|
filtroIds != null &&
|
|
!filtroIds.Contains(
|
|
rua.Id
|
|
)
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
resultado.RuasAnalisadas++;
|
|
|
|
|
|
if (
|
|
!rua.Geometria
|
|
.EnvelopeInternal
|
|
.Intersects(
|
|
linhaCorte
|
|
.EnvelopeInternal
|
|
)
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
Geometry intersecao =
|
|
rua.Geometria
|
|
.Intersection(
|
|
linhaCorte
|
|
);
|
|
|
|
|
|
if (
|
|
intersecao == null ||
|
|
intersecao.IsEmpty
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
bool possuiTrechoSobreposto =
|
|
false;
|
|
|
|
|
|
List<Coordinate>
|
|
pontosIntersecao =
|
|
ExtrairPontosIntersecao(
|
|
intersecao,
|
|
ref possuiTrechoSobreposto
|
|
);
|
|
|
|
|
|
/*
|
|
* Se a linha GNSS andar exatamente
|
|
* sobre uma rua, não existe uma
|
|
* interpretação segura.
|
|
*/
|
|
if (
|
|
possuiTrechoSobreposto
|
|
)
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* Exigimos exatamente UM cruzamento
|
|
* da borda com a rua.
|
|
*/
|
|
if (
|
|
pontosIntersecao.Count != 1
|
|
)
|
|
{
|
|
if (
|
|
pontosIntersecao.Count == 0
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
}
|
|
else
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
}
|
|
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
var indexed =
|
|
new LengthIndexedLine(
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
double indice =
|
|
indexed.Project(
|
|
pontosIntersecao[0]
|
|
);
|
|
|
|
|
|
Geometry trechoA =
|
|
indexed.ExtractLine(
|
|
indexed.StartIndex,
|
|
indice
|
|
);
|
|
|
|
|
|
Geometry trechoB =
|
|
indexed.ExtractLine(
|
|
indice,
|
|
indexed.EndIndex
|
|
);
|
|
|
|
|
|
var fragmentosA =
|
|
ExtrairLineStrings(
|
|
trechoA
|
|
);
|
|
|
|
|
|
var fragmentosB =
|
|
ExtrairLineStrings(
|
|
trechoB
|
|
);
|
|
|
|
|
|
if (
|
|
fragmentosA.Count != 1 ||
|
|
fragmentosB.Count != 1
|
|
)
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
LineString linhaA =
|
|
NormalizarOrientacao(
|
|
fragmentosA[0],
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
LineString linhaB =
|
|
NormalizarOrientacao(
|
|
fragmentosB[0],
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
double comprimentoA =
|
|
CalcularComprimentoMetros(
|
|
linhaA
|
|
);
|
|
|
|
|
|
double comprimentoB =
|
|
CalcularComprimentoMetros(
|
|
linhaB
|
|
);
|
|
|
|
|
|
double menor =
|
|
Math.Min(
|
|
comprimentoA,
|
|
comprimentoB
|
|
);
|
|
|
|
|
|
/*
|
|
* Cruzamento praticamente no extremo.
|
|
* Não existe recorte material.
|
|
*/
|
|
if (
|
|
menor <=
|
|
ToleranciaFragmentoMetros
|
|
)
|
|
{
|
|
resultado
|
|
.RuasIgnoradas++;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
/*
|
|
* Se os dois lados forem praticamente
|
|
* iguais, não existe um "menor lado"
|
|
* confiável.
|
|
*/
|
|
if (
|
|
Math.Abs(
|
|
comprimentoA -
|
|
comprimentoB
|
|
) <=
|
|
ToleranciaFragmentoMetros
|
|
)
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
LineString manter;
|
|
|
|
double comprimentoManter;
|
|
|
|
|
|
if (
|
|
comprimentoA >
|
|
comprimentoB
|
|
)
|
|
{
|
|
manter =
|
|
linhaA;
|
|
|
|
comprimentoManter =
|
|
comprimentoA;
|
|
}
|
|
else
|
|
{
|
|
manter =
|
|
linhaB;
|
|
|
|
comprimentoManter =
|
|
comprimentoB;
|
|
}
|
|
|
|
|
|
manter =
|
|
NormalizarOrientacao(
|
|
manter,
|
|
rua.Geometria
|
|
);
|
|
|
|
|
|
alteracoes.Add(
|
|
AlteracaoRua.Substituir(
|
|
rua,
|
|
manter,
|
|
comprimentoManter
|
|
)
|
|
);
|
|
|
|
|
|
resultado
|
|
.RuasAlteradasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
|
|
|
|
resultado
|
|
.ComprimentoRemovidoMetros +=
|
|
menor;
|
|
}
|
|
catch (
|
|
TopologyException
|
|
)
|
|
{
|
|
resultado
|
|
.RuasAmbiguasIds
|
|
.Add(
|
|
rua.Id
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
AplicarAlteracoes(
|
|
documento,
|
|
alteracoes
|
|
);
|
|
|
|
|
|
return resultado;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* CONSTRUÇÃO DAS GEOMETRIAS DE ENTRADA
|
|
* =========================================================
|
|
*/
|
|
|
|
private Polygon CriarPoligono(
|
|
IEnumerable<Coordinate>
|
|
pontos)
|
|
{
|
|
List<Coordinate>
|
|
normalizados =
|
|
NormalizarPontos(
|
|
pontos,
|
|
3
|
|
);
|
|
|
|
|
|
Coordinate primeiro =
|
|
normalizados[0];
|
|
|
|
|
|
Coordinate ultimo =
|
|
normalizados[
|
|
normalizados.Count -
|
|
1
|
|
];
|
|
|
|
|
|
if (
|
|
DistanciaMetros(
|
|
primeiro,
|
|
ultimo
|
|
) <=
|
|
0.001
|
|
)
|
|
{
|
|
normalizados[
|
|
normalizados.Count -
|
|
1
|
|
] =
|
|
CopiarCoordinate(
|
|
primeiro
|
|
);
|
|
}
|
|
else
|
|
{
|
|
normalizados.Add(
|
|
CopiarCoordinate(
|
|
primeiro
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
if (
|
|
normalizados.Count < 4
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O polígono de recorte " +
|
|
"não possui vértices suficientes."
|
|
);
|
|
}
|
|
|
|
|
|
var ring =
|
|
_geometryFactory
|
|
.CreateLinearRing(
|
|
normalizados
|
|
.ToArray()
|
|
);
|
|
|
|
|
|
var poligono =
|
|
_geometryFactory
|
|
.CreatePolygon(
|
|
ring
|
|
);
|
|
|
|
|
|
if (
|
|
poligono.IsEmpty ||
|
|
!poligono.IsValid ||
|
|
poligono.Area <= 0
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"O polígono de recorte " +
|
|
"é inválido ou se auto-intersecta."
|
|
);
|
|
}
|
|
|
|
|
|
return poligono;
|
|
}
|
|
|
|
|
|
private LineString CriarLinha(
|
|
IEnumerable<Coordinate>
|
|
pontos)
|
|
{
|
|
List<Coordinate>
|
|
normalizados =
|
|
NormalizarPontos(
|
|
pontos,
|
|
2
|
|
);
|
|
|
|
|
|
var linha =
|
|
_geometryFactory
|
|
.CreateLineString(
|
|
normalizados
|
|
.ToArray()
|
|
);
|
|
|
|
|
|
if (
|
|
linha.IsEmpty ||
|
|
linha.NumPoints < 2
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"A linha de borda é inválida."
|
|
);
|
|
}
|
|
|
|
|
|
return linha;
|
|
}
|
|
|
|
|
|
private static List<Coordinate>
|
|
NormalizarPontos(
|
|
IEnumerable<Coordinate>
|
|
pontos,
|
|
int minimo)
|
|
{
|
|
if (pontos == null)
|
|
{
|
|
throw new ArgumentNullException(
|
|
nameof(pontos)
|
|
);
|
|
}
|
|
|
|
|
|
var resultado =
|
|
new List<Coordinate>();
|
|
|
|
|
|
foreach (
|
|
Coordinate? ponto
|
|
in pontos)
|
|
{
|
|
if (ponto == null)
|
|
continue;
|
|
|
|
|
|
ValidarCoordenada(
|
|
ponto
|
|
);
|
|
|
|
|
|
var copia =
|
|
CopiarCoordinate(
|
|
ponto
|
|
);
|
|
|
|
|
|
if (
|
|
resultado.Count > 0 &&
|
|
DistanciaMetros(
|
|
resultado[
|
|
resultado.Count -
|
|
1
|
|
],
|
|
copia
|
|
) <=
|
|
0.001
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
resultado.Add(
|
|
copia
|
|
);
|
|
}
|
|
|
|
|
|
if (
|
|
resultado.Count <
|
|
minimo
|
|
)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"São necessários pelo menos " +
|
|
$"{minimo} pontos distintos."
|
|
);
|
|
}
|
|
|
|
|
|
return resultado;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* EXTRAÇÃO DE RESULTADOS NTS
|
|
* =========================================================
|
|
*/
|
|
|
|
private static List<LineString>
|
|
ExtrairLineStrings(
|
|
Geometry geometria)
|
|
{
|
|
var resultado =
|
|
new List<LineString>();
|
|
|
|
|
|
ExtrairLineStringsRecursivo(
|
|
geometria,
|
|
resultado
|
|
);
|
|
|
|
|
|
return resultado
|
|
.Where(
|
|
x =>
|
|
x != null &&
|
|
!x.IsEmpty &&
|
|
x.NumPoints >= 2 &&
|
|
CalcularComprimentoMetros(
|
|
x
|
|
) >
|
|
0.001
|
|
)
|
|
.ToList();
|
|
}
|
|
|
|
|
|
private static void
|
|
ExtrairLineStringsRecursivo(
|
|
Geometry geometria,
|
|
List<LineString> resultado)
|
|
{
|
|
if (
|
|
geometria == null ||
|
|
geometria.IsEmpty
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
geometria
|
|
is LineString linha
|
|
)
|
|
{
|
|
resultado.Add(
|
|
linha
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
geometria
|
|
is GeometryCollection colecao
|
|
)
|
|
{
|
|
for (
|
|
int i = 0;
|
|
i <
|
|
colecao.NumGeometries;
|
|
i++)
|
|
{
|
|
ExtrairLineStringsRecursivo(
|
|
colecao
|
|
.GetGeometryN(
|
|
i
|
|
),
|
|
resultado
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private static List<Coordinate>
|
|
ExtrairPontosIntersecao(
|
|
Geometry geometria,
|
|
ref bool possuiTrechoLinear)
|
|
{
|
|
var resultado =
|
|
new List<Coordinate>();
|
|
|
|
|
|
ExtrairPontosIntersecaoRecursivo(
|
|
geometria,
|
|
resultado,
|
|
ref possuiTrechoLinear
|
|
);
|
|
|
|
|
|
/*
|
|
* Elimina duplicatas que podem surgir
|
|
* quando o cruzamento ocorre exatamente
|
|
* em um vértice.
|
|
*/
|
|
var unicos =
|
|
new List<Coordinate>();
|
|
|
|
|
|
foreach (
|
|
var ponto
|
|
in resultado)
|
|
{
|
|
bool repetido =
|
|
unicos.Any(
|
|
existente =>
|
|
DistanciaMetros(
|
|
existente,
|
|
ponto
|
|
) <=
|
|
ToleranciaIntersecaoMetros
|
|
);
|
|
|
|
|
|
if (!repetido)
|
|
{
|
|
unicos.Add(
|
|
ponto
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
return unicos;
|
|
}
|
|
|
|
|
|
private static void
|
|
ExtrairPontosIntersecaoRecursivo(
|
|
Geometry geometria,
|
|
List<Coordinate> resultado,
|
|
ref bool possuiTrechoLinear)
|
|
{
|
|
if (
|
|
geometria == null ||
|
|
geometria.IsEmpty
|
|
)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
geometria
|
|
is Point ponto)
|
|
{
|
|
resultado.Add(
|
|
CopiarCoordinate(
|
|
ponto.Coordinate
|
|
)
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
geometria
|
|
is LineString linha)
|
|
{
|
|
if (
|
|
CalcularComprimentoMetros(
|
|
linha
|
|
) >
|
|
ToleranciaIntersecaoMetros
|
|
)
|
|
{
|
|
possuiTrechoLinear =
|
|
true;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
if (
|
|
geometria
|
|
is GeometryCollection colecao)
|
|
{
|
|
for (
|
|
int i = 0;
|
|
i <
|
|
colecao.NumGeometries;
|
|
i++)
|
|
{
|
|
ExtrairPontosIntersecaoRecursivo(
|
|
colecao
|
|
.GetGeometryN(
|
|
i
|
|
),
|
|
resultado,
|
|
ref possuiTrechoLinear
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* COMMIT DAS ALTERAÇÕES
|
|
* =========================================================
|
|
*/
|
|
|
|
private static void AplicarAlteracoes(
|
|
MapaDocumento documento,
|
|
IEnumerable<AlteracaoRua>
|
|
alteracoes)
|
|
{
|
|
bool alterou =
|
|
false;
|
|
|
|
|
|
foreach (
|
|
var alteracao
|
|
in alteracoes)
|
|
{
|
|
if (
|
|
alteracao.RemoverRua
|
|
)
|
|
{
|
|
documento
|
|
.Ruas
|
|
.Remove(
|
|
alteracao.Rua
|
|
);
|
|
|
|
|
|
alterou =
|
|
true;
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (
|
|
alteracao.NovaGeometria
|
|
== null
|
|
)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
alteracao
|
|
.Rua
|
|
.Geometria =
|
|
alteracao
|
|
.NovaGeometria;
|
|
|
|
|
|
alteracao
|
|
.Rua
|
|
.ComprimentoMetros =
|
|
alteracao
|
|
.NovoComprimentoMetros;
|
|
|
|
|
|
alterou =
|
|
true;
|
|
}
|
|
|
|
|
|
if (alterou)
|
|
{
|
|
documento.Modificado =
|
|
true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* ORIENTAÇÃO
|
|
* =========================================================
|
|
*/
|
|
|
|
private static LineString
|
|
NormalizarOrientacao(
|
|
LineString fragmento,
|
|
LineString original)
|
|
{
|
|
Coordinate inicioOriginal =
|
|
original
|
|
.Coordinates
|
|
.First();
|
|
|
|
|
|
Coordinate inicioFragmento =
|
|
fragmento
|
|
.Coordinates
|
|
.First();
|
|
|
|
|
|
Coordinate fimFragmento =
|
|
fragmento
|
|
.Coordinates
|
|
.Last();
|
|
|
|
|
|
double distanciaInicio =
|
|
DistanciaMetros(
|
|
inicioOriginal,
|
|
inicioFragmento
|
|
);
|
|
|
|
|
|
double distanciaFim =
|
|
DistanciaMetros(
|
|
inicioOriginal,
|
|
fimFragmento
|
|
);
|
|
|
|
|
|
Coordinate[] coords =
|
|
fragmento
|
|
.Coordinates
|
|
.Select(
|
|
CopiarCoordinate
|
|
)
|
|
.ToArray();
|
|
|
|
|
|
if (
|
|
distanciaFim <
|
|
distanciaInicio
|
|
)
|
|
{
|
|
Array.Reverse(
|
|
coords
|
|
);
|
|
}
|
|
|
|
|
|
var resultado =
|
|
original
|
|
.Factory
|
|
.CreateLineString(
|
|
coords
|
|
);
|
|
|
|
|
|
resultado.SRID =
|
|
original.SRID;
|
|
|
|
|
|
resultado.UserData =
|
|
original.UserData;
|
|
|
|
|
|
return resultado;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* DISTÂNCIA WGS84
|
|
* =========================================================
|
|
*/
|
|
|
|
public static double
|
|
CalcularComprimentoMetros(
|
|
LineString linha)
|
|
{
|
|
if (
|
|
linha == null ||
|
|
linha.IsEmpty ||
|
|
linha.NumPoints < 2
|
|
)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
|
|
Coordinate[] coords =
|
|
linha.Coordinates;
|
|
|
|
|
|
double total =
|
|
0;
|
|
|
|
|
|
for (
|
|
int i = 1;
|
|
i < coords.Length;
|
|
i++)
|
|
{
|
|
total +=
|
|
DistanciaMetros(
|
|
coords[i - 1],
|
|
coords[i]
|
|
);
|
|
}
|
|
|
|
|
|
return total;
|
|
}
|
|
|
|
|
|
private static double DistanciaMetros(
|
|
Coordinate a,
|
|
Coordinate b)
|
|
{
|
|
const double raioTerra =
|
|
6371008.8;
|
|
|
|
|
|
double lat1 =
|
|
GrausParaRadianos(
|
|
a.Y
|
|
);
|
|
|
|
|
|
double lat2 =
|
|
GrausParaRadianos(
|
|
b.Y
|
|
);
|
|
|
|
|
|
double deltaLat =
|
|
GrausParaRadianos(
|
|
b.Y -
|
|
a.Y
|
|
);
|
|
|
|
|
|
double deltaLon =
|
|
GrausParaRadianos(
|
|
b.X -
|
|
a.X
|
|
);
|
|
|
|
|
|
double senLat =
|
|
Math.Sin(
|
|
deltaLat /
|
|
2.0
|
|
);
|
|
|
|
|
|
double senLon =
|
|
Math.Sin(
|
|
deltaLon /
|
|
2.0
|
|
);
|
|
|
|
|
|
double h =
|
|
senLat *
|
|
senLat +
|
|
Math.Cos(
|
|
lat1
|
|
) *
|
|
Math.Cos(
|
|
lat2
|
|
) *
|
|
senLon *
|
|
senLon;
|
|
|
|
|
|
h =
|
|
Math.Clamp(
|
|
h,
|
|
0,
|
|
1
|
|
);
|
|
|
|
|
|
double c =
|
|
2.0 *
|
|
Math.Asin(
|
|
Math.Sqrt(
|
|
h
|
|
)
|
|
);
|
|
|
|
|
|
return
|
|
raioTerra *
|
|
c;
|
|
}
|
|
|
|
|
|
private static double
|
|
GrausParaRadianos(
|
|
double graus)
|
|
{
|
|
return
|
|
graus *
|
|
Math.PI /
|
|
180.0;
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* VALIDAÇÃO
|
|
* =========================================================
|
|
*/
|
|
|
|
private static void ValidarDocumento(
|
|
MapaDocumento documento)
|
|
{
|
|
if (documento == null)
|
|
{
|
|
throw new ArgumentNullException(
|
|
nameof(documento)
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
private static void ValidarCoordenada(
|
|
Coordinate ponto)
|
|
{
|
|
if (
|
|
!double.IsFinite(
|
|
ponto.X
|
|
) ||
|
|
!double.IsFinite(
|
|
ponto.Y
|
|
) ||
|
|
ponto.X < -180 ||
|
|
ponto.X > 180 ||
|
|
ponto.Y < -90 ||
|
|
ponto.Y > 90
|
|
)
|
|
{
|
|
throw new ArgumentOutOfRangeException(
|
|
nameof(ponto),
|
|
"Coordenada WGS84 inválida."
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
private static Coordinate
|
|
CopiarCoordinate(
|
|
Coordinate origem)
|
|
{
|
|
return new Coordinate(
|
|
origem.X,
|
|
origem.Y
|
|
);
|
|
}
|
|
|
|
|
|
private static HashSet<string>?
|
|
CriarFiltroIds(
|
|
IReadOnlyCollection<string>?
|
|
ruasPermitidas)
|
|
{
|
|
if (
|
|
ruasPermitidas == null
|
|
)
|
|
{
|
|
/*
|
|
* null = todas as ruas
|
|
*/
|
|
return null;
|
|
}
|
|
|
|
|
|
return new HashSet<string>(
|
|
ruasPermitidas
|
|
.Where(
|
|
x =>
|
|
!string.IsNullOrWhiteSpace(
|
|
x
|
|
)
|
|
),
|
|
StringComparer.OrdinalIgnoreCase
|
|
);
|
|
}
|
|
|
|
|
|
/*
|
|
* =========================================================
|
|
* MODELO INTERNO DE ALTERAÇÃO
|
|
* =========================================================
|
|
*/
|
|
|
|
private sealed class AlteracaoRua
|
|
{
|
|
public MapaRua Rua
|
|
{
|
|
get;
|
|
}
|
|
|
|
|
|
public bool RemoverRua
|
|
{
|
|
get;
|
|
}
|
|
|
|
|
|
public LineString?
|
|
NovaGeometria
|
|
{
|
|
get;
|
|
}
|
|
|
|
|
|
public double
|
|
NovoComprimentoMetros
|
|
{
|
|
get;
|
|
}
|
|
|
|
|
|
private AlteracaoRua(
|
|
MapaRua rua,
|
|
bool removerRua,
|
|
LineString?
|
|
novaGeometria,
|
|
double
|
|
novoComprimentoMetros)
|
|
{
|
|
Rua =
|
|
rua;
|
|
|
|
RemoverRua =
|
|
removerRua;
|
|
|
|
NovaGeometria =
|
|
novaGeometria;
|
|
|
|
NovoComprimentoMetros =
|
|
novoComprimentoMetros;
|
|
}
|
|
|
|
|
|
public static AlteracaoRua
|
|
Remover(
|
|
MapaRua rua,
|
|
double comprimentoAnterior)
|
|
{
|
|
return new AlteracaoRua(
|
|
rua,
|
|
true,
|
|
null,
|
|
0
|
|
);
|
|
}
|
|
|
|
|
|
public static AlteracaoRua
|
|
Substituir(
|
|
MapaRua rua,
|
|
LineString geometria,
|
|
double comprimento)
|
|
{
|
|
return new AlteracaoRua(
|
|
rua,
|
|
false,
|
|
geometria,
|
|
comprimento
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} |