Skip to content

Commit

Permalink
add holes support + polygons
Browse files Browse the repository at this point in the history
  • Loading branch information
bertt committed Feb 7, 2024
1 parent 603507a commit a7e6989
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 24 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Triangulator

.NET 6 library for triangulating 3D WKB geometries (PolyhedralSurface/MultiPolygon) using Earcut algorithm
.NET 6 library for triangulating 3D WKB geometries (PolyhedralSurface/MultiPolygon/Polygon) using Earcut algorithm

## NuGet

Expand Down Expand Up @@ -31,9 +31,8 @@ POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 0 0,0 0 0)),((1 1 0,1 0 0,0 1 0,1 1 0)),((0

## Remarks

- Input wkb must be of type PolyhedralSurface/MultiPolygon, otherwise an error will occur;
- Input wkb must be of type PolyhedralSurface/MultiPolygon/Polygon, otherwise an error will occur;
- Triangulated geometry is returned as WKB (as PolyhedralSurface/MultiPolygon);
- Geometries with holes are not supported (yet).

## Method

Expand Down Expand Up @@ -73,6 +72,8 @@ wkx-sharp - https://github.com/cschwarz/wkx-sharp for handling geometries

## History

2024-02-07: release 1.3.0: add support for interior rings + polygons

2023-09-26: release 1.2.3: fix normal close to 0

2023-09-22: release 1.2.2: fix normals calculation
Expand Down
22 changes: 19 additions & 3 deletions src/triangulator.tests/TriangulateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ namespace Triangulate.Tests
{
public class Tests
{
[Test]
// testcase: ogc_fid = ogc_fid = 13967 of Delaware dataset
public void TriangulateWithInteriorRing()
{
var wkt = "POLYGON Z ((-75.55329332999997 39.101191216000075 0,-75.55324867699994 39.101184972000055 0,-75.55321256999997 39.101297267000064 0,-75.55286213099998 39.10123284600007 0,-75.55278609799996 39.10147265200004 0,-75.55273937899995 39.10146597100004 0,-75.55273290199995 39.10148368100005 0,-75.55249205599995 39.10143683100006 0,-75.55252592399995 39.101320928000064 0,-75.55243013499995 39.101299453000024 0,-75.55249910199996 39.10107472900006 0,-75.55243817199994 39.10106216400004 0,-75.55260907299999 39.10051940000005 0,-75.55279111499993 39.10055427900005 0,-75.55280219199994 39.10052833700007 0,-75.55342259199995 39.100650028000075 0,-75.55338423099994 39.10078085500004 0,-75.55341641699994 39.100788409000074 0,-75.55329332999997 39.101191216000075 0),(-75.55273513599997 39.101084135000065 0,-75.55288693999995 39.10111439600007 0,-75.55289197899998 39.10110587500003 0,-75.55285487799995 39.10106216400004 0,-75.55288545899998 39.10105070700007 0,-75.55289078899995 39.10104343000006 0,-75.55297369599998 39.10099946400004 0,-75.55302623199998 39.10104551200004 0,-75.55304452299998 39.10102677900005 0,-75.55310796299995 39.100821739000025 0,-75.55282236499994 39.10077075500004 0,-75.55273513599997 39.101084135000065 0))";
var geom = (Polygon)Geometry.Deserialize<WktSerializer>(Encoding.UTF8.GetBytes(wkt));
Assert.That(geom.InteriorRings.Count > 0);
var wkb = geom.AsBinary();
var wkbResult = Triangulator.Triangulate(wkb);

// convert to wkt
var result = (MultiPolygon)Geometry.Deserialize<WkbSerializer>(wkbResult);
Assert.That(result.Geometries.Count == 29);
}

[Test]
public void NormalTest()
{
Expand All @@ -29,12 +44,12 @@ public void MultipolygonZTriangulateTest()
{
// arrange
var buildingWkt = "MULTIPOLYGON Z(((43603.770435546874 361514.6418164063 1.2790000438690186,43603.50600073242 361509.7590039063 1.2790000438690186,43598.60899755859 361510.0461132813 1.2790000438690186,43603.770435546874 361514.6418164063 1.2790000438690186)),((43599.11101171875 361520.242890625 1.2790000438690186,43604.058002685546 361519.95211914065 1.2790000438690186,43598.85057470703 361514.95309570315 1.2790000438690186,43599.11101171875 361520.242890625 1.2790000438690186)),((43598.85057470703 361514.95309570315 1.2790000438690186,43603.770435546874 361514.6418164063 1.2790000438690186,43598.60899755859 361510.0461132813 1.2790000438690186,43598.85057470703 361514.95309570315 1.2790000438690186)),((43604.058002685546 361519.95211914065 1.2790000438690186,43603.770435546874 361514.6418164063 1.2790000438690186,43598.85057470703 361514.95309570315 1.2790000438690186,43604.058002685546 361519.95211914065 1.2790000438690186)),((43598.60899755859 361510.0461132813 6.778176307678223,43598.85057470703 361514.95309570315 7.397906303405762,43598.85057470703 361514.95309570315 1.2790000438690186,43598.60899755859 361510.0461132813 6.778176307678223)),((43598.60899755859 361510.0461132813 1.2790000438690186,43598.60899755859 361510.0461132813 6.778176307678223,43598.85057470703 361514.95309570315 1.2790000438690186,43598.60899755859 361510.0461132813 1.2790000438690186)),((43603.770435546874 361514.6418164063 1.2790000438690186,43603.50600073242 361509.7590039063 6.764927387237549,43603.50600073242 361509.7590039063 1.2790000438690186,43603.770435546874 361514.6418164063 1.2790000438690186)),((43603.770435546874 361514.6418164063 1.2790000438690186,43603.770435546874 361514.6418164063 7.381703853607178,43603.50600073242 361509.7590039063 6.764927387237549,43603.770435546874 361514.6418164063 1.2790000438690186)),((43598.85057470703 361514.95309570315 1.2790000438690186,43599.11101171875 361520.242890625 6.854876518249512,43599.11101171875 361520.242890625 1.2790000438690186,43598.85057470703 361514.95309570315 1.2790000438690186)),((43598.85057470703 361514.95309570315 1.2790000438690186,43598.85057470703 361514.95309570315 7.397906303405762,43599.11101171875 361520.242890625 6.854876518249512,43598.85057470703 361514.95309570315 1.2790000438690186)),((43604.058002685546 361519.95211914065 6.836284160614014,43603.770435546874 361514.6418164063 7.381703853607178,43603.770435546874 361514.6418164063 1.2790000438690186,43604.058002685546 361519.95211914065 6.836284160614014)),((43604.058002685546 361519.95211914065 1.2790000438690186,43604.058002685546 361519.95211914065 6.836284160614014,43603.770435546874 361514.6418164063 1.2790000438690186,43604.058002685546 361519.95211914065 1.2790000438690186)),((43599.11101171875 361520.242890625 1.2790000438690186,43604.058002685546 361519.95211914065 6.836284160614014,43604.058002685546 361519.95211914065 1.2790000438690186,43599.11101171875 361520.242890625 1.2790000438690186)),((43599.11101171875 361520.242890625 1.2790000438690186,43599.11101171875 361520.242890625 6.854876518249512,43604.058002685546 361519.95211914065 6.836284160614014,43599.11101171875 361520.242890625 1.2790000438690186)),((43603.50600073242 361509.7590039063 6.764927387237549,43598.60899755859 361510.0461132813 6.778176307678223,43598.60899755859 361510.0461132813 1.2790000438690186,43603.50600073242 361509.7590039063 6.764927387237549)),((43603.50600073242 361509.7590039063 1.2790000438690186,43603.50600073242 361509.7590039063 6.764927387237549,43598.60899755859 361510.0461132813 1.2790000438690186,43603.50600073242 361509.7590039063 1.2790000438690186)),((43603.770435546874 361514.6418164063 7.381703853607178,43598.60899755859 361510.0461132813 6.778176307678223,43603.50600073242 361509.7590039063 6.764927387237549,43603.770435546874 361514.6418164063 7.381703853607178)),((43603.770435546874 361514.6418164063 7.381703853607178,43598.85057470703 361514.95309570315 7.397906303405762,43598.60899755859 361510.0461132813 6.778176307678223,43603.770435546874 361514.6418164063 7.381703853607178)),((43604.058002685546 361519.95211914065 6.836284160614014,43598.85057470703 361514.95309570315 7.397906303405762,43603.770435546874 361514.6418164063 7.381703853607178,43604.058002685546 361519.95211914065 6.836284160614014)),((43604.058002685546 361519.95211914065 6.836284160614014,43599.11101171875 361520.242890625 6.854876518249512,43598.85057470703 361514.95309570315 7.397906303405762,43604.058002685546 361519.95211914065 6.836284160614014)))";
var multipolygon = (MultiPolygon)Wkx.Geometry.Deserialize<WktSerializer>(buildingWkt);
var multipolygon = (MultiPolygon)Geometry.Deserialize<WktSerializer>(buildingWkt);
var wkb = multipolygon.AsBinary();

// act
var wkbResult = Triangulator.Triangulate(wkb);
var multipolygonResult = (MultiPolygon)Wkx.Geometry.Deserialize<WkbSerializer>(wkbResult);
var multipolygonResult = (MultiPolygon)Geometry.Deserialize<WkbSerializer>(wkbResult);

// assert
Assert.That(multipolygonResult.Geometries.Count == multipolygon.Geometries.Count);
Expand Down Expand Up @@ -62,7 +77,8 @@ public void TriangulateCubeTest()
{
// arrange
var cubeWkt = "POLYHEDRALSURFACE Z (((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),((0 0 0, 0 1 0, 0 1 1, 0 0 1, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 1, 1 0 1, 0 0 1, 0 1 1, 1 1 1)),((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1)))";
var expectedResult = "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 0 0,0 0 0)),((1 1 0,1 0 0,0 1 0,1 1 0)),((0 1 1,0 0 1,0 0 0,0 1 1)),((0 0 0,0 1 0,0 1 1,0 0 0)),((1 0 1,0 0 1,0 0 0,1 0 1)),((0 0 0,1 0 0,1 0 1,0 0 0)),((1 1 1,1 0 1,0 1 1,1 1 1)),((0 0 1,0 1 1,1 0 1,0 0 1)),((1 0 0,1 1 0,1 1 1,1 0 0)),((1 1 1,1 0 1,1 0 0,1 1 1)),((1 1 1,1 1 0,0 1 1,1 1 1)),((0 1 0,0 1 1,1 1 0,0 1 0)))";
// was: var expectedResult = "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 0 0,0 0 0)),((1 1 0,1 0 0,0 1 0,1 1 0)),((0 1 1,0 0 1,0 0 0,0 1 1)),((0 0 0,0 1 0,0 1 1,0 0 0)),((1 0 1,0 0 1,0 0 0,1 0 1)),((0 0 0,1 0 0,1 0 1,0 0 0)),((1 1 1,1 0 1,0 1 1,1 1 1)),((0 0 1,0 1 1,1 0 1,0 0 1)),((1 0 0,1 1 0,1 1 1,1 0 0)),((1 1 1,1 0 1,1 0 0,1 1 1)),((1 1 1,1 1 0,0 1 1,1 1 1)),((0 1 0,0 1 1,1 1 0,0 1 0)))";
var expectedResult = "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 0 0,0 0 0)),((1 1 0,1 0 0,0 1 0,1 1 0)),((0 0 1,0 0 0,0 1 0,0 0 1)),((0 1 0,0 1 1,0 0 1,0 1 0)),((0 0 1,0 0 0,1 0 0,0 0 1)),((1 0 0,1 0 1,0 0 1,1 0 0)),((1 1 1,1 0 1,0 1 1,1 1 1)),((0 0 1,0 1 1,1 0 1,0 0 1)),((1 1 0,1 1 1,1 0 1,1 1 0)),((1 0 1,1 0 0,1 1 0,1 0 1)),((1 1 1,1 1 0,0 1 1,1 1 1)),((0 1 0,0 1 1,1 1 0,0 1 0)))";
var wkt = Geometry.Deserialize<WktSerializer>(cubeWkt);
var wkb = wkt.AsBinary();

Expand Down
7 changes: 3 additions & 4 deletions src/triangulator.tests/triangulator.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Gdal.Core" Version="2.3.0-beta-023" />
<PackageReference Include="Gdal.Core.WindowsRuntime" Version="2.3.0-beta-023" />
<PackageReference Include="nunit" Version="4.0.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.10.0">
<PackageReference Include="NUnit.Analyzers" Version="4.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />

<PackageReference Include="SharpGLTF.Toolkit" Version="1.0.0-alpha0030" />
<PackageReference Include="Wkx" Version="0.5.1" />
</ItemGroup>
Expand Down
3 changes: 1 addition & 2 deletions src/triangulator/PolygonExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Numerics;
using System.Numerics;
using Wkx;

namespace Triangulate
Expand Down
82 changes: 74 additions & 8 deletions src/triangulator/Triangulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,33 @@ public static Geometry Triangulate(Geometry geom)
{
return Triangulate(multiPolygon);
}
else if (geom is Polygon polygon)
{
return Triangulate(polygon);
}
else
{
throw new NotSupportedException($"Geometry type {geom.GeometryType} is not supported");
}
}
}
public static byte[] Triangulate(byte[] wkb)
{
var geom = Geometry.Deserialize<WkbSerializer>(wkb);
var result = Triangulate(geom);
return result.AsBinary();
}

private static MultiPolygon Triangulate(Polygon polygon)
{
var polygons = TriangulatePolygon(polygon);
var result = new MultiPolygon
{
Dimension = Dimension.Xyz
};
result.Geometries.AddRange(polygons);
return result;
}

private static MultiPolygon Triangulate(MultiPolygon multipolygon)
{
var result = new MultiPolygon
Expand Down Expand Up @@ -59,13 +74,13 @@ private static List<Polygon> GetTriangles(List<Polygon> geometries)
var result = new List<Polygon>();
foreach (var g in geometries)
{
var triangles = Triangulate(g);
var triangles = TriangulatePolygon(g);
result.AddRange(triangles);
}
return result;
}

private static List<Polygon> Triangulate(Polygon inputpolygon)
private static List<Polygon> TriangulatePolygon(Polygon inputpolygon)
{
var normal = inputpolygon.GetNormal();
var polygonflat = Flatten(inputpolygon, normal);
Expand All @@ -91,6 +106,15 @@ private static Polygon Flatten(Polygon inputpolygon, Vector3 normal)
{
polygonflat.ExteriorRing.Points.Add(new Point((double)p.Y, (double)p.Z));
}
foreach (var ring in inputpolygon.InteriorRings)
{
var newRing = new LinearRing();
foreach (var p in ring.Points)
{
newRing.Points.Add(new Point((double)p.Y, (double)p.Z));
}
polygonflat.InteriorRings.Add(newRing);
}
}
else if (Math.Abs(normal.Y) > Math.Abs(normal.Z))
{
Expand All @@ -99,6 +123,15 @@ private static Polygon Flatten(Polygon inputpolygon, Vector3 normal)
{
polygonflat.ExteriorRing.Points.Add(new Point((double)p.X, (double)p.Z));
}
foreach (var ring in inputpolygon.InteriorRings)
{
var newRing = new LinearRing();
foreach (var p in ring.Points)
{
newRing.Points.Add(new Point((double)p.X, (double)p.Z));
}
polygonflat.InteriorRings.Add(newRing);
}
}
else
{
Expand All @@ -107,6 +140,16 @@ private static Polygon Flatten(Polygon inputpolygon, Vector3 normal)
{
polygonflat.ExteriorRing.Points.Add(new Point((double)p.X, (double)p.Y));
}

foreach (var ring in inputpolygon.InteriorRings)
{
var newRing = new LinearRing();
foreach (var p in ring.Points)
{
newRing.Points.Add(new Point((double)p.X, (double)p.Y));
}
polygonflat.InteriorRings.Add(newRing);
}
}

return polygonflat;
Expand All @@ -125,7 +168,7 @@ private static Polygon GetPolygon(Polygon inputpolygon, Vector3 normal, List<int
// check crossproduct again...
var normalTriangles = t.GetNormal();
var dot = Vector3.Dot(normal, normalTriangles);
var mustInvert = dot < 0;
var mustInvert = dot < 0;

if (mustInvert)
{
Expand Down Expand Up @@ -154,10 +197,20 @@ private static List<int> Tesselate(Polygon footprint)
var data = new List<double>();
var holeIndices = new List<int>();

for(var p=0;p< points.Count-1;p++)
foreach (var p in points)
{
data.Add((double)p.X);
data.Add((double)p.Y);
}

foreach (var interiorRing in footprint.InteriorRings)
{
data.Add((double)points[p].X);
data.Add((double)points[p].Y);
holeIndices.Add((data.Count / 2) + 1);
foreach (var p in interiorRing.Points)
{
data.Add((double)p.X);
data.Add((double)p.Y);
}
}

var trianglesIndices = Earcut.Tessellate(data, holeIndices);
Expand All @@ -166,7 +219,20 @@ private static List<int> Tesselate(Polygon footprint)

private static Point GetPoint(Polygon polygon, int index)
{
return polygon.ExteriorRing.Points[index];
if(index < polygon.ExteriorRing.Points.Count)
{
return polygon.ExteriorRing.Points[index];
}
else
{
// make a list of the vertices of the interior rings
var interiorRingVertices = new List<Point>();
foreach (var ring in polygon.InteriorRings)
{
interiorRingVertices.AddRange(ring.Points);
}
return interiorRingVertices[index - polygon.ExteriorRing.Points.Count];
}
}
}
}
8 changes: 4 additions & 4 deletions src/triangulator/triangulator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
<PackageTags>wkb triangulate earcut</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>Bert Temme</Copyright>
<Version>1.2.3</Version>
<PackageReleaseNotes>Add support for MultiPolygon</PackageReleaseNotes>
<Version>1.3.0</Version>
<PackageReleaseNotes>Add support for holes and polygons</PackageReleaseNotes>
<RootNamespace>Triangulate</RootNamespace>
<AssemblyVersion>1.2.3</AssemblyVersion>
<FileVersion>1.2.3</FileVersion>
<AssemblyVersion>1.3.0</AssemblyVersion>
<FileVersion>1.3.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit a7e6989

Please sign in to comment.