283 lines
10 KiB
C++
283 lines
10 KiB
C++
// Lot STM1 (1.3.0) : lecture en flux des fichiers OSM pour les commandlets de ville (LPBuildCityHouses, LPBuildCityStatic,
|
|
// LPEnrichCityBuildings), sans interface.
|
|
//
|
|
// - Projection du plugin StreetMap (StreetMapFactory.cpp : Sanson-Flamsteed, cosinus de la latitude du noeud), relative a
|
|
// l'origine de l'import (moyenne des noeuds du .osm importe), en cm.
|
|
// - bWays : centre de boite projete et balises de chaque voie d'au moins 3 points (extrait complet : noeuds puis voies) ;
|
|
// la voie d'un batiment de l'UStreetMap se retrouve par ce centre, a 50 cm pres.
|
|
// - bPoints : points d'interet balises (sortie Overpass « out tags center ») : noeuds, et centre des voies et relations.
|
|
// - bLines (1.4.0, STM1-SPEC-17) : points projetes et balises des voies highway=* d'au moins 2 points (lignes ouvertes ou
|
|
// fermees, sans retrait du dernier point) : routes pavees, trottoirs.
|
|
// Le fichier est lu, jamais execute.
|
|
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "FastXml.h"
|
|
|
|
namespace LPCityOsm
|
|
{
|
|
// Meme constante que le plugin (StreetMapFactory.cpp:8-9), en centimetres par degre.
|
|
constexpr double CmPerDegree = 40075036.0 / 360.0 * 100.0;
|
|
|
|
inline FVector2D Project(double Lat, double Lon, double OriginLat, double OriginLon)
|
|
{
|
|
const double CosLat = FMath::Cos(FMath::DegreesToRadians(Lat));
|
|
return FVector2D((Lon * CosLat - OriginLon * CosLat) * CmPerDegree, -(Lat - OriginLat) * CmPerDegree);
|
|
}
|
|
|
|
/** Centre de boite des points, en float comme GI.FindCenterOfPolygon (LPDataCompat). */
|
|
inline FVector2D BoxCenter(const TArray<FVector2D>& Points)
|
|
{
|
|
float MinX = float(Points[0].X), MaxX = MinX, MinY = float(Points[0].Y), MaxY = MinY;
|
|
for (const FVector2D& P : Points)
|
|
{
|
|
MinX = FMath::Min(MinX, float(P.X)); MaxX = FMath::Max(MaxX, float(P.X));
|
|
MinY = FMath::Min(MinY, float(P.Y)); MaxY = FMath::Max(MaxY, float(P.Y));
|
|
}
|
|
return FVector2D((double(MinX) + double(MaxX)) / 2.0, (double(MinY) + double(MaxY)) / 2.0);
|
|
}
|
|
|
|
/** Cle de case de 1 m pour retrouver un centre a 50 cm pres. */
|
|
inline FIntPoint Cell(const FVector2D& P)
|
|
{
|
|
return FIntPoint(FMath::FloorToInt32(P.X / 100.0), FMath::FloorToInt32(P.Y / 100.0));
|
|
}
|
|
|
|
inline bool InsidePolygon(const TArray<FVector2D>& Pts, const FVector2D& P)
|
|
{
|
|
bool bInside = false;
|
|
for (int32 I = 0, J = Pts.Num() - 1; I < Pts.Num(); J = I++)
|
|
{
|
|
if (((Pts[I].Y > P.Y) != (Pts[J].Y > P.Y))
|
|
&& (P.X < (Pts[J].X - Pts[I].X) * (P.Y - Pts[I].Y) / (Pts[J].Y - Pts[I].Y) + Pts[I].X))
|
|
{
|
|
bInside = !bInside;
|
|
}
|
|
}
|
|
return bInside;
|
|
}
|
|
|
|
/** Point d'interet : position projetee, balises, identifiant OSM (type et numero). */
|
|
struct FPoint
|
|
{
|
|
FVector2D Position;
|
|
TMap<FString, FString> Tags;
|
|
FString Id;
|
|
};
|
|
|
|
class FReader : public IFastXmlCallback
|
|
{
|
|
public:
|
|
double OriginLat = 0.0, OriginLon = 0.0;
|
|
bool bWays = true;
|
|
bool bPoints = false;
|
|
bool bLines = false;
|
|
// 1.4.1 (STM1-DIAG-03 E1) : avec bLines, garder aussi les voies fermees leisure, landuse, place ou area:highway.
|
|
bool bAreas = false;
|
|
// 1.7.6 (STM1-DIAG-16 W) : avec bAreas, garder aussi les plans d'eau fermes (natural=water, amenity=fountain, water=*).
|
|
bool bWaterAreas = false;
|
|
TMap<int64, FVector2d> Nodes; // X = longitude, Y = latitude
|
|
TArray<FVector2D> WayCenters;
|
|
TArray<TMap<FString, FString>> WayTags;
|
|
TArray<FPoint> Points;
|
|
TArray<TArray<FVector2D>> Lines;
|
|
TArray<TMap<FString, FString>> LineTags;
|
|
|
|
virtual ~FReader() = default;
|
|
virtual bool ProcessXmlDeclaration(const TCHAR* ElementData, int32 XmlFileLineNumber) override { return true; }
|
|
virtual bool ProcessComment(const TCHAR* Comment) override { return true; }
|
|
|
|
virtual bool ProcessElement(const TCHAR* ElementName, const TCHAR* ElementData, int32 XmlFileLineNumber) override
|
|
{
|
|
if (!FCString::Strcmp(ElementName, TEXT("node"))) { Begin(EState::Node); }
|
|
else if (!FCString::Strcmp(ElementName, TEXT("way"))) { Begin(EState::Way); }
|
|
else if (!FCString::Strcmp(ElementName, TEXT("relation"))) { Begin(EState::Relation); }
|
|
else if (!FCString::Strcmp(ElementName, TEXT("nd"))) { Child = EChild::Nd; }
|
|
else if (!FCString::Strcmp(ElementName, TEXT("tag"))) { Child = EChild::Tag; TagKey.Reset(); }
|
|
else if (!FCString::Strcmp(ElementName, TEXT("center"))) { Child = EChild::Center; }
|
|
else { Child = EChild::Other; }
|
|
return true;
|
|
}
|
|
|
|
virtual bool ProcessAttribute(const TCHAR* AttributeName, const TCHAR* AttributeValue) override
|
|
{
|
|
switch (Child)
|
|
{
|
|
case EChild::Tag:
|
|
if (!FCString::Strcmp(AttributeName, TEXT("k"))) TagKey = AttributeValue;
|
|
else if (!FCString::Strcmp(AttributeName, TEXT("v")) && State != EState::None) Tags.Add(TagKey, AttributeValue);
|
|
break;
|
|
case EChild::Nd:
|
|
if (State == EState::Way && !FCString::Strcmp(AttributeName, TEXT("ref"))) Refs.Add(FCString::Atoi64(AttributeValue));
|
|
break;
|
|
case EChild::Center:
|
|
if (!FCString::Strcmp(AttributeName, TEXT("lat"))) { Lat = FCString::Atod(AttributeValue); bHasPosition = true; }
|
|
else if (!FCString::Strcmp(AttributeName, TEXT("lon"))) Lon = FCString::Atod(AttributeValue);
|
|
break;
|
|
case EChild::None:
|
|
if (!FCString::Strcmp(AttributeName, TEXT("id"))) Id = FCString::Atoi64(AttributeValue);
|
|
else if (State == EState::Node && !FCString::Strcmp(AttributeName, TEXT("lat"))) { Lat = FCString::Atod(AttributeValue); bHasPosition = true; }
|
|
else if (State == EState::Node && !FCString::Strcmp(AttributeName, TEXT("lon"))) Lon = FCString::Atod(AttributeValue);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
virtual bool ProcessClose(const TCHAR* Element) override
|
|
{
|
|
const bool bTop = !FCString::Strcmp(Element, TEXT("node")) || !FCString::Strcmp(Element, TEXT("way")) || !FCString::Strcmp(Element, TEXT("relation"));
|
|
if (!bTop)
|
|
{
|
|
Child = EChild::None;
|
|
return true;
|
|
}
|
|
if (State == EState::Node)
|
|
{
|
|
Nodes.Add(Id, FVector2d(Lon, Lat));
|
|
}
|
|
else if (State == EState::Way && bWays)
|
|
{
|
|
FinishWay();
|
|
}
|
|
if (bPoints && bHasPosition && !Tags.IsEmpty())
|
|
{
|
|
const TCHAR* Kind = State == EState::Node ? TEXT("node") : State == EState::Way ? TEXT("way") : TEXT("relation");
|
|
Points.Add({ Project(Lat, Lon, OriginLat, OriginLon), MoveTemp(Tags), FString::Printf(TEXT("%s/%lld"), Kind, Id) });
|
|
}
|
|
State = EState::None;
|
|
Child = EChild::None;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
enum class EState { None, Node, Way, Relation };
|
|
enum class EChild { None, Nd, Tag, Center, Other };
|
|
EState State = EState::None;
|
|
EChild Child = EChild::None;
|
|
int64 Id = 0;
|
|
double Lat = 0.0, Lon = 0.0;
|
|
bool bHasPosition = false;
|
|
TArray<int64> Refs;
|
|
TMap<FString, FString> Tags;
|
|
FString TagKey;
|
|
|
|
void Begin(EState NewState)
|
|
{
|
|
State = NewState;
|
|
Child = EChild::None;
|
|
Id = 0;
|
|
Lat = Lon = 0.0;
|
|
bHasPosition = false;
|
|
Refs.Reset();
|
|
Tags.Reset();
|
|
}
|
|
|
|
void FinishWay()
|
|
{
|
|
TArray<FVector2D> Pts;
|
|
for (const int64 Ref : Refs)
|
|
{
|
|
if (const FVector2d* LL = Nodes.Find(Ref))
|
|
{
|
|
Pts.Add(Project(LL->Y, LL->X, OriginLat, OriginLon));
|
|
}
|
|
}
|
|
const bool bClosed = Pts.Num() >= 4 && Pts[0].Equals(Pts.Last(), KINDA_SMALL_NUMBER);
|
|
const bool bArea = bAreas && bClosed && (Tags.Contains(TEXT("leisure")) || Tags.Contains(TEXT("landuse")) || Tags.Contains(TEXT("place")) || Tags.Contains(TEXT("area:highway"))
|
|
|| (bWaterAreas && (Tags.FindRef(TEXT("natural")) == TEXT("water") || Tags.FindRef(TEXT("amenity")) == TEXT("fountain") || Tags.Contains(TEXT("water")))));
|
|
if (bLines && Pts.Num() >= 2 && (Tags.Contains(TEXT("highway")) || bArea))
|
|
{
|
|
Lines.Add(Pts);
|
|
LineTags.Add(Tags);
|
|
}
|
|
if (Pts.Num() > 1 && Pts[0].Equals(Pts.Last(), KINDA_SMALL_NUMBER))
|
|
{
|
|
Pts.Pop();
|
|
}
|
|
if (Pts.Num() >= 3)
|
|
{
|
|
WayCenters.Add(BoxCenter(Pts));
|
|
WayTags.Add(Tags);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Lit un fichier ; faux et message en cas d'echec. */
|
|
inline bool Read(FReader& Reader, const FString& Path, FString& OutError)
|
|
{
|
|
FText XmlError;
|
|
int32 XmlErrorLine = 0;
|
|
if (!FFastXml::ParseXmlFile(&Reader, *Path, nullptr, nullptr, false, false, XmlError, XmlErrorLine))
|
|
{
|
|
OutError = FString::Printf(TEXT("lecture de %s (ligne %d : %s)"), *Path, XmlErrorLine, *XmlError.ToString());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Index des voies par centre (case de 1 m) ; Find : indice de la voie dont le centre est a 50 cm pres, sinon -1. */
|
|
struct FWayIndex
|
|
{
|
|
TMultiMap<FIntPoint, int32> Cells;
|
|
const TArray<FVector2D>* Centers = nullptr;
|
|
|
|
void Build(const TArray<FVector2D>& InCenters)
|
|
{
|
|
Centers = &InCenters;
|
|
Cells.Reset();
|
|
for (int32 I = 0; I < InCenters.Num(); ++I) Cells.Add(Cell(InCenters[I]), I);
|
|
}
|
|
|
|
int32 Find(const FVector2D& C) const
|
|
{
|
|
const FIntPoint K = Cell(C);
|
|
for (int32 DX = -1; DX <= 1; ++DX)
|
|
for (int32 DY = -1; DY <= 1; ++DY)
|
|
for (auto It = Cells.CreateConstKeyIterator(K + FIntPoint(DX, DY)); It; ++It)
|
|
if (FVector2D::Distance((*Centers)[It.Value()], C) <= 50.0) return It.Value();
|
|
return INDEX_NONE;
|
|
}
|
|
};
|
|
|
|
/** Batiment retire par les filtres de la 1.2.1 (souterrain, layer < 0, tunnel, toit). L'emprise minimale se teste a part. */
|
|
inline bool IsExcludedBuilding(const TMap<FString, FString>* Tags)
|
|
{
|
|
if (!Tags) return false;
|
|
const FString* Layer = Tags->Find(TEXT("layer"));
|
|
return Tags->FindRef(TEXT("location")) == TEXT("underground")
|
|
|| (Layer && FCString::Atoi(**Layer) < 0)
|
|
|| Tags->FindRef(TEXT("tunnel")) == TEXT("yes")
|
|
|| Tags->FindRef(TEXT("building")) == TEXT("roof");
|
|
}
|
|
|
|
/** 1.3.2 (COMMUN-27b) : musee ou batiment historique (dans un parc, il est masque : ni immeuble ni volume classique). */
|
|
inline bool IsMuseumOrHistoric(const TMap<FString, FString>* Tags)
|
|
{
|
|
return Tags && (Tags->FindRef(TEXT("tourism")) == TEXT("museum") || Tags->FindRef(TEXT("building")) == TEXT("museum")
|
|
|| Tags->Contains(TEXT("historic")));
|
|
}
|
|
|
|
/** Point dans l'un des polygones (boites a l'appui). */
|
|
inline bool InAny(const TArray<TArray<FVector2D>>& Polygons, const TArray<FBox2D>& Boxes, const FVector2D& P)
|
|
{
|
|
for (int32 I = 0; I < Polygons.Num(); ++I)
|
|
{
|
|
if (Boxes[I].IsInside(P) && InsidePolygon(Polygons[I], P)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Etages : building:levels (1 a 15), sinon height / hauteur d'etage ; 0 si inconnus. */
|
|
inline int32 KnownLevels(const TMap<FString, FString>* Tags, double LevelHeightCm)
|
|
{
|
|
if (!Tags) return 0;
|
|
const double Levels = FCString::Atod(*Tags->FindRef(TEXT("building:levels")));
|
|
if (Levels > 0.0) return FMath::Clamp(FMath::RoundToInt32(Levels), 1, 15);
|
|
const double Height = FCString::Atod(*Tags->FindRef(TEXT("height")));
|
|
if (Height > 0.0) return FMath::Clamp(FMath::RoundToInt32(Height * 100.0 / LevelHeightCm), 1, 15);
|
|
return 0;
|
|
}
|
|
}
|