Skip to content
This repository has been archived by the owner on Jul 31, 2024. It is now read-only.

use local path in config #16

Merged
merged 15 commits into from
Apr 15, 2024
Merged
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}
2 changes: 1 addition & 1 deletion TileBakeLibrary/BinaryMeshReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

namespace TileBakeLibrary
{
class BinaryMeshReader
public class BinaryMeshReader
{
public static MeshData ReadBinaryMesh(string filename)
{
Expand Down
136 changes: 64 additions & 72 deletions TileBakeLibrary/CityJSONParsing/CityJSON.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ public class CityJSON

public Vector3Double TransformOffset { get => transformOffset; }

public CityJSON(string filepath, bool applyTransformScale = true, bool applyTransformOffset = true)
private int minHoleVertices = 3;
private float minHoleSize = 1;

public CityJSON(string filepath, bool applyTransformScale = true, bool applyTransformOffset = true, int minHoleVertices = 3, float minHoleSize = 1)
{
sourceFilePath = filepath;
cityJsonNode = JSON.StreamParse(filepath);
this.minHoleVertices = minHoleVertices;
this.minHoleSize = minHoleSize;

if (cityJsonNode == null || cityJsonNode["CityObjects"] == null)
{
Expand Down Expand Up @@ -179,26 +184,16 @@ private CityObject ReadCityObject(JSONNode node, string filter = "")
{
if (geometrynode["type"] == "Solid")
{
JSONNode exteriorshell = geometrynode["boundaries"][0];
foreach (JSONNode surfacenode in exteriorshell)
{
surfaces.Add(ReadSurfaceVectors(surfacenode));
}
JSONNode interiorshell = geometrynode[0][1];
if (interiorshell != null)
{
foreach (JSONNode surfacenode in interiorshell)
{
surfaces.Add(ReadSurfaceVectors(surfacenode));
}
}
JSONNode boundaries = geometrynode["boundaries"];
surfaces = ReadSolid(geometrynode, cityObject);
}
if (geometrynode["type"] == "MultiSurface")
else if (geometrynode["type"] == "MultiSurface")
{
foreach (JSONNode surfacenode in geometrynode["boundaries"])
{
surfaces.Add(ReadSurfaceVectors(surfacenode));
}
surfaces = ReadMultiSurface(geometrynode, cityObject);
}
else
{
Console.WriteLine($"Unknown geometry type: {geometrynode["type"]}");
}

//read textureValues
Expand Down Expand Up @@ -235,7 +230,6 @@ private CityObject ReadCityObject(JSONNode node, string filter = "")
}
}


//read SurfaceAttributes
JSONNode semanticsnode = geometrynode["semantics"];
if (semanticsnode != null)
Expand Down Expand Up @@ -294,59 +288,21 @@ private CityObject ReadCityObject(JSONNode node, string filter = "")
return cityObject;
}

private List<Surface> ReadSolid(JSONNode geometrynode)
private List<Surface> ReadSolid(JSONNode geometrynode, CityObject sourceCityObject)
{
JSONNode boundariesNode = geometrynode["boundaries"];
List<Surface> result = new List<Surface>();

foreach (JSONNode node in boundariesNode[0])
{
Surface surf = new Surface();
foreach (JSONNode vertexnode in node[0])
{
surf.outerRing.Add(vertices[vertexnode.AsInt]);
}
result.Add(surf);
}
JSONNode semanticsnode = geometrynode["semantics"];
JSONNode ValuesNode = semanticsnode["values"][0];
for (int i = 0; i < ValuesNode.Count; i++)
{
result[i].SurfaceType = geometrynode["semantics"]["surfaces"][ValuesNode[i].AsInt]["type"];
result[i].semantics = ReadSemantics(geometrynode["semantics"]["surfaces"][ValuesNode[i].AsInt]);
//Read exterior shell
foreach (JSONNode surfacenode in boundariesNode[0])
{
result.Add(ReadSurfaceVectors(surfacenode,sourceCityObject,true,true));
}

if (geometrynode["texture"] != null)
{
Surfacetexture surfacematerial = null;
int counter = 0;
foreach (JSONNode node in geometrynode["texture"][0][0])
{
List<Vector2> indices = new List<Vector2>();
for (int i = 0; i < node[0][0].Count; i++)
{
JSONNode item = node[0][0][i];

if (surfacematerial is null)
{
surfacematerial = Textures[item.AsInt];
result[i].surfacetexture = surfacematerial;
}
else
{
indices.Add(textureVertices[item.AsInt]);
}

}
indices.Reverse();
result[counter].outerringUVs = indices;
counter++;
}
}
return result;
}

private List<Surface> ReadMultiSurface(JSONNode geometrynode)
private List<Surface> ReadMultiSurface(JSONNode geometrynode, CityObject sourceCityObject)
{
JSONNode boundariesNode = geometrynode["boundaries"];
List<Surface> result = new List<Surface>();
Expand All @@ -356,7 +312,6 @@ private List<Surface> ReadMultiSurface(JSONNode geometrynode)
foreach (JSONNode vertexnode in node[0])
{
surf.outerRing.Add(vertices[vertexnode.AsInt]);

}
for (int i = 1; i < node.Count; i++)
{
Expand Down Expand Up @@ -420,26 +375,60 @@ private Surface AddSurfaceUVs(JSONNode UVValueNode, Surface surf)
}
return surf;
}
private Surface ReadSurfaceVectors(JSONNode surfacenode)
private Surface ReadSurfaceVectors(JSONNode surfacenode, CityObject sourceCityObject, bool createOuterRing = true, bool createInnerRings = true)
{
Surface surf = new Surface();
//read exteriorRing
List<Vector3Double> verts = new List<Vector3Double>();
foreach (JSONNode vectornode in surfacenode[0])

if(createOuterRing)
{
verts.Add(vertices[vectornode.AsInt]);
}
surf.outerRing = verts;
foreach (JSONNode vectornode in surfacenode[0])
{
verts.Add(vertices[vectornode.AsInt]);
}
surf.outerRing = verts;
}

if(!createInnerRings)
return surf;

Vector3Double v1;
Vector3Double v2;
for (int i = 1; i < surfacenode.Count; i++)
{
verts = new List<Vector3Double>();
foreach (JSONNode vectornode in surfacenode[i])
{
verts.Add(vertices[vectornode.AsInt]);
}
surf.innerRings.Add(verts);
}

//Skip certain holes if they are too small, of dont have enough vertices
if(verts.Count < minHoleVertices)
{
sourceCityObject.holeWarnings += $"- Found a hole with less than {minHoleVertices} vertices, skipping this hole.\n";
}
else
{
//Calculate the area of the hole
double holeSize = 0;
for (int j = 0; j < verts.Count; j++)
{
v1 = verts[j];
v2 = verts[(j + 1) % verts.Count];
holeSize += (v1.X * v2.Y - v1.Y * v2.X);
}
holeSize = Math.Abs(holeSize) / 2;
if(holeSize < minHoleSize)
{
sourceCityObject.holeWarnings += $"- Found a hole smaller than {minHoleSize}. Hole size is {holeSize}, skipping this hole.\n";
continue;
}

surf.innerRings.Add(verts);
}
}

return surf;
}

Expand All @@ -466,6 +455,9 @@ public class CityObject
public string cityObjectType;
public string keyName = "";

public string holeWarnings = "";
public string triangulationWarnings = "";

public CityObject()
{
semantics = new();
Expand Down
65 changes: 58 additions & 7 deletions TileBakeLibrary/CityJSONToTileConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ namespace TileBakeLibrary
{
public class CityJSONToTileConverter
{
private string logFileName = "";
private string sourcePath = "";
private string outputPath = "";
private string identifier = "";
Expand All @@ -58,6 +59,8 @@ public class CityJSONToTileConverter
private float spikeCeiling = 0;
private float spikeFloor = 0;

private int minHoleVertices = 3;
private float minHoleSize = 0.0001f;
private int filecounter = 0;
private int totalFiles = 0;

Expand Down Expand Up @@ -85,6 +88,24 @@ public void SetClipSpikes(bool setFunction, float ceiling, float floor)
spikeFloor = floor;
}

/// <summary>
/// Sets the minimum amount of vertices a hole should have to be considered a hole
/// </summary>
/// <param name="vertices">Min vertex count of the hole loop. Defaults to 3 ( a triangle )</param>
public void SetMinHoleVertices(int vertices)
{
minHoleVertices = vertices;
}

/// <summary>
/// Sets the minimum size of a hole in square meters
/// </summary>
/// <param name="size">Hole min size in square meters</param>
public void SetMinHoleSize(float size)
{
minHoleSize = size;
}

/// <summary>
/// Sets the square tile size
/// </summary>
Expand Down Expand Up @@ -177,6 +198,13 @@ public void SetObjectFilters(CityObjectFilter[] cityObjectFilters)
///
public void Convert()
{
//Create log file (overwrite)
var readableDateTime = DateTime.Now.ToString("yyyy-MM-dd_HH_mm");
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

logFileName = currentPath + "/bakelog" + readableDateTime + ".txt";
File.WriteAllText(logFileName, string.Empty);

//If no specific filename or wildcard was supplied, default to .json files
var filter = Path.GetFileName(sourcePath);
if (filter == "") filter = "*.json";
Expand All @@ -203,7 +231,7 @@ public void Convert()
totalFiles = sourceFiles.Length;
if (sourceFiles.Length > 0)
{
cityJson = new CityJSON(sourceFiles[0], true, true);
cityJson = new CityJSON(sourceFiles[0], true, true, minHoleVertices, minHoleSize);
}
for (int i = 0; i < sourceFiles.Length; i++)
{
Expand All @@ -215,7 +243,7 @@ public void Convert()
nextJsonID = i;
}
Thread thread;
thread = new Thread(() => { nextCityJSON = new CityJSON(sourceFiles[nextJsonID], true, true); });
thread = new Thread(() => { nextCityJSON = new CityJSON(sourceFiles[nextJsonID], true, true, minHoleVertices, minHoleSize); });
thread.Start();

//Start reading current CityJSON
Expand All @@ -227,12 +255,14 @@ public void Convert()
thread.Join();
cityJson = nextCityJSON;
}
Console.WriteLine($"Log file: {currentPath}/bakelog{readableDateTime}.txt");

//Optional compressed variant
if (brotliCompress)
{
CompressFiles();
}
Console.WriteLine($"Log file: {currentPath}/bakelog{readableDateTime}.txt");
}

/// <summary>
Expand All @@ -244,11 +274,24 @@ private void ReadCityJSON()
watch.Start();
tiles = new List<Tile>();

var cityObjects = CityJSONParseProcess(cityJson);
//Warnings bag
var warnings = new ConcurrentBag<string>();
var cityObjects = CityJSONParseProcess(cityJson, warnings);
allSubObjects.Clear();
allSubObjects = cityObjects;

Console.WriteLine($"\n{allSubObjects.Count} CityObjects with LOD{lod} were imported");

//write warnings to log newlines
if(warnings.Count > 0)
File.AppendAllText(logFileName, $"Warnings for {cityJson.sourceFilePath}\n");
foreach (var warning in warnings)
{
File.AppendAllText(logFileName, warning + "\n");
}
File.AppendAllText(logFileName, "\n");

//Tile the objects
PrepareTiles();
WriteTileData();

Expand Down Expand Up @@ -430,7 +473,7 @@ private void ParseExistingBinaryTile(Tile tile)
bmd = null;
}

private List<SubObject> CityJSONParseProcess(CityJSON cityJson)
private List<SubObject> CityJSONParseProcess(CityJSON cityJson, ConcurrentBag<string> warnings)
{
List<SubObject> filteredObjects = new List<SubObject>();
Console.WriteLine("");
Expand All @@ -444,6 +487,7 @@ private List<SubObject> CityJSONParseProcess(CityJSON cityJson)
int simplifying = 0;
int tiling = 0;
var filterObjectsBucket = new ConcurrentBag<SubObject>();
var failedSubObjects = new ConcurrentBag<string>();
int[] indices = Enumerable.Range(0, cityObjectCount).ToArray();

//Turn cityobjects (and their children) into SubObject mesh data
Expand All @@ -455,9 +499,17 @@ private List<SubObject> CityJSONParseProcess(CityJSON cityJson)

CityObject cityObject = cityJson.LoadCityObjectByIndex(i, lod);
var subObject = ToSubObjectMeshData(cityObject);

//Collect warnings for log
if(!string.IsNullOrEmpty(cityObject.holeWarnings))
warnings.Add(cityObject.keyName + ":\n" + cityObject.holeWarnings);

if(!string.IsNullOrEmpty(cityObject.triangulationWarnings))
warnings.Add(cityObject.keyName + ":\n" + cityObject.holeWarnings);

cityObject = null;
Interlocked.Decrement(ref parsing);
cityObject = null;

if (subObject == null)
{
Interlocked.Increment(ref done);
Expand Down Expand Up @@ -654,7 +706,6 @@ private static void AppendCityObjectGeometry(CityObject cityObject, SubObject su
{
normallist.AddRange(defaultnormalList);
}

continue;
}

Expand Down Expand Up @@ -707,7 +758,7 @@ private static void AppendCityObjectGeometry(CityObject cityObject, SubObject su
continue;
}
//Poly2Mesh takes care of calculating normals, using a right-handed coordinate system
Poly2Mesh.CreateMeshData(poly, out surfaceVertices, out surfaceNormals, out surfaceIndices, out surfaceUvs);
Poly2Mesh.CreateMeshData(poly, out surfaceVertices, out surfaceNormals, out surfaceIndices, out surfaceUvs, cityObject);

var offset = vertexlist.Count + subObject.vertices.Count;

Expand Down
Loading
Loading