360 lines
12 KiB
C++
360 lines
12 KiB
C++
|
|
// Lot STM1 (STM1-SPEC-09, B1). Voir LPClipCityOSMCommandlet.h.
|
||
|
|
|
||
|
|
#include "LPClipCityOSMCommandlet.h"
|
||
|
|
|
||
|
|
#include "FastXml.h"
|
||
|
|
#include "HAL/FileManager.h"
|
||
|
|
#include "HAL/PlatformMemory.h"
|
||
|
|
#include "HAL/PlatformTime.h"
|
||
|
|
#include "Misc/FileHelper.h"
|
||
|
|
|
||
|
|
DEFINE_LOG_CATEGORY_STATIC(LogLPClipCityOSM, Log, All);
|
||
|
|
|
||
|
|
namespace LPClipCityOSM
|
||
|
|
{
|
||
|
|
struct FWay
|
||
|
|
{
|
||
|
|
int64 Id = 0;
|
||
|
|
TArray<int64> Refs;
|
||
|
|
/** Cle et valeur telles que lues (deja echappees en XML : recopiees sans transformation). */
|
||
|
|
TArray<TPair<FString, FString>> Tags;
|
||
|
|
bool bHighway = false;
|
||
|
|
bool bBuilding = false;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Lecteur en flux : meme automate que FOSMFile du plugin (noeuds, voies, nd, tag). */
|
||
|
|
class FReader : public IFastXmlCallback
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
TMap<int64, FVector2d> Nodes; // X = lon, Y = lat
|
||
|
|
TArray<FWay> Ways;
|
||
|
|
|
||
|
|
virtual ~FReader() = default;
|
||
|
|
|
||
|
|
virtual bool ProcessXmlDeclaration(const TCHAR*, int32) override { return true; }
|
||
|
|
virtual bool ProcessComment(const TCHAR*) override { return true; }
|
||
|
|
|
||
|
|
virtual bool ProcessElement(const TCHAR* Name, const TCHAR*, int32) override
|
||
|
|
{
|
||
|
|
if (State == EState::Root)
|
||
|
|
{
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("node"))) { State = EState::Node; NodeId = 0; NodePos = FVector2d::ZeroVector; }
|
||
|
|
else if (!FCString::Stricmp(Name, TEXT("way"))) { State = EState::Way; Way = FWay(); }
|
||
|
|
}
|
||
|
|
else if (State == EState::Node)
|
||
|
|
{
|
||
|
|
State = EState::NodeChild;
|
||
|
|
}
|
||
|
|
else if (State == EState::Way)
|
||
|
|
{
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("nd"))) State = EState::WayNd;
|
||
|
|
else if (!FCString::Stricmp(Name, TEXT("tag"))) { State = EState::WayTag; TagKey.Reset(); }
|
||
|
|
else State = EState::WayOther;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool ProcessAttribute(const TCHAR* Name, const TCHAR* Value) override
|
||
|
|
{
|
||
|
|
switch (State)
|
||
|
|
{
|
||
|
|
case EState::Node:
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("id"))) NodeId = FCString::Atoi64(Value);
|
||
|
|
else if (!FCString::Stricmp(Name, TEXT("lat"))) NodePos.Y = FCString::Atod(Value);
|
||
|
|
else if (!FCString::Stricmp(Name, TEXT("lon"))) NodePos.X = FCString::Atod(Value);
|
||
|
|
break;
|
||
|
|
case EState::Way:
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("id"))) Way.Id = FCString::Atoi64(Value);
|
||
|
|
break;
|
||
|
|
case EState::WayNd:
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("ref"))) Way.Refs.Add(FCString::Atoi64(Value));
|
||
|
|
break;
|
||
|
|
case EState::WayTag:
|
||
|
|
if (!FCString::Stricmp(Name, TEXT("k"))) TagKey = Value;
|
||
|
|
else if (!FCString::Stricmp(Name, TEXT("v")))
|
||
|
|
{
|
||
|
|
Way.bHighway |= TagKey == TEXT("highway");
|
||
|
|
Way.bBuilding |= TagKey == TEXT("building");
|
||
|
|
Way.Tags.Emplace(TagKey, FString(Value));
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
default:
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual bool ProcessClose(const TCHAR*) override
|
||
|
|
{
|
||
|
|
switch (State)
|
||
|
|
{
|
||
|
|
case EState::Node: Nodes.Add(NodeId, NodePos); State = EState::Root; break;
|
||
|
|
case EState::NodeChild: State = EState::Node; break;
|
||
|
|
case EState::Way: Ways.Add(MoveTemp(Way)); State = EState::Root; break;
|
||
|
|
case EState::WayNd:
|
||
|
|
case EState::WayTag:
|
||
|
|
case EState::WayOther: State = EState::Way; break;
|
||
|
|
default: break;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private:
|
||
|
|
enum class EState { Root, Node, NodeChild, Way, WayNd, WayTag, WayOther };
|
||
|
|
EState State = EState::Root;
|
||
|
|
int64 NodeId = 0;
|
||
|
|
FVector2d NodePos = FVector2d::ZeroVector;
|
||
|
|
FWay Way;
|
||
|
|
FString TagKey;
|
||
|
|
};
|
||
|
|
|
||
|
|
/** Liang-Barsky : portion [T0, T1] du segment A-B dans la boite ; false si le segment est dehors. */
|
||
|
|
bool ClipSegment(const FVector2d& A, const FVector2d& B, const FVector2d& Min, const FVector2d& Max, double& T0, double& T1)
|
||
|
|
{
|
||
|
|
T0 = 0.0;
|
||
|
|
T1 = 1.0;
|
||
|
|
const FVector2d D = B - A;
|
||
|
|
const double P[4] = { -D.X, D.X, -D.Y, D.Y };
|
||
|
|
const double Q[4] = { A.X - Min.X, Max.X - A.X, A.Y - Min.Y, Max.Y - A.Y };
|
||
|
|
for (int32 I = 0; I < 4; ++I)
|
||
|
|
{
|
||
|
|
if (FMath::IsNearlyZero(P[I]))
|
||
|
|
{
|
||
|
|
if (Q[I] < 0.0) return false;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const double R = Q[I] / P[I];
|
||
|
|
if (P[I] < 0.0) T0 = FMath::Max(T0, R);
|
||
|
|
else T1 = FMath::Min(T1, R);
|
||
|
|
if (T0 > T1) return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
FString Coord(double Value)
|
||
|
|
{
|
||
|
|
return FString::Printf(TEXT("%.7f"), Value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
ULPClipCityOSMCommandlet::ULPClipCityOSMCommandlet()
|
||
|
|
{
|
||
|
|
IsClient = false;
|
||
|
|
IsServer = false;
|
||
|
|
IsEditor = true;
|
||
|
|
LogToConsole = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
int32 ULPClipCityOSMCommandlet::Main(const FString& Params)
|
||
|
|
{
|
||
|
|
using namespace LPClipCityOSM;
|
||
|
|
|
||
|
|
TArray<FString> Tokens, Switches;
|
||
|
|
TMap<FString, FString> ParamsMap;
|
||
|
|
ParseCommandLine(*Params, Tokens, Switches, ParamsMap);
|
||
|
|
const FString OsmPath = ParamsMap.FindRef(TEXT("Osm"));
|
||
|
|
const FString OutPath = ParamsMap.FindRef(TEXT("Out"));
|
||
|
|
const FString BoxText = ParamsMap.FindRef(TEXT("Box"));
|
||
|
|
const FString ReportPath = ParamsMap.FindRef(TEXT("Report"));
|
||
|
|
|
||
|
|
TArray<FString> Report;
|
||
|
|
auto Line = [&Report](const FString& Text)
|
||
|
|
{
|
||
|
|
UE_LOG(LogLPClipCityOSM, Display, TEXT("%s"), *Text);
|
||
|
|
Report.Add(Text);
|
||
|
|
};
|
||
|
|
auto Finish = [&](int32 Code)
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("CODE %d"), Code));
|
||
|
|
if (!ReportPath.IsEmpty())
|
||
|
|
{
|
||
|
|
FFileHelper::SaveStringArrayToFile(Report, *ReportPath, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM);
|
||
|
|
}
|
||
|
|
return Code;
|
||
|
|
};
|
||
|
|
|
||
|
|
TArray<FString> BoxParts;
|
||
|
|
BoxText.ParseIntoArray(BoxParts, TEXT(","));
|
||
|
|
if (OsmPath.IsEmpty() || OutPath.IsEmpty() || BoxParts.Num() != 4)
|
||
|
|
{
|
||
|
|
Line(TEXT("usage : -Osm=<entree.osm> -Out=<sortie.osm> -Box=<sud>,<ouest>,<nord>,<est> [-Report=<f.md>]"));
|
||
|
|
return Finish(2);
|
||
|
|
}
|
||
|
|
const FVector2d Min(FCString::Atod(*BoxParts[1]), FCString::Atod(*BoxParts[0])); // lon, lat
|
||
|
|
const FVector2d Max(FCString::Atod(*BoxParts[3]), FCString::Atod(*BoxParts[2]));
|
||
|
|
Line(FString::Printf(TEXT("# LPClipCityOSM %s -> %s"), *OsmPath, *OutPath));
|
||
|
|
Line(FString::Printf(TEXT("boite : sud %.5f ouest %.5f nord %.5f est %.5f"), Min.Y, Min.X, Max.Y, Max.X));
|
||
|
|
if (Min.X >= Max.X || Min.Y >= Max.Y)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : boite invalide"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
if (IFileManager::Get().FileExists(*OutPath))
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : le fichier de sortie existe deja ; rien ecrit"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 1. Lecture.
|
||
|
|
const double Start = FPlatformTime::Seconds();
|
||
|
|
FReader Reader;
|
||
|
|
FText Error;
|
||
|
|
int32 ErrorLine = 0;
|
||
|
|
if (!FFastXml::ParseXmlFile(&Reader, *OsmPath, nullptr, nullptr, false, false, Error, ErrorLine))
|
||
|
|
{
|
||
|
|
Line(FString::Printf(TEXT("ECHEC : lecture XML (%s, ligne %d)"), *Error.ToString(), ErrorLine));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("lu : %d noeuds, %d voies en %.1f s"), Reader.Nodes.Num(), Reader.Ways.Num(), FPlatformTime::Seconds() - Start));
|
||
|
|
|
||
|
|
// 2. Decoupe.
|
||
|
|
auto Inside = [&](const FVector2d& P) { return P.X >= Min.X && P.X <= Max.X && P.Y >= Min.Y && P.Y <= Max.Y; };
|
||
|
|
TMap<int64, FVector2d> OutNodes;
|
||
|
|
TArray<FWay> OutWays;
|
||
|
|
int64 NextNodeId = -1, NextWayId = -1;
|
||
|
|
int32 BoundaryNodes = 0;
|
||
|
|
int32 RoadsIn = 0, RoadsKept = 0, RoadsCut = 0, RoadPieces = 0, AreasIn = 0, AreasKept = 0, MissingRefs = 0;
|
||
|
|
for (const FWay& Way : Reader.Ways)
|
||
|
|
{
|
||
|
|
TArray<FVector2d> Points;
|
||
|
|
TArray<int64> Refs;
|
||
|
|
for (const int64 Ref : Way.Refs)
|
||
|
|
{
|
||
|
|
if (const FVector2d* P = Reader.Nodes.Find(Ref)) { Points.Add(*P); Refs.Add(Ref); }
|
||
|
|
else ++MissingRefs;
|
||
|
|
}
|
||
|
|
if (Points.Num() < 2) continue;
|
||
|
|
|
||
|
|
if (Way.bHighway && !Way.bBuilding)
|
||
|
|
{
|
||
|
|
++RoadsIn;
|
||
|
|
TArray<TArray<int64>> Pieces;
|
||
|
|
TArray<int64> Current;
|
||
|
|
bool bCut = false;
|
||
|
|
for (int32 I = 0; I + 1 < Points.Num(); ++I)
|
||
|
|
{
|
||
|
|
double T0, T1;
|
||
|
|
if (!ClipSegment(Points[I], Points[I + 1], Min, Max, T0, T1))
|
||
|
|
{
|
||
|
|
if (Current.Num() >= 2) Pieces.Add(Current);
|
||
|
|
Current.Reset();
|
||
|
|
bCut = true;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
auto PointId = [&](double T) -> int64
|
||
|
|
{
|
||
|
|
if (T <= 0.0) return Refs[I];
|
||
|
|
if (T >= 1.0) return Refs[I + 1];
|
||
|
|
const int64 Id = NextNodeId--;
|
||
|
|
++BoundaryNodes;
|
||
|
|
OutNodes.Add(Id, Points[I] + T * (Points[I + 1] - Points[I]));
|
||
|
|
bCut = true;
|
||
|
|
return Id;
|
||
|
|
};
|
||
|
|
const int64 A = PointId(T0);
|
||
|
|
if (Current.IsEmpty() || Current.Last() != A)
|
||
|
|
{
|
||
|
|
if (Current.Num() >= 2) Pieces.Add(Current);
|
||
|
|
Current.Reset();
|
||
|
|
Current.Add(A);
|
||
|
|
}
|
||
|
|
Current.Add(PointId(T1));
|
||
|
|
if (T1 < 1.0)
|
||
|
|
{
|
||
|
|
Pieces.Add(Current);
|
||
|
|
Current.Reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (Current.Num() >= 2) Pieces.Add(Current);
|
||
|
|
if (Pieces.IsEmpty()) continue;
|
||
|
|
++RoadsKept;
|
||
|
|
RoadsCut += bCut ? 1 : 0;
|
||
|
|
for (int32 Index = 0; Index < Pieces.Num(); ++Index)
|
||
|
|
{
|
||
|
|
FWay& Piece = OutWays.AddDefaulted_GetRef();
|
||
|
|
Piece.Id = Index == 0 ? Way.Id : NextWayId--;
|
||
|
|
Piece.Refs = Pieces[Index];
|
||
|
|
Piece.Tags = Way.Tags;
|
||
|
|
for (const int64 Ref : Piece.Refs)
|
||
|
|
{
|
||
|
|
if (Ref > 0) OutNodes.Add(Ref, Reader.Nodes[Ref]);
|
||
|
|
}
|
||
|
|
++RoadPieces;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
++AreasIn;
|
||
|
|
FVector2d BoxMin = Points[0], BoxMax = Points[0];
|
||
|
|
for (const FVector2d& P : Points)
|
||
|
|
{
|
||
|
|
BoxMin = FVector2d(FMath::Min(BoxMin.X, P.X), FMath::Min(BoxMin.Y, P.Y));
|
||
|
|
BoxMax = FVector2d(FMath::Max(BoxMax.X, P.X), FMath::Max(BoxMax.Y, P.Y));
|
||
|
|
}
|
||
|
|
if (!Inside(0.5 * (BoxMin + BoxMax))) continue;
|
||
|
|
++AreasKept;
|
||
|
|
FWay& Kept = OutWays.AddDefaulted_GetRef();
|
||
|
|
Kept.Id = Way.Id;
|
||
|
|
Kept.Refs = Refs;
|
||
|
|
Kept.Tags = Way.Tags;
|
||
|
|
for (const int64 Ref : Refs) OutNodes.Add(Ref, Reader.Nodes[Ref]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Line(FString::Printf(TEXT("routes : %d lues, %d gardees (dont %d coupees au bord), %d morceaux ; autres voies : %d lues, %d gardees (centre de boite dans la boite) ; references absentes %d"),
|
||
|
|
RoadsIn, RoadsKept, RoadsCut, RoadPieces, AreasIn, AreasKept, MissingRefs));
|
||
|
|
|
||
|
|
// 3. Ecriture (noeuds puis voies) et origine attendue (moyenne des coordonnees ecrites, comme FOSMFile).
|
||
|
|
TUniquePtr<FArchive> Writer(IFileManager::Get().CreateFileWriter(*OutPath));
|
||
|
|
if (!Writer)
|
||
|
|
{
|
||
|
|
Line(TEXT("ECHEC : creation du fichier de sortie"));
|
||
|
|
return Finish(1);
|
||
|
|
}
|
||
|
|
FString Buffer;
|
||
|
|
auto Flush = [&](bool bForce)
|
||
|
|
{
|
||
|
|
if (bForce || Buffer.Len() > (1 << 20))
|
||
|
|
{
|
||
|
|
const FTCHARToUTF8 Utf8(*Buffer);
|
||
|
|
Writer->Serialize(const_cast<ANSICHAR*>(Utf8.Get()), Utf8.Length());
|
||
|
|
Buffer.Reset();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
Buffer += TEXT("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<osm version=\"0.6\" generator=\"LifePlay LPClipCityOSM (STM1-SPEC-09)\">\n");
|
||
|
|
Buffer += FString::Printf(TEXT(" <bounds minlat=\"%s\" minlon=\"%s\" maxlat=\"%s\" maxlon=\"%s\"/>\n"), *Coord(Min.Y), *Coord(Min.X), *Coord(Max.Y), *Coord(Max.X));
|
||
|
|
TArray<int64> NodeIds;
|
||
|
|
OutNodes.GetKeys(NodeIds);
|
||
|
|
NodeIds.Sort();
|
||
|
|
double SumLat = 0.0, SumLon = 0.0;
|
||
|
|
for (const int64 Id : NodeIds)
|
||
|
|
{
|
||
|
|
const FVector2d& P = OutNodes[Id];
|
||
|
|
const FString Lat = Coord(P.Y), Lon = Coord(P.X);
|
||
|
|
SumLat += FCString::Atod(*Lat);
|
||
|
|
SumLon += FCString::Atod(*Lon);
|
||
|
|
Buffer += FString::Printf(TEXT(" <node id=\"%lld\" lat=\"%s\" lon=\"%s\"/>\n"), Id, *Lat, *Lon);
|
||
|
|
Flush(false);
|
||
|
|
}
|
||
|
|
for (const FWay& Way : OutWays)
|
||
|
|
{
|
||
|
|
Buffer += FString::Printf(TEXT(" <way id=\"%lld\">\n"), Way.Id);
|
||
|
|
for (const int64 Ref : Way.Refs) Buffer += FString::Printf(TEXT(" <nd ref=\"%lld\"/>\n"), Ref);
|
||
|
|
for (const TPair<FString, FString>& Tag : Way.Tags) Buffer += FString::Printf(TEXT(" <tag k=\"%s\" v=\"%s\"/>\n"), *Tag.Key, *Tag.Value);
|
||
|
|
Buffer += TEXT(" </way>\n");
|
||
|
|
Flush(false);
|
||
|
|
}
|
||
|
|
Buffer += TEXT("</osm>\n");
|
||
|
|
Flush(true);
|
||
|
|
Writer->Close();
|
||
|
|
const int64 OutBytes = IFileManager::Get().FileSize(*OutPath);
|
||
|
|
|
||
|
|
const double OriginLat = NodeIds.Num() ? SumLat / NodeIds.Num() : 0.0;
|
||
|
|
const double OriginLon = NodeIds.Num() ? SumLon / NodeIds.Num() : 0.0;
|
||
|
|
Line(FString::Printf(TEXT("ecrit : %s, %lld octets ; %d noeuds (dont %d crees au bord), %d voies"),
|
||
|
|
*OutPath, OutBytes, NodeIds.Num(), BoundaryNodes, OutWays.Num()));
|
||
|
|
Line(FString::Printf(TEXT("origine attendue de l'import (moyenne des noeuds ecrits) : lat %.9f lon %.9f"), OriginLat, OriginLon));
|
||
|
|
const FPlatformMemoryStats Mem = FPlatformMemory::GetStats();
|
||
|
|
Line(FString::Printf(TEXT("duree %.1f s ; pic physique %.2f Go"), FPlatformTime::Seconds() - Start, double(Mem.PeakUsedPhysical) / (1024.0 * 1024.0 * 1024.0)));
|
||
|
|
return Finish(0);
|
||
|
|
}
|