Skip to content

Commit

Permalink
Add min[Lineal|Polygonal]Extent to MapBoxTileWriter
Browse files Browse the repository at this point in the history
* Add ExtentInPixels to TileGeometryTransform
* Add HasValidExtent functions to EncodeTo functions for lineal and polygonal geometries
  • Loading branch information
FObermaier committed Aug 13, 2024
1 parent 4fee7a5 commit e150f8d
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 21 deletions.
77 changes: 64 additions & 13 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxTileWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,32 @@ namespace NetTopologySuite.IO.VectorTiles.Mapbox
// see: https://github.com/mapbox/vector-tile-spec/tree/master/2.1
public static class MapboxTileWriter
{
private const uint DefaultMinLinealExtent = 1;

private const uint DefaultMinPolygonalExtent = 2;

private const string DefaultIdAttributeName = "id";

/// <summary>
/// Writes the tiles in a /z/x/y.mvt folder structure.
/// </summary>
/// <param name="tree">The tree.</param>
/// <param name="path">The path.</param>
/// <param name="extent">The extent.</param>
/// <remarks>Replaces the files if they are already present.</remarks>
[Obsolete("Use overload that can specify minLineal- and minPolygonalExtent")]
public static void Write(this VectorTileTree tree, string path, uint extent = 4096)
=> Write(tree, path, DefaultMinLinealExtent, DefaultMinPolygonalExtent, extent, DefaultIdAttributeName);

/// <summary>
/// Writes the tiles in a /z/x/y.mvt folder structure.
/// </summary>
/// <param name="tree">The tree.</param>
/// <param name="path">The path.</param>
/// <param name="extent">The extent.</param>
/// <remarks>Replaces the files if they are already present.</remarks>
public static void Write(this VectorTileTree tree, string path, uint minLinealExtent, uint minPolygonalExtent,
uint extent = 4096, string idAttributeName = DefaultIdAttributeName)
{
IEnumerable<VectorTile> GetTiles()
{
Expand All @@ -26,7 +44,7 @@ IEnumerable<VectorTile> GetTiles()
}
}

GetTiles().Write(path, extent);
GetTiles().Write(path, minLinealExtent, minPolygonalExtent, extent, idAttributeName);
}

/// <summary>
Expand All @@ -36,7 +54,19 @@ IEnumerable<VectorTile> GetTiles()
/// <param name="path">The path.</param>
/// <param name="extent">The extent.</param>
/// <remarks>Replaces the files if they are already present.</remarks>
[Obsolete("Use overload that can specify minLineal- and minPolygonalExtent")]
public static void Write(this IEnumerable<VectorTile> vectorTiles, string path, uint extent = 4096)
=> Write(vectorTiles, path, DefaultMinLinealExtent, DefaultMinPolygonalExtent, extent, DefaultIdAttributeName);

/// <summary>
/// Writes the tiles in a /z/x/y.mvt folder structure.
/// </summary>
/// <param name="vectorTiles">The tiles.</param>
/// <param name="path">The path.</param>
/// <param name="extent">The extent.</param>
/// <remarks>Replaces the files if they are already present.</remarks>
public static void Write(this IEnumerable<VectorTile> vectorTiles, string path, uint minLinealExtent, uint minPolygonalExtent,
uint extent = 4096, string idAttributeName = DefaultIdAttributeName)
{
foreach (var vectorTile in vectorTiles)
{
Expand All @@ -54,7 +84,7 @@ public static void Write(this IEnumerable<VectorTile> vectorTiles, string path,
string file = Path.Combine(xFolder, $"{tile.Y}.mvt");

using var stream = File.Open(file, FileMode.Create);
vectorTile.Write(stream, extent);
vectorTile.Write(stream, minLinealExtent, minPolygonalExtent, extent, idAttributeName);
}
}

Expand All @@ -65,7 +95,21 @@ public static void Write(this IEnumerable<VectorTile> vectorTiles, string path,
/// <param name="stream">The stream to write to.</param>
/// <param name="extent">The extent.</param>
/// <param name="idAttributeName">The name of an attribute property to use as the ID for the Feature. Vector tile feature ID's should be integer or ulong numbers.</param>
[Obsolete("Use overload that can specify minLineal- and minPolygonalExtent")]
public static void Write(this VectorTile vectorTile, Stream stream, uint extent = 4096, string idAttributeName = "id")
=> Write(vectorTile, stream, DefaultMinLinealExtent, DefaultMinPolygonalExtent, extent, idAttributeName);

/// <summary>
/// Writes the tile to the given stream.
/// </summary>
/// <param name="vectorTile">The vector tile.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="minLinealExtent">The minimum length in pixel one of a lineal feature's extent edges has to have in order for the feature to be written. The default is 1.</param>
/// <param name="minPolygonalExtent">The minimum area in pixel one of a polygonal feature's extent has to have in order for the feature to be written. The default is 2.</param>
/// <param name="extent">The extent.</param>
/// <param name="idAttributeName">The name of an attribute property to use as the ID for the Feature. Vector tile feature ID's should be integer or ulong numbers.</param>
public static void Write(this VectorTile vectorTile, Stream stream, uint minLinealExtent, uint minPolygonalExtent,
uint extent = 4096, string idAttributeName = "id")
{
var tile = new Tiles.Tile(vectorTile.TileId);
var tgt = new TileGeometryTransform(tile, extent);
Expand Down Expand Up @@ -95,11 +139,11 @@ public static void Write(this VectorTile vectorTile, Stream stream, uint extent
break;
case ILineal lineal:
feature.Type = Tile.GeomType.LineString;
EncodeTo(feature.Geometry, lineal, tgt);
EncodeTo(feature.Geometry, lineal, minLinealExtent, tgt);
break;
case IPolygonal polygonal:
feature.Type = Tile.GeomType.Polygon;
EncodeTo(feature.Geometry, polygonal, tgt, tile.Zoom);
EncodeTo(feature.Geometry, polygonal, minPolygonalExtent, tgt);
break;
default:
feature.Type = Tile.GeomType.Unknown;
Expand Down Expand Up @@ -236,39 +280,46 @@ private static void EncodeTo(List<uint> destination, IPuntal puntal, TileGeometr
destination[moveToIndex] = GenerateCommandInteger(MapboxCommandType.MoveTo, (destination.Count - moveToIndex) / 2);
}

private static void EncodeTo(List<uint> destination, ILineal lineal, TileGeometryTransform tgt)
private static void EncodeTo(List<uint> destination, ILineal lineal, uint minLinealExtent, TileGeometryTransform tgt)
{
bool HasValidLength((long x, long y) tpl)
=> tpl.x >= minLinealExtent || tpl.y >= minLinealExtent;

var geometry = (Geometry)lineal;
int currentX = 0, currentY = 0;
for (int i = 0; i < geometry.NumGeometries; i++)
{
var lineString = (LineString)geometry.GetGeometryN(i);
if (tgt.IsGreaterThanOnePixelOfTile(lineString))
if (HasValidLength(tgt.ExtentInPixel(lineString.EnvelopeInternal)))
EncodeTo(destination, lineString.CoordinateSequence, tgt, ref currentX, ref currentY, false);
}
}

private static void EncodeTo(List<uint> destination, IPolygonal polygonal, TileGeometryTransform tgt, int zoom)
private static void EncodeTo(List<uint> destination, IPolygonal polygonal, uint minPolygonalExtent, TileGeometryTransform tgt)
{
bool HasValidExtent((long x, long y) tpl)
=> (tpl.x > minPolygonalExtent && tpl.y > 0) ||
(tpl.x > 0 && tpl.y > minPolygonalExtent);

var geometry = (Geometry)polygonal;

//Test the whole polygon geometry is larger than a single pixel.
if (tgt.IsGreaterThanOnePixelOfTile(geometry))
//Test the whole polygonal geometry has a valid extent.
if (HasValidExtent(tgt.ExtentInPixel(geometry.EnvelopeInternal)))
{
int currentX = 0, currentY = 0;
for (int i = 0; i < geometry.NumGeometries; i++)
{
var polygon = (Polygon)geometry.GetGeometryN(i);

// Test that the exterior ring is larger than a single pixel.
if (!tgt.IsGreaterThanOnePixelOfTile(polygon.ExteriorRing))
// Test that the exterior ring has a valid extent
if (!HasValidExtent(tgt.ExtentInPixel(polygon.ExteriorRing.EnvelopeInternal)))
continue;

EncodeTo(destination, polygon.ExteriorRing.CoordinateSequence, tgt, ref currentX, ref currentY, true, false);
foreach (var interiorRing in polygon.InteriorRings)
{
// Test that the interior ring is larger than a single pixel.
if (!tgt.IsGreaterThanOnePixelOfTile(interiorRing))
// Test that the interior ring has a valid extent.
if (!HasValidExtent(tgt.ExtentInPixel(interiorRing.EnvelopeInternal)))
continue;

EncodeTo(destination, interiorRing.CoordinateSequence, tgt, ref currentX, ref currentY, true, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,13 @@ public bool IsGreaterThanOnePixelOfTile(Geometry geometry)
// 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);
}
}
}
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", 1, 2);
}
}
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private static void WriteTile(int x, int y, int z)
MemoryStream? ms;
using (ms = new MemoryStream(1024 * 32))
{
vectorTile.Write(ms);
vectorTile.Write(ms, 1, 2);
result = ms.ToArray();
}

Expand Down
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, 1, 2);
}
else
{
vtS.Write(ms, 4096, idAttributeName);
vtS.Write(ms, 1, 2, 4096, idAttributeName);
}
ms.Position = 0;

Expand Down
4 changes: 2 additions & 2 deletions test/NetTopologySuite.IO.VectorTiles.Tests/RoundTripBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ protected void AssertRoundTrip(Geometry inputGeometry, Geometry expectedGeometry
VectorTile? vtD = null;
using (var ms = new MemoryStream())
{
vtS.Write(ms, Extent);
vtS.Write(ms, 1, 2, Extent);
ms.Position = 0;
vtD = new MapboxTileReader(Factory).Read(ms, new VectorTiles.Tiles.Tile(0));
}
Expand Down Expand Up @@ -153,7 +153,7 @@ protected void AssertRoundTripEmptyTile(string inputDefinition, string? name = n
VectorTile? vtD = null;
using (var ms = new MemoryStream())
{
vtS.Write(ms);
vtS.Write(ms, 1, 1);
ms.Position = 0;
vtD = new MapboxTileReader(Factory).Read(ms, new VectorTiles.Tiles.Tile(0));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ private static void DoTestEmpty(Geometry geom, int numGeoms = 0)
var f1 = new Feature(geom, att);
l1.Features.Add(f1);
using var ms = new MemoryStream();
var e = Record.Exception(() => MapboxTileWriter.Write(vt1, ms));
var e = Record.Exception(() => MapboxTileWriter.Write(vt1, ms, 1, 2));
Assert.Null(e);
Assert.True(ms.Length > 0);

Expand Down

0 comments on commit e150f8d

Please sign in to comment.