// 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 Refs; /** Cle et valeur telles que lues (deja echappees en XML : recopiees sans transformation). */ TArray> 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 Nodes; // X = lon, Y = lat TArray 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 Tokens, Switches; TMap 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 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 BoxParts; BoxText.ParseIntoArray(BoxParts, TEXT(",")); if (OsmPath.IsEmpty() || OutPath.IsEmpty() || BoxParts.Num() != 4) { Line(TEXT("usage : -Osm= -Out= -Box=,,, [-Report=]")); 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 OutNodes; TArray 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 Points; TArray 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> Pieces; TArray 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 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(Utf8.Get()), Utf8.Length()); Buffer.Reset(); } }; Buffer += TEXT("\n\n"); Buffer += FString::Printf(TEXT(" \n"), *Coord(Min.Y), *Coord(Min.X), *Coord(Max.Y), *Coord(Max.X)); TArray 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(" \n"), Id, *Lat, *Lon); Flush(false); } for (const FWay& Way : OutWays) { Buffer += FString::Printf(TEXT(" \n"), Way.Id); for (const int64 Ref : Way.Refs) Buffer += FString::Printf(TEXT(" \n"), Ref); for (const TPair& Tag : Way.Tags) Buffer += FString::Printf(TEXT(" \n"), *Tag.Key, *Tag.Value); Buffer += TEXT(" \n"); Flush(false); } Buffer += TEXT("\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); }