Skip to content

Commit b52a801

Browse files
authored
Merge pull request #233 from Geodan/copilot/add-explicit-tiling-option
Add explicit tiling support for OCTREE subdivision
2 parents 06510e7 + 4a11cc9 commit b52a801

File tree

13 files changed

+280
-39
lines changed

13 files changed

+280
-39
lines changed

README.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -330,15 +330,7 @@ When the input geometries are distributed in a flat area (like buildings in a ci
330330

331331
OCTREE is used when the input geometries are distributed in a cube-like area.
332332

333-
OCTREE tiling schema is currently in development.
334-
335-
Most features are supported when using OCTREE subdivsion, except:
336-
337-
- Explicting tiling;
338-
339-
- LOD support;
340-
341-
- Creating multiple subtree files.
333+
Most features are supported when using OCTREE subdivision, except LOD support;
342334

343335
## Query parameter
344336

src/b3dm.tileset.tests/CesiumTilerTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,43 @@ public void TestCreateExplicitTilesetsJson()
111111
var json = File.ReadAllText("testExplicit/tileset.json");
112112
Assert.That(json.Contains("geometricError"));
113113
}
114+
115+
[Test]
116+
public void TestCreateExplicitTilesetsJson3D()
117+
{
118+
// Arrange
119+
var version = new Version(1, 0);
120+
var outputDirectory = "testExplicit3D";
121+
var outputDirs = OutputDirectoryCreator.GetFolders(outputDirectory);
122+
var translation = new double[] { 0, 0, 0 };
123+
var geometricError = 100;
124+
var geometricErrorFactor = 2;
125+
var refinement = RefinementType.ADD;
126+
var rootBoundingVolumeRegion = new double[] { 0, 0, 0, 0, 0, 0 };
127+
var createGltf = false;
128+
129+
var tile0 = new Tile3D(0, 0, 0, 0);
130+
131+
var tile1 = new Tile3D(2, 0, 0, 0);
132+
var tile2 = new Tile3D(2, 0, 0, 1);
133+
var tile3 = new Tile3D(3, 0, 0, 0) { Available = true };
134+
135+
var tiles = new List<Tile3D>() { tile0, tile1, tile2, tile3 };
136+
137+
// Create bounding boxes for the tiles
138+
var tileBounds = new Dictionary<string, BoundingBox3D>
139+
{
140+
{ "0_0_0_0", new BoundingBox3D(0, 0, 0, 10, 10, 10) },
141+
{ "2_0_0_0", new BoundingBox3D(0, 0, 0, 5, 5, 5) },
142+
{ "2_0_0_1", new BoundingBox3D(0, 0, 5, 5, 5, 10) },
143+
{ "3_0_0_0", new BoundingBox3D(0, 0, 0, 2.5, 2.5, 2.5) }
144+
};
145+
146+
// Act
147+
CesiumTiler.CreateExplicitTilesetsJson3D(version, outputDirectory, translation, geometricError, geometricErrorFactor, refinement, rootBoundingVolumeRegion, tile1, tiles, tileBounds, createGltf);
148+
149+
// Assert
150+
var json = File.ReadAllText("testExplicit3D/tileset.json");
151+
Assert.That(json.Contains("geometricError"));
152+
}
114153
}

src/b3dm.tileset.tests/b3dm.tileset.tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<PrivateAssets>all</PrivateAssets>
1818
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1919
</PackageReference>
20-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
20+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
2121
</ItemGroup>
2222

2323
<ItemGroup>

src/b3dm.tileset/CesiumTiler.cs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ public static void CreateExplicitTilesetsJson(Version version, string outputDire
5252
{
5353
var splitLevel = (int)Math.Ceiling((tiles.Max((Tile s) => s.Z) + 1.0) / 2.0);
5454

55-
var rootTiles = TileSelector.Select(tiles, tile, 0, splitLevel);
55+
var rootTiles = TileSelector.Select(tiles, tile, 0, splitLevel - 1);
5656
var rootTileset = TreeSerializer.ToTileset(rootTiles, translation, rootBoundingVolumeRegion, geometricError, geometricErrorFactor, version, refinement, tilesetVersion, crs);
5757

5858
var maxlevel = tiles.Max((Tile s) => s.Z);
5959

6060
var externalTilesets = 0;
61-
if (maxlevel > splitLevel) {
61+
if (maxlevel >= splitLevel) {
6262
// now create the tileset.json files on splitLevel
6363

6464
var width = Math.Pow(2, splitLevel);
@@ -113,4 +113,92 @@ private static BoundingBox GetBoundingBox(List<Tile> children)
113113
return new BoundingBox(minx, miny, maxx, maxy);
114114
}
115115

116+
private static BoundingBox3D GetBoundingBox3D(List<Tile3D> children, Dictionary<string, BoundingBox3D> tileBounds)
117+
{
118+
var keys = children.Select(t => $"{t.Level}_{t.Z}_{t.X}_{t.Y}").ToList();
119+
var boxes = keys.Select(k => tileBounds[k]).ToList();
120+
121+
var minx = boxes.Min(b => b.XMin);
122+
var maxx = boxes.Max(b => b.XMax);
123+
var miny = boxes.Min(b => b.YMin);
124+
var maxy = boxes.Max(b => b.YMax);
125+
var minz = boxes.Min(b => b.ZMin);
126+
var maxz = boxes.Max(b => b.ZMax);
127+
128+
return new BoundingBox3D(minx, miny, minz, maxx, maxy, maxz);
129+
}
130+
131+
public static List<Tile3D> GetChildren(List<Tile3D> tiles, Tile3D tile)
132+
{
133+
var res = new List<Tile3D>();
134+
var root = tiles.Where(x => x.Equals(tile) && x.Available).FirstOrDefault();
135+
if (root != null) {
136+
res.Add(root);
137+
}
138+
var children = tiles.Where(x => tile.HasChild(x) && x.Available);
139+
res.AddRange(children);
140+
141+
return res;
142+
}
143+
144+
public static void CreateExplicitTilesetsJson3D(Version version, string outputDirectory, double[] translation, double geometricError, double geometricErrorFactor, RefinementType refinement, double[] rootBoundingVolumeRegion, Tile3D tile, List<Tile3D> tiles, Dictionary<string, BoundingBox3D> tileBounds, bool createGltf = false, string tilesetVersion = "", string crs = "")
145+
{
146+
var splitLevel = (int)Math.Ceiling((tiles.Max((Tile3D s) => s.Level) + 1.0) / 2.0);
147+
148+
var rootTiles = TileSelector3D.Select(tiles, tile, 0, splitLevel - 1);
149+
var rootTileset = TreeSerializer.ToTileset3D(rootTiles, tileBounds, translation, rootBoundingVolumeRegion, geometricError, geometricErrorFactor, version, refinement, createGltf, tilesetVersion, crs);
150+
151+
var maxlevel = tiles.Max((Tile3D s) => s.Level);
152+
153+
var externalTilesets = 0;
154+
if (maxlevel >= splitLevel) {
155+
// now create the tileset.json files on splitLevel
156+
157+
var dimension = Math.Pow(2, splitLevel);
158+
Console.WriteLine($"Writing tileset.json files...");
159+
160+
for (var x = 0; x < dimension; x++) {
161+
for (var y = 0; y < dimension; y++) {
162+
for (var z = 0; z < dimension; z++) {
163+
var splitLevelTile = new Tile3D(splitLevel, x, y, z);
164+
var children = GetChildren(tiles, splitLevelTile);
165+
166+
if (children.Count > 0) {
167+
var childrenBbox3D = GetBoundingBox3D(children, tileBounds);
168+
var childrenBbox2D = new BoundingBox(childrenBbox3D.XMin, childrenBbox3D.YMin, childrenBbox3D.XMax, childrenBbox3D.YMax);
169+
var childrenBoundingVolumeRegion = childrenBbox2D.ToRadians().ToRegion(childrenBbox3D.ZMin, childrenBbox3D.ZMax);
170+
171+
// Translation is the same as identity matrix in case of child tileset.
172+
var tileset = TreeSerializer.ToTileset3D(children, tileBounds, null, childrenBoundingVolumeRegion, geometricError, geometricErrorFactor, version, refinement, createGltf, tilesetVersion);
173+
174+
var childGeometricError = GeometricErrorCalculator.GetGeometricError(geometricError, geometricErrorFactor, splitLevel);
175+
tileset.geometricError = childGeometricError;
176+
tileset.root.geometricError = GeometricErrorCalculator.GetGeometricError(childGeometricError, geometricErrorFactor, 1);
177+
var detailedJson = JsonConvert.SerializeObject(tileset, Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
178+
var filename = $"tileset_{splitLevel}_{z}_{x}_{y}.json";
179+
Console.Write($"\rWriting {filename}...");
180+
181+
File.WriteAllText($"{outputDirectory}{Path.AltDirectorySeparatorChar}{filename}", detailedJson);
182+
externalTilesets++;
183+
// add the child tilesets to the root tileset
184+
var child = new Child();
185+
child.boundingVolume = new Boundingvolume() { region = childrenBoundingVolumeRegion };
186+
child.refine = refinement;
187+
child.geometricError = tileset.root.geometricError;
188+
189+
child.content = new Content() { uri = filename };
190+
rootTileset.root.children.Add(child);
191+
}
192+
}
193+
}
194+
}
195+
}
196+
// write the root tileset
197+
Console.WriteLine();
198+
Console.WriteLine($"External tileset.json files: {externalTilesets}");
199+
Console.WriteLine("Writing root tileset.json...");
200+
var rootJson = JsonConvert.SerializeObject(rootTileset, Formatting.Indented, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
201+
File.WriteAllText($"{outputDirectory}{Path.AltDirectorySeparatorChar}tileset.json", rootJson);
202+
}
203+
116204
}

src/b3dm.tileset/OctreeTiler.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ public OctreeTiler(NpgsqlConnection conn, InputTable inputTable, TilingSettings
2727
}
2828

2929
public List<Tile3D> GenerateTiles3D(BoundingBox3D bbox, int level, Tile3D tile, List<Tile3D> tiles)
30+
{
31+
return GenerateTiles3D(bbox, level, tile, tiles, null);
32+
}
33+
34+
public List<Tile3D> GenerateTiles3D(BoundingBox3D bbox, int level, Tile3D tile, List<Tile3D> tiles, Dictionary<string, BoundingBox3D> tileBounds)
3035
{
3136
var where = inputTable.GetQueryClause();
3237

@@ -35,6 +40,10 @@ public List<Tile3D> GenerateTiles3D(BoundingBox3D bbox, int level, Tile3D tile,
3540
var t2 = new Tile3D(level, tile.X, tile.Y, tile.Z);
3641
t2.Available = false;
3742
tiles.Add(t2);
43+
if (tileBounds != null) {
44+
var key = $"{level}_{tile.Z}_{tile.X}_{tile.Y}";
45+
tileBounds[key] = bbox;
46+
}
3847
}
3948
else if (numberOfFeatures > tilingSettings.MaxFeaturesPerTile) {
4049
level++;
@@ -56,7 +65,7 @@ public List<Tile3D> GenerateTiles3D(BoundingBox3D bbox, int level, Tile3D tile,
5665
var bbox3d = new BoundingBox3D(xstart, ystart, z_start, xend, yend, zend);
5766

5867
var new_tile = new Tile3D(level, tile.X * 2 + x, tile.Y * 2 + y, tile.Z * 2 + z);
59-
GenerateTiles3D(bbox3d, level, new_tile, tiles);
68+
GenerateTiles3D(bbox3d, level, new_tile, tiles, tileBounds);
6069
}
6170
}
6271
}
@@ -88,6 +97,10 @@ public List<Tile3D> GenerateTiles3D(BoundingBox3D bbox, int level, Tile3D tile,
8897
}
8998

9099
tiles.Add(tile);
100+
if (tileBounds != null) {
101+
var key = $"{tile.Level}_{tile.Z}_{tile.X}_{tile.Y}";
102+
tileBounds[key] = bbox;
103+
}
91104
}
92105

93106
return tiles;

src/b3dm.tileset/OutputDirectoryCreator.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ namespace pg2b3dm;
55

66
public static class OutputDirectoryCreator
77
{
8-
public static OutputSettings GetFolders(string outputFolder)
8+
public static OutputSettings GetFolders(string outputFolder, bool createSubtreeFolder = true)
99
{
1010
if (!Directory.Exists(outputFolder)) {
1111
Directory.CreateDirectory(outputFolder);
1212
}
1313

14-
var subtreesDirectory = $"{outputFolder}{Path.AltDirectorySeparatorChar}subtrees";
15-
if (!Directory.Exists(subtreesDirectory)) {
16-
Directory.CreateDirectory(subtreesDirectory);
14+
string subtreesDirectory = string.Empty;
15+
16+
if (createSubtreeFolder) {
17+
subtreesDirectory = $"{outputFolder}{Path.AltDirectorySeparatorChar}subtrees";
18+
if (!Directory.Exists(subtreesDirectory)) {
19+
Directory.CreateDirectory(subtreesDirectory);
20+
}
1721
}
1822

1923
var contentDirectory = $"{outputFolder}{Path.AltDirectorySeparatorChar}content";
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using subtree;
2+
3+
namespace B3dm.Tileset;
4+
5+
public class Tile3DWithBounds
6+
{
7+
public Tile3D Tile { get; set; }
8+
public BoundingBox3D BoundingBox { get; set; }
9+
10+
public Tile3DWithBounds(Tile3D tile, BoundingBox3D boundingBox)
11+
{
12+
Tile = tile;
13+
BoundingBox = boundingBox;
14+
}
15+
}

src/b3dm.tileset/TileSelector3D.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using subtree;
4+
5+
namespace B3dm.Tileset;
6+
public static class TileSelector3D
7+
{
8+
public static List<Tile3D> Select(List<Tile3D> tiles, Tile3D root, int fromLevel, int toLevel)
9+
{
10+
if (tiles.Count == 1 && tiles.First().Level == root.Level) {
11+
return tiles;
12+
}
13+
var result = new List<Tile3D>();
14+
for (var level = fromLevel; level <= toLevel; level++) {
15+
var selected = tiles.FindAll(t => t.Level == level && t.Available && root.HasChild(t));
16+
result.AddRange(selected);
17+
}
18+
return result;
19+
}
20+
}

src/b3dm.tileset/TreeSerializer.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,65 @@ public static Child GetChild(Tile tile, double geometricError)
147147

148148
return child;
149149
}
150+
151+
public static TileSet ToTileset3D(List<subtree.Tile3D> tiles, Dictionary<string, BoundingBox3D> tileBounds, double[] translate, double[] region, double geometricError, double geometricErrorFactor = 2, Version version = null, RefinementType refine = RefinementType.ADD, bool createGltf = false, string tilesetVersion = "", string crs = "")
152+
{
153+
var tileset = GetTilesetObject(version, geometricError, tilesetVersion, crs);
154+
155+
var t = new double[] { 1.0, 0.0, 0.0, 0.0,
156+
0.0,1.0, 0.0, 0.0,
157+
0.0, 0.0, 1.0, 0.0,
158+
0.0, 0.0, 0.0, 1.0};
159+
160+
if (translate != null) {
161+
t = new double[] { 1.0, 0.0, 0.0, 0.0,
162+
0.0,1.0, 0.0, 0.0,
163+
0.0, 0.0, 1.0, 0.0,
164+
translate[0], translate[1], translate[2], 1.0};
165+
}
166+
167+
var root = GetRoot(geometricError, t, region, refine);
168+
tileset.geometricError = geometricError;
169+
root.geometricError = GeometricErrorCalculator.GetGeometricError(geometricError, geometricErrorFactor, 1);
170+
var childrenGeometricError = GeometricErrorCalculator.GetGeometricError(geometricError, geometricErrorFactor, 2);
171+
var children = GetChildren3D(tiles, tileBounds, childrenGeometricError, geometricErrorFactor, createGltf);
172+
root.children = children;
173+
174+
tileset.root = root;
175+
return tileset;
176+
}
177+
178+
private static List<Child> GetChildren3D(List<subtree.Tile3D> tiles, Dictionary<string, BoundingBox3D> tileBounds, double geometricError, double geometricErrorFactor, bool createGltf = false)
179+
{
180+
var children = new List<Child>();
181+
foreach (var tile in tiles) {
182+
if (tile.Available) {
183+
var ge = GeometricErrorCalculator.GetGeometricError(geometricError, geometricErrorFactor, tile.Level);
184+
var child = GetChild3D(tile, tileBounds, ge, createGltf);
185+
children.Add(child);
186+
}
187+
}
188+
189+
return children;
190+
}
191+
192+
public static Child GetChild3D(subtree.Tile3D tile, Dictionary<string, BoundingBox3D> tileBounds, double geometricError, bool createGltf = false)
193+
{
194+
var ext = createGltf ? ".glb" : ".b3dm";
195+
var child = new Child {
196+
geometricError = geometricError,
197+
content = new Content()
198+
};
199+
child.content.uri = $"content{Path.AltDirectorySeparatorChar}{tile.Level}_{tile.Z}_{tile.X}_{tile.Y}{ext}";
200+
201+
var key = $"{tile.Level}_{tile.Z}_{tile.X}_{tile.Y}";
202+
var bbox = tileBounds[key];
203+
var boundingBox = new BoundingBox(bbox.XMin, bbox.YMin, bbox.XMax, bbox.YMax);
204+
var region = boundingBox.ToRadians().ToRegion(bbox.ZMin, bbox.ZMax);
205+
child.boundingVolume = new Boundingvolume {
206+
region = region
207+
};
208+
209+
return child;
210+
}
150211
}

src/pg2b3dm.database.tests/pg2b3dm.database.tests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.Extensions.Configuration" Version="9.0.10" />
13-
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
14-
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
15-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.0" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.0" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
1616
<PackageReference Include="NUnit" Version="4.4.0" />
1717
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />
1818
<PackageReference Include="NUnit.Analyzers" Version="4.11.2">

0 commit comments

Comments
 (0)