Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you choose to add an interface over making TileGeometryTransform a public class that can be derived from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. That would be another viable way to do this. Would you prefer, that I write it in this way? Should be possible without problems.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if a class is the better approach. How much of it could be reused? @memsom could you set up a Transform for an arbitrary spatial reference system based on this PR?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@FObermaier I haven't had time to dig in to the PR, but I believe this PR in general would help my use case. I don;t have a preference interface vs class, I mostly need to be able to plot the tiles in the co-ordinates system being used by the mapping engine's world view, and getting there directly without my current hack would help a lot.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's go one step back. All Mapbox vector tile coordinates are in the 4096x4096 raster. The tile has a size of 512x512. The NTS reader converts this coordinates to WGS84 (with negative y axis). It would be nice to have a function, that read the vector tiles data in its native coordinate system or can convert this coordinates in any coordinate system. For that you need a converter. This is the reason for this PR.

I hope, that explains the background. I fear, no one was looking in this part of the code since it was created.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using NetTopologySuite.Geometries;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("NetTopologySuite.IO.VectorTiles.Tests")]
namespace NetTopologySuite.IO.VectorTiles.Mapbox
{
/// <summary>
/// A transformation utility from WGS84 coordinates to a local tile coordinate system in pixel
/// </summary>
public interface ITileGeometryTransform
{
/// <summary>
/// The zoom level pixel resolution based on the extent.
/// </summary>
double ZoomResolution { get; }

/// <summary>
/// Transforms the coordinate at <paramref name="index"/> of <paramref name="sequence"/> to the tile coordinate system.
/// The return value is the position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>).
/// </summary>
/// <param name="sequence">The input sequence</param>
/// <param name="index">The index of the coordinate to transform</param>
/// <param name="currentX">The current horizontal component of the cursor location. This value is updated.</param>
/// <param name="currentY">The current vertical component of the cursor location. This value is updated.</param>
/// <returns>The position relative to the local point at (<paramref name="currentX"/>, <paramref name="currentY"/>).</returns>
(int x, int y) Transform(CoordinateSequence sequence, int index, ref int currentX, ref int currentY);

/// <summary>
/// Transforms the point in the local tile pixel coordinates into WGS84 coordinates.
/// The return value is longitude and latitude of the tile pixel point (<paramref name="x"/>, <paramref name="y"/>).
/// </summary>
/// <param name="x">The horizontal component of the point in the tile coordinate system</param>
/// <param name="y">The vertical component of the point in the tile coordinate system</param>
/// <returns>WGS84 coordinates of the point in tile "pixel" coordinates (<paramref name="x"/>, <paramref name="y"/>).</returns>
(double longitude, double latitude) TransformInverse(int x, int y);

/// <summary>
/// Check if the point with tile coordinates (<paramref name="x"/>, <paramref name="y"/> lies inside tile extent
/// </summary>
/// <param name="x">Horizontal component of the point in the tile coordinate system</param>
/// <param name="y">Vertical component of the point in the tile coordinate system</param>
/// <returns>true if point lies inside tile extent</returns>
bool IsPointInExtent(int x, int y);

/// <summary>
/// Checks to see if a geometries envelope is greater than 1 square pixel in size for a specified zoom level.
/// </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>
bool IsGreaterThanOnePixelOfTile(Geometry geometry);

(long x, long y) ExtentInPixel(Envelope env);
}
}
59 changes: 30 additions & 29 deletions src/NetTopologySuite.IO.VectorTiles.Mapbox/MapboxTileReader.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NetTopologySuite.Features;
using NetTopologySuite.Geometries;

namespace NetTopologySuite.IO.VectorTiles.Mapbox
{
public class MapboxTileReader
{


private readonly GeometryFactory _factory;

/// <summary>
/// The default attribute name of a feature's identifier
/// </summary>
public const string DefaultIdAttributeName = "id";

public MapboxTileReader()
: this(new GeometryFactory(new PrecisionModel(), 4326))
{
Expand Down Expand Up @@ -41,10 +44,8 @@ public VectorTile Read(Stream stream, Tiles.Tile tileDefinition)
/// <param name="tileDefinition">Tile information.</param>
/// <param name="idAttributeName">Optional. Specifies the name of the attribute that the vector tile feature's ID should be stored in the NetTopologySuite Features AttributeTable.</param>
/// <returns></returns>
public VectorTile Read(Stream stream, Tiles.Tile tileDefinition, string idAttributeName)
public VectorTile Read(Stream stream, Tiles.Tile tileDefinition, string idAttributeName = DefaultIdAttributeName, Func<Tiles.Tile, uint, ITileGeometryTransform> tgtFactory = null)
{


// Deserialize the tile
var tile = ProtoBuf.Serializer.Deserialize<Mapbox.Tile>(stream);

Expand All @@ -53,11 +54,11 @@ public VectorTile Read(Stream stream, Tiles.Tile tileDefinition, string idAttrib
{
Debug.Assert(mbTileLayer.Version == 2U);

var tgs = new TileGeometryTransform(tileDefinition, mbTileLayer.Extent);
var tgt = tgtFactory == null ? new TileGeometryTransform(tileDefinition, mbTileLayer.Extent) : tgtFactory(tileDefinition, mbTileLayer.Extent);
var layer = new Layer {Name = mbTileLayer.Name};
foreach (var mbTileFeature in mbTileLayer.Features)
{
var feature = ReadFeature(tgs, mbTileLayer, mbTileFeature, idAttributeName);
var feature = ReadFeature(tgt, mbTileLayer, mbTileFeature, idAttributeName);
layer.Features.Add(feature);
}
vectorTile.Layers.Add(layer);
Expand All @@ -66,9 +67,9 @@ public VectorTile Read(Stream stream, Tiles.Tile tileDefinition, string idAttrib
return vectorTile;
}

private IFeature ReadFeature(TileGeometryTransform tgs, Tile.Layer mbTileLayer, Tile.Feature mbTileFeature, string idAttributeName)
private IFeature ReadFeature(ITileGeometryTransform tgt, Tile.Layer mbTileLayer, Tile.Feature mbTileFeature, string idAttributeName)
{
var geometry = ReadGeometry(tgs, mbTileFeature.Type, mbTileFeature.Geometry);
var geometry = ReadGeometry(tgt, mbTileFeature.Type, mbTileFeature.Geometry);
var attributes = ReadAttributeTable(mbTileFeature, mbTileLayer.Keys, mbTileLayer.Values);

//Check to see if an id value is already captured in the attributes, if not, add it.
Expand All @@ -81,41 +82,41 @@ private IFeature ReadFeature(TileGeometryTransform tgs, Tile.Layer mbTileLayer,
return new Feature(geometry, attributes);
}

private Geometry ReadGeometry(TileGeometryTransform tgs, Tile.GeomType type, IList<uint> geometry)
private Geometry ReadGeometry(ITileGeometryTransform tgt, Tile.GeomType type, IList<uint> geometry)
{
switch (type)
{
case Tile.GeomType.Point:
return ReadPoint(tgs, geometry);
return ReadPoint(tgt, geometry);

case Tile.GeomType.LineString:
return ReadLineString(tgs, geometry);
return ReadLineString(tgt, geometry);

case Tile.GeomType.Polygon:
return ReadPolygon(tgs, geometry);
return ReadPolygon(tgt, geometry);
}

return null;
}

private Geometry ReadPoint(TileGeometryTransform tgs, IList<uint> geometry)
private Geometry ReadPoint(ITileGeometryTransform tgt, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY, forPoint:true);
var sequences = ReadCoordinateSequences(tgt, geometry, ref currentIndex, ref currentX, ref currentY, forPoint:true);
return CreatePuntal(sequences);
}

private Geometry ReadLineString(TileGeometryTransform tgs, IList<uint> geometry)
private Geometry ReadLineString(ITileGeometryTransform tgt, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY);
var sequences = ReadCoordinateSequences(tgt, geometry, ref currentIndex, ref currentX, ref currentY);
return CreateLineal(sequences);
}

private Geometry ReadPolygon(TileGeometryTransform tgs, IList<uint> geometry)
private Geometry ReadPolygon(ITileGeometryTransform tgt, IList<uint> geometry)
{
int currentIndex = 0; int currentX = 0; int currentY = 0;
var sequences = ReadCoordinateSequences(tgs, geometry, ref currentIndex, ref currentX, ref currentY, 1);
var sequences = ReadCoordinateSequences(tgt, geometry, ref currentIndex, ref currentX, ref currentY, 1);
return CreatePolygonal(sequences);
}

Expand Down Expand Up @@ -204,15 +205,15 @@ private Geometry CreatePolygonal(CoordinateSequence[] sequences)
}

private CoordinateSequence[] ReadCoordinateSequences(
TileGeometryTransform tgs, IList<uint> geometry,
ITileGeometryTransform tgt, IList<uint> geometry,
ref int currentIndex, ref int currentX, ref int currentY, int buffer = 0, bool forPoint = false)
{
(var command, int count) = ParseCommandInteger(geometry[currentIndex]);
Debug.Assert(command == MapboxCommandType.MoveTo);
if (count > 1)
{
currentIndex++;
return ReadSinglePointSequences(tgs, geometry, count, ref currentIndex, ref currentX, ref currentY);
return ReadSinglePointSequences(tgt, geometry, count, ref currentIndex, ref currentX, ref currentY);
}

var sequences = new List<CoordinateSequence>();
Expand Down Expand Up @@ -240,13 +241,13 @@ private CoordinateSequence[] ReadCoordinateSequences(
// Create sequence, add starting point
var sequence = _factory.CoordinateSequenceFactory.Create(1 + count + buffer, 2);
int sequenceIndex = 0;
TransformOffsetAndAddToSequence(tgs, currentPosition, sequence, sequenceIndex++);
TransformOffsetAndAddToSequence(tgt, currentPosition, sequence, sequenceIndex++);

// Read and add offsets
for (int i = 1; i <= count; i++)
{
currentPosition = ParseOffset(currentPosition, geometry, ref currentIndex);
TransformOffsetAndAddToSequence(tgs, currentPosition, sequence, sequenceIndex++);
TransformOffsetAndAddToSequence(tgt, currentPosition, sequence, sequenceIndex++);
}

// Check for ClosePath command
Expand Down Expand Up @@ -276,7 +277,7 @@ private CoordinateSequence[] ReadCoordinateSequences(
return sequences.ToArray();
}

private CoordinateSequence[] ReadSinglePointSequences(TileGeometryTransform tgs, IList<uint> geometry,
private CoordinateSequence[] ReadSinglePointSequences(ITileGeometryTransform tgt, IList<uint> geometry,
int numSequences, ref int currentIndex, ref int currentX, ref int currentY)
{
var res = new CoordinateSequence[numSequences];
Expand All @@ -286,17 +287,17 @@ private CoordinateSequence[] ReadSinglePointSequences(TileGeometryTransform tgs,
res[i] = _factory.CoordinateSequenceFactory.Create(1, 2);

currentPosition = ParseOffset(currentPosition, geometry, ref currentIndex);
TransformOffsetAndAddToSequence(tgs, currentPosition, res[i], 0);
TransformOffsetAndAddToSequence(tgt, currentPosition, res[i], 0);
}

currentX = currentPosition.currentX;
currentY = currentPosition.currentY;
return res;
}

private void TransformOffsetAndAddToSequence(TileGeometryTransform tgs, (int x, int y) localPosition, CoordinateSequence sequence, int index)
private void TransformOffsetAndAddToSequence(ITileGeometryTransform tgt, (int x, int y) localPosition, CoordinateSequence sequence, int index)
{
var (longitude, latitude) = tgs.TransformInverse(localPosition.x, localPosition.y);
var (longitude, latitude) = tgt.TransformInverse(localPosition.x, localPosition.y);
sequence.SetOrdinate(index, Ordinate.X, longitude);
sequence.SetOrdinate(index, Ordinate.Y, latitude);
}
Expand Down
Loading
Loading