Skip to content

Commit

Permalink
Improve performance when writing MBTiles (#31)
Browse files Browse the repository at this point in the history
* Write encoded geometries directly to the destination list
* If id is ulong use it directly
  • Loading branch information
danzel authored Aug 12, 2024
1 parent 2bb98ee commit 6a30b00
Showing 1 changed file with 38 additions and 41 deletions.
79 changes: 38 additions & 41 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxTileWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ public static void Write(this VectorTile vectorTile, Stream stream, uint extent
{
case IPuntal puntal:
feature.Type = Tile.GeomType.Point;
feature.Geometry.AddRange(Encode(puntal, tgt));
EncodeTo(feature.Geometry, puntal, tgt);
break;
case ILineal lineal:
feature.Type = Tile.GeomType.LineString;
feature.Geometry.AddRange(Encode(lineal, tgt));
EncodeTo(feature.Geometry, lineal, tgt);
break;
case IPolygonal polygonal:
feature.Type = Tile.GeomType.Polygon;
feature.Geometry.AddRange(Encode(polygonal, tgt, tile.Zoom));
EncodeTo(feature.Geometry, polygonal, tgt, tile.Zoom);
break;
default:
feature.Type = Tile.GeomType.Unknown;
Expand All @@ -117,9 +117,12 @@ public static void Write(this VectorTile vectorTile, Stream stream, uint extent
object id = localLayerFeature.Attributes.GetOptionalValue(idAttributeName);

//Converting ID to string, then trying to parse. This will handle situations will ignore situations where the ID value is not actually an integer or ulong number.
if (id != null && ulong.TryParse(id.ToString(), out ulong idVal))
if (id != null)
{
feature.Id = idVal;
if (id is ulong idu)
feature.Id = idu;
else if (ulong.TryParse(id.ToString(), out ulong idVal))
feature.Id = idVal;
}

// Add feature to layer
Expand Down Expand Up @@ -196,14 +199,17 @@ private static Tile.Value ToTileValue(object value)
return null;
}

private static IEnumerable<uint> Encode(IPuntal puntal, TileGeometryTransform tgt)
private static void EncodeTo(List<uint> destination, IPuntal puntal, TileGeometryTransform tgt)
{
const int CoordinateIndex = 0;

var geometry = (Geometry)puntal;
int currentX = 0, currentY = 0;

var parameters = new List<uint>();
int moveToIndex = destination.Count;
destination.Add(0); //Overwritten below by the MoveTo command

//var parameters = new List<uint>();
for (int i = 0; i < geometry.NumGeometries; i++)
{
var point = (Point)geometry.GetGeometryN(i);
Expand All @@ -215,8 +221,8 @@ private static IEnumerable<uint> Encode(IPuntal puntal, TileGeometryTransform tg

if (i == 0 || tgt.IsPointInExtent(currentX, currentY))
{
parameters.Add(GenerateParameterInteger(x));
parameters.Add(GenerateParameterInteger(y));
destination.Add(GenerateParameterInteger(x));
destination.Add(GenerateParameterInteger(y));
}
else
{
Expand All @@ -227,25 +233,21 @@ private static IEnumerable<uint> Encode(IPuntal puntal, TileGeometryTransform tg
}
}

// Return result
yield return GenerateCommandInteger(MapboxCommandType.MoveTo, parameters.Count / 2);
foreach (uint parameter in parameters)
yield return parameter;
destination[moveToIndex] = GenerateCommandInteger(MapboxCommandType.MoveTo, (destination.Count - moveToIndex) / 2);
}

private static IEnumerable<uint> Encode(ILineal lineal, TileGeometryTransform tgt)
private static void EncodeTo(List<uint> destination, ILineal lineal, TileGeometryTransform tgt)
{
var geometry = (Geometry)lineal;
int currentX = 0, currentY = 0;
for (int i = 0; i < geometry.NumGeometries; i++)
{
var lineString = (LineString)geometry.GetGeometryN(i);
foreach (uint encoded in Encode(lineString.CoordinateSequence, tgt, ref currentX, ref currentY, false))
yield return encoded;
EncodeTo(destination, lineString.CoordinateSequence, tgt, ref currentX, ref currentY, false);
}
}

private static IEnumerable<uint> Encode(IPolygonal polygonal, TileGeometryTransform tgt, int zoom)
private static void EncodeTo(List<uint> destination, IPolygonal polygonal, TileGeometryTransform tgt, int zoom)
{
var geometry = (Geometry)polygonal;

Expand All @@ -261,18 +263,16 @@ private static IEnumerable<uint> Encode(IPolygonal polygonal, TileGeometryTransf
if (!tgt.IsGreaterThanOnePixelOfTile(polygon))
continue;

foreach (uint encoded in Encode(polygon.Shell.CoordinateSequence, tgt, ref currentX, ref currentY, true, false))
yield return encoded;
EncodeTo(destination, polygon.Shell.CoordinateSequence, tgt, ref currentX, ref currentY, true, false);
foreach (var hole in polygon.InteriorRings)
{
foreach (uint encoded in Encode(hole.CoordinateSequence, tgt, ref currentX, ref currentY, true, true))
yield return encoded;
EncodeTo(destination, hole.CoordinateSequence, tgt, ref currentX, ref currentY, true, true);
}
}
}
}

private static IEnumerable<uint> Encode(CoordinateSequence sequence, TileGeometryTransform tgt,
private static void EncodeTo(List<uint> destination, CoordinateSequence sequence, TileGeometryTransform tgt,
ref int currentX, ref int currentY,
bool ring = false, bool ccw = false)
{
Expand All @@ -282,7 +282,7 @@ private static IEnumerable<uint> Encode(CoordinateSequence sequence, TileGeometr

// If the sequence is empty there is nothing we can do with it.
if (count == 0)
return Array.Empty<uint>();
return;

// if we have a ring we need to check orientation
if (ring)
Expand All @@ -293,51 +293,48 @@ private static IEnumerable<uint> Encode(CoordinateSequence sequence, TileGeometr
CoordinateSequences.Reverse(sequence);
}
}
var encoded = new List<uint>
{
// Start point
GenerateCommandInteger(MapboxCommandType.MoveTo, 1)
};

int initialSize = destination.Count;
// Start point
destination.Add(GenerateCommandInteger(MapboxCommandType.MoveTo, 1));
var position = tgt.Transform(sequence, 0, ref currentX, ref currentY);
encoded.Add(GenerateParameterInteger(position.x));
encoded.Add(GenerateParameterInteger(position.y));
destination.Add(GenerateParameterInteger(position.x));
destination.Add(GenerateParameterInteger(position.y));

// Add LineTo command (stub)
int lineToCount = 0;
encoded.Add(GenerateCommandInteger(MapboxCommandType.LineTo, lineToCount));
destination.Add(GenerateCommandInteger(MapboxCommandType.LineTo, lineToCount));
for (int i = 1; i < count; i++)
{
position = tgt.Transform(sequence, i, ref currentX, ref currentY);

if (position.x != 0 || position.y != 0)
{
encoded.Add(GenerateParameterInteger(position.x));
encoded.Add(GenerateParameterInteger(position.y));
destination.Add(GenerateParameterInteger(position.x));
destination.Add(GenerateParameterInteger(position.y));
lineToCount++;
}
}
if (lineToCount > 0)
encoded[3] = GenerateCommandInteger(MapboxCommandType.LineTo, lineToCount);
destination[initialSize + 3] = GenerateCommandInteger(MapboxCommandType.LineTo, lineToCount);

// Validate encoded data
if (ring)
{
// A ring has 1 MoveTo and 1 LineTo command.
// A ring is only valid if we have at least 3 points, otherwise collapse
if (encoded.Count - 2 >= 6)
encoded.Add(GenerateCommandInteger(MapboxCommandType.ClosePath, 1));
if (destination.Count - initialSize - 2 >= 6)
destination.Add(GenerateCommandInteger(MapboxCommandType.ClosePath, 1));
else
encoded.Clear();
destination.RemoveRange(initialSize, destination.Count - initialSize);
}
else
{
// A line has 1 MoveTo and 1 LineTo command.
// A line is valid if it has at least 2 points
if (encoded.Count - 2 < 4)
encoded.Clear();
if (destination.Count - initialSize - 2 < 4)
destination.RemoveRange(initialSize, destination.Count - initialSize);
}

return encoded;
}

/*
Expand Down

0 comments on commit 6a30b00

Please sign in to comment.