Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix misplacement of geometry parts #30

Merged
merged 23 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
450ec94
Update frameworks
FObermaier Mar 18, 2024
e56e0eb
Reset currentX and currentY when encoded data is not written
FObermaier Mar 18, 2024
328c89c
Check extent of polygon's interior rings prior to encoding
FObermaier Mar 19, 2024
60cab58
Update action versions
FObermaier Mar 19, 2024
580ea72
Add extent test for lineal geometries
FObermaier Mar 19, 2024
556e70c
Fix failure case
FObermaier Mar 19, 2024
6a30b00
Improve performance when writing MBTiles (#31)
danzel Aug 12, 2024
3f510d8
Update frameworks
FObermaier Mar 18, 2024
c40b4b1
Reset currentX and currentY when encoded data is not written
FObermaier Mar 18, 2024
1093c35
Check extent of polygon's interior rings prior to encoding
FObermaier Mar 19, 2024
38d9ee1
Update action versions
FObermaier Mar 19, 2024
92bff64
Add extent test for lineal geometries
FObermaier Mar 19, 2024
77f24b9
Fix failure case
FObermaier Mar 19, 2024
f69330d
Fix code documentation
FObermaier Aug 13, 2024
afc6159
Fix rebase glitches
FObermaier Aug 13, 2024
4fee7a5
Enable test
FObermaier Aug 13, 2024
e150f8d
Add min[Lineal|Polygonal]Extent to MapBoxTileWriter
FObermaier Aug 13, 2024
ba0fe2f
Remove duplicate check
FObermaier Aug 13, 2024
22891ef
Remove duplicate check (2)
FObermaier Aug 13, 2024
acee777
Expose default values for min[Lineal|Polygonal]Extent and idAttribute…
FObermaier Aug 13, 2024
ab6e016
Add unit test for min[Lineal|Polygonal]Extent
FObermaier Aug 13, 2024
2c37740
Fix GitHub Actions criticism
FObermaier Aug 14, 2024
ca95a90
Update README.md
FObermaier Aug 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/dotnetcore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ jobs:

steps:
- name: Get source
uses: actions/checkout@v1
uses: actions/checkout@v4

- name: Setup .NET Core 6.
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:

steps:
- name: Get source
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: 'true'
- name: Setup .NET Core 6.
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Restore packages.
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ jobs:

steps:
- name: Get source
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: 'true'
- name: Setup .NET Core 6.
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.x
- name: Restore packages.
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A package that can be used to read or generate vector tiles using NTS.

## Getting started

This package is somewhat experimental. A NuGet-package is hosted on [Github Packages](https://github.com/orgs/NetTopologySuite/packages?repo_name=NetTopologySuite.IO.VectorTiles)
A NuGet-package is hosted on [Github Packages](https://github.com/orgs/NetTopologySuite/packages?repo_name=NetTopologySuite.IO.VectorTiles)

### Create a vector tile

Expand All @@ -34,10 +34,19 @@ vt.Layers.Add(lyr);
using (var fs = new FileStream(filePath, FileMode.Create))
{
//Write the tile to the stream.
vt.Write(fs);
vt.Write(fs, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent);
}
```

`MapboxTileWriter.Write` method takes two arguments controlling the output of features.

Argument | Default | Meaning
--- | --- | ---
minLinealExtent | 1 pixel |This applies to features with lineal geometries. If their scaled geometries for this tile have a bounding box with both edge lengths less than this value will not be written
minPolygonalExtent | 2 pixel<sup>2</sup> | This applies to polygonal geometries. If their scaled geometries for this tile have a bounding box with an area less than this value will not be exported.

Both constraints apply to parts of geometries, too. Holes in polygons or parts of multi-geometries not meeting the requirement will be omitted.

### Read a vector tile

The following shows how to read an individual vector tile and access the underlying NTS Features.
Expand Down Expand Up @@ -127,10 +136,10 @@ vt.Layers.Add(lyr);
using (var fs = new FileStream(filePath, FileMode.Create))
{
//Write the tile to the stream. This will automatically look for an "id" attribute that is a ulong or integer value as set the tile's feature ID to it.
vt.Write(fs);
vt.Write(fs, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent);

//Alternatively, pass in a different attribute name to have the vector tiles feature use that ID value.
//vt.Write(fs, 4096, "alternateId");
//vt.Write(fs, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent, 4096, "alternateId");
}
```

Expand Down
180 changes: 129 additions & 51 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxTileWriter.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,27 @@ public bool IsPointInExtent(int x, int y)
/// </summary>
/// <param name="polygon">Polygon to test.</param>
/// <returns>true if the <paramref name="polygon"/> is greater than 1 pixel in the tile pixel coordinates</returns>
public bool IsGreaterThanOnePixelOfTile(Geometry polygon)
public bool IsGreaterThanOnePixelOfTile(Geometry geometry)
{
if (polygon.IsEmpty) return false;
if (geometry.IsEmpty) return false;

(double x1, double y1) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(polygon.EnvelopeInternal.MinY, polygon.EnvelopeInternal.MinX), ZoomResolution);
(double x2, double y2) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(polygon.EnvelopeInternal.MaxY, polygon.EnvelopeInternal.MaxX), ZoomResolution);
var env = geometry.EnvelopeInternal;
(double x1, double y1) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(env.MinY, env.MinX), ZoomResolution);
(double x2, double y2) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(env.MaxY, env.MaxX), ZoomResolution);

double dx = Math.Abs(x2 - x1);
double dy = Math.Abs(y2 - y1);

// Both must be greater than 0, and at least one of them needs to be larger than 1.
return dx > 0 && dy > 0 && (dx > 1 || dy > 1);
}

public (long x, long y) ExtentInPixel(Envelope env)
{
(long minX, long minY) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(env.MinY, env.MinX), ZoomResolution);
(long maxX, long maxY) = WebMercatorHandler.FromMetersToPixels(WebMercatorHandler.LatLonToMeters(env.MaxY, env.MaxX), ZoomResolution);

return (maxX - minX, maxY - minY);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<CopyRefAssembliesToPublishDirectory>false</CopyRefAssembliesToPublishDirectory>
</PropertyGroup>

Expand Down Expand Up @@ -34,7 +34,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.28" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON" Version="2.0.2" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion test/NetTopologySuite.IO.VectorTiles.Samples/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

tree.GetExtents(out Pages.IndexModel._BBox, out Pages.IndexModel._MinZoom, out Pages.IndexModel._MaxZoom);

tree.Write("wwwroot/tiles");
tree.Write("wwwroot/tiles", MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ private static void RunTest(string fileName, int minZoom, int maxZoom, string st
};

// write the tiles to disk as mvt.
Mapbox.MapboxTileWriter.Write(tree, "tiles");
Mapbox.MapboxTileWriter.Write(tree, "tiles", 1, 2);

stopwatch.Stop();

Expand Down
52 changes: 52 additions & 0 deletions test/NetTopologySuite.IO.VectorTiles.Tests/Issues/Issue29.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO.VectorTiles.Mapbox;
using System.Collections.Generic;
using System.IO;
using Xunit;

namespace NetTopologySuite.IO.VectorTiles.Tests.Issues
{
public class Issue29
{
[Fact]
public void MvtWriterGeometryIssue()
{
WriteTile(8, 6, 4);
WriteTile(8, 5, 4);
WriteTile(34, 24, 6);
}

private static void WriteTile(int x, int y, int z)
{
var geometry = new WKTReader().Read(ItalyWkt);
var feature = new Feature(geometry, new AttributesTable(new Dictionary<string, object>()
{
{ "id", 1 }
}));
var tileDefinition = new VectorTiles.Tiles.Tile(x, y, z);
var vectorTile = new VectorTile { TileId = tileDefinition.Id };
var layer = new Layer { Name = "layer1" };
vectorTile.Layers.Add(layer);
layer.Features.Add(feature);

byte[] result;
MemoryStream? ms;
using (ms = new MemoryStream(1024 * 32))
{
vectorTile.Write(ms, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent);
result = ms.ToArray();
}

File.WriteAllBytes($"{x}_{y}_{z}.mvt", result);
}

private static string ItalyWkt
{
get
{
return File.ReadAllText(Path.Combine("Issues", $"{typeof(Issue29).Name}.wkt"));
}
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ private void FeatureIdReadWriteTest(Features.Feature featureS, ulong expectedId,
if (string.IsNullOrEmpty(idAttributeName))
{
//No ID property specified when writing.
vtS.Write(ms);
vtS.Write(ms, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent);
}
else
{
vtS.Write(ms, 4096, idAttributeName);
vtS.Write(ms, MapboxTileWriter.DefaultMinLinealExtent, MapboxTileWriter.DefaultMinPolygonalExtent, 4096, idAttributeName);
}
ms.Position = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void TestHashCodeDontChangeOnInstance()
Assert.Equal(hashCode, v.GetHashCode());
}

[Fact(Skip = "This is known to fail.")]
[Fact/*(Skip = "This is known to fail.")*/]
public void TestEquals()
{
var v1 = new Tile.Value {StringValue = "Hello"};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -8,10 +8,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="NetTopologySuite.IO.GeoJSON" Version="2.0.2" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand All @@ -28,4 +31,10 @@
</None>
</ItemGroup>

<ItemGroup>
<None Update="Issues\Issue29.wkt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
36 changes: 24 additions & 12 deletions test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@

protected void AssertRoundTrip(Geometry inputGeometry, Geometry expectedGeometry,
string? name = null, IAttributesTable? properties = null, uint? id = null,
int expectedNumFeatures = 1, IAttributesTable? expectedProperties = null)
int expectedNumFeatures = 1, IAttributesTable? expectedProperties = null,
uint minLinealExtent = MapboxTileWriter.DefaultMinLinealExtent,
uint minPolygonalExtent = MapboxTileWriter.DefaultMinPolygonalExtent, bool poorQuality = false)
{
if (inputGeometry == null)
inputGeometry = FeatureGeometry;
Expand All @@ -73,32 +75,35 @@
VectorTile? vtD = null;
using (var ms = new MemoryStream())
{
vtS.Write(ms, Extent);
vtS.Write(ms, minLinealExtent, minPolygonalExtent, Extent);
ms.Position = 0;
vtD = new MapboxTileReader(Factory).Read(ms, new VectorTiles.Tiles.Tile(0));
}

Assert.NotNull(vtD);
Assert.False(vtD.IsEmpty);
Assert.Equal(1, vtD.Layers.Count);
Assert.False(vtD.IsEmpty, "Parsed vector tile is empty");
Assert.Single(vtD.Layers);
Assert.Equal(expectedNumFeatures, vtD.Layers[0].Features.Count);
var featureD = vtD.Layers[0].Features[0];

// Perform geometry checks
CheckGeometry(expectedGeometry, featureD.Geometry);
CheckGeometry(expectedGeometry, featureD.Geometry, poorQuality);

// Perform attribute checks
CheckAttributes(expectedProperties, featureD.Attributes);
}

private void CheckGeometry(Geometry expected, Geometry parsed)
private void CheckGeometry(Geometry expected, Geometry parsed, bool poorQuality = false)
{
// Points checked
// Type checked
Assert.Equal(expected.OgcGeometryType, parsed.OgcGeometryType);

// Coordinates count checked
Assert.Equal(expected.Coordinates.Length, parsed.Coordinates.Length);

// If the conversion is known to have poor quality than don't test it.
if (poorQuality) return;

double pixelDiff = (85.5 * 2);
double error = 2 * System.Math.Sqrt(pixelDiff * pixelDiff * 2) / Extent;
if (expected is IPuntal)
Expand All @@ -113,7 +118,10 @@
}
}
else
Assert.True(new HausdorffSimilarityMeasure().Measure(expected, parsed) > 1 - error);
{
double similarityMeasure = new HausdorffSimilarityMeasure().Measure(expected, parsed);
Assert.True(similarityMeasure > 1 - error, $"Similarity measure insufficient: {similarityMeasure}\n\tExpected: {expected}\n\tActual: {parsed}");
}
}

private void CheckAttributes(IAttributesTable expected, IAttributesTable parsed)
Expand All @@ -133,7 +141,9 @@
}

protected void AssertRoundTripEmptyTile(string inputDefinition, string? name = null,
IAttributesTable? properties = null, uint? id = null)
IAttributesTable? properties = null, uint? id = null,
uint minLinealExtent = MapboxTileWriter.DefaultMinLinealExtent,
uint minPolygonalExtent = MapboxTileWriter.DefaultMinPolygonalExtent)
{
var inputGeometry = ParseGeometry(inputDefinition);

Expand All @@ -153,7 +163,7 @@
VectorTile? vtD = null;
using (var ms = new MemoryStream())
{
vtS.Write(ms);
vtS.Write(ms, minLinealExtent, minPolygonalExtent);
ms.Position = 0;
vtD = new MapboxTileReader(Factory).Read(ms, new VectorTiles.Tiles.Tile(0));
}
Expand All @@ -164,7 +174,9 @@

protected void AssertRoundTrip(string inputDefinition, string? expectedDefinition = null,
string? name = null, IAttributesTable? properties = null, uint? id = null,
int expectedNumFeatures = 1, IAttributesTable? expectedProperties = null)
int expectedNumFeatures = 1, IAttributesTable? expectedProperties = null,
uint minLinealExtent = MapboxTileWriter.DefaultMinLinealExtent,
uint minPolygonalExtent = MapboxTileWriter.DefaultMinPolygonalExtent, bool poorQuality = false)
{
var input = ParseGeometry(inputDefinition);

Expand All @@ -173,14 +185,14 @@
: input.Copy();

AssertRoundTrip(input, expected, name, properties, id,
expectedNumFeatures, expectedProperties);
expectedNumFeatures, expectedProperties, minLinealExtent, minPolygonalExtent, poorQuality);
}

private Geometry ParseGeometry(string definition)
{
if (!definition.TrimStart().StartsWith("{"))
return Reader.Read(definition);
return Serializer.Deserialize<Geometry>(new JsonTextReader(new StringReader(definition)));

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Possible null reference return.

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / ubuntu-latest

Possible null reference return.

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Possible null reference return.

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / windows-latest

Possible null reference return.

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Possible null reference return.

Check warning on line 195 in test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs

View workflow job for this annotation

GitHub Actions / macOS-latest

Possible null reference return.
}

protected static IAttributesTable? ToAttributesTable(params (string Label, object? Value)[]? attributes)
Expand Down
Loading