Skip to content

Commit

Permalink
Added calculation to get tiles below line.
Browse files Browse the repository at this point in the history
  • Loading branch information
xivk committed Sep 22, 2023
1 parent 23c97ba commit 62668ae
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 64 deletions.
9 changes: 9 additions & 0 deletions TilesMath.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "root", "root", "{70F90716-4
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7F819ADD-9958-44B5-AA0E-1812BB9C90C8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TilesMath.Tests", "test\TilesMath.Tests\TilesMath.Tests.csproj", "{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -26,8 +30,13 @@ Global
{0D1D5A4A-C7E1-4B92-87DA-E5FC8F544904}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D1D5A4A-C7E1-4B92-87DA-E5FC8F544904}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D1D5A4A-C7E1-4B92-87DA-E5FC8F544904}.Release|Any CPU.Build.0 = Release|Any CPU
{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0D1D5A4A-C7E1-4B92-87DA-E5FC8F544904} = {61289184-772B-4826-B7A0-1249F00A7FDD}
{7FDDFC0D-2970-48E1-9EF5-0879EE73F6AB} = {7F819ADD-9958-44B5-AA0E-1812BB9C90C8}
EndGlobalSection
EndGlobal
79 changes: 16 additions & 63 deletions src/TilesMath/Tile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace TilesMath;
/// <summary>
/// Represents a tile.
/// </summary>
public readonly struct Tile
public readonly partial struct Tile
{
private Tile(int x, int y, byte zoom)
{
Expand Down Expand Up @@ -100,85 +100,38 @@ public IEnumerable<Tile> ChildrenAtZoom(int zoom)
}
}

/// <summary>
/// Creates a new tile from x-y coordinate and zoom level.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
public static Tile Create(int x, int y, int zoom)
public bool Equals(Tile other)
{
return new Tile(x, y, (byte)zoom);
return this.X == other.X && this.Y == other.Y && this.Zoom == other.Zoom;
}

/// <summary>
/// Creates a new tile from its global id.
/// </summary>
/// <param name="globalId">The global id.</param>
/// <returns>The tile equivalent to the global id.</returns>
public static Tile FromGlobalId(long globalId)
public override bool Equals(object? obj)
{
var (x, y, z) = GlobalTileId.From(globalId);

return new Tile(x, y, (byte)z);
}

/// <summary>
/// Creates a new tile from its local id.
/// </summary>
/// <param name="localId">The local id.</param>
/// <param name="zoom"></param>
/// <returns>The tile equivalent to for the local id at the given zoom level.</returns>
public static Tile FromLocalId(int localId, int zoom)
{
var (x, y) = LocalTileId.From(localId, zoom);

return new Tile(x, y, (byte)zoom);
return obj is Tile other && this.Equals(other);
}

/// <summary>
/// Calculates the maximum local id for the given zoom level.
/// </summary>
/// <param name="zoom">The zoom level.</param>
/// <returns>The maximum local id for the given zoom level</returns>
public static int MaxLocalId(int zoom)
public override int GetHashCode()
{
return LocalTileId.Max(zoom);
return HashCode.Combine(this.X, this.Y, this.Zoom);
}

/// <summary>
/// Creates the tile at the given WGS84 coordinates and zoom level.
/// </summary>
/// <param name="longitude">The longitude.</param>
/// <param name="latitude">The latitude.</param>
/// <param name="zoom">The zoom-level.</param>
/// <returns>The tile at the given location and zoom level.</returns>
public static Tile AtLocation(double longitude, double latitude, int zoom)
public static bool operator ==(Tile left, Tile right)
{
var (x, y) = TileGeo.ForLocation(longitude, latitude, zoom);

return new Tile(x, y, (byte)zoom);
return left.Equals(right);
}

/// <summary>
/// Creates the tile at the given WGS84 coordinates and zoom level.
/// </summary>
/// <param name="longitude">The longitude.</param>
/// <param name="latitude">The latitude.</param>
/// <param name="zoom">The zoom-level.</param>
/// <returns>The tile at the given location and zoom level.</returns>
public static Tile? TryAtLocation(double longitude, double latitude, int zoom)
public static bool operator !=(Tile left, Tile right)
{
var result = TileGeo.TryForLocation(longitude, latitude, zoom);
if (result == null) return null;

var (x, y) = result.Value;
return new Tile(x, y, (byte)zoom);
return !(left == right);
}

/// <summary>
/// Creates an empty tile.
/// </summary>
public static Tile Empty = new Tile(-1, -1, 0);

public override string ToString()
{
return $"{nameof(this.X)}: {this.X}, {nameof(this.Y)}: {this.Y}, {nameof(this.Zoom)}: {this.Zoom}, {nameof(this.LocalId)}: {this.LocalId}, {nameof(this.GlobalId)}: {this.GlobalId}";
}
}
141 changes: 141 additions & 0 deletions src/TilesMath/TileExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,145 @@ IEnumerable<int> EnumerateX()
yield return Tile.Create(x, y, topLeft.Zoom);
}
}

internal static IEnumerable<Tile> EnumerableTilesForLine(IEnumerable<(double longitude, double latitude)> line,
int zoom)
{
using var enumerator = line.GetEnumerator();
var hasNext = enumerator.MoveNext();
if (!hasNext) yield break;
var point1 = enumerator.Current;
hasNext = enumerator.MoveNext();

Tile? previous = null;
while (hasNext)
{
var segmentTiles = EnumerateTilesForLineSegment(point1, enumerator.Current, zoom);
foreach (var segmentTile in segmentTiles)
{
if (previous == segmentTile) continue;

previous = segmentTile;
yield return segmentTile;
}

point1 = enumerator.Current;
hasNext = enumerator.MoveNext();
}
}

private static IEnumerable<Tile> EnumerateTilesForLineSegment((double longitude, double latitude) point1,
(double longitude, double latitude) point2, int zoom)
{
var point1Tile = Tile.TryAtLocation(point1.longitude, point1.latitude, zoom);
if (point1Tile == null) yield break;
var point2Tile = Tile.TryAtLocation(point2.longitude, point2.latitude, zoom);
if (point2Tile == null) yield break;

// line equation: y = ax + b
// a = slope
// b = start

var a = (point2.latitude - point1.latitude) / (point2.longitude - point1.longitude);
var b = 0.0;

var isMoreVertical = a > 0.5;
if (isMoreVertical)
{
a = (point2.longitude - point1.longitude) / (point2.latitude - point1.latitude);
b = point1.longitude - (a * point1.latitude);
}
else
{
b = point1.latitude - (a * point1.longitude);
}

var tileX = point1Tile.Value.X;
var tileY = point1Tile.Value.Y;
while (tileX != point2Tile.Value.X ||
tileY != point2Tile.Value.Y)
{
var tile = Tile.Create(tileX, tileY, zoom);
yield return tile;

// determine to change x or y.

// if y at the next x is inside the current tile range (miny, maxy), we step x.
if (tileX != point2Tile.Value.X)
{
if (point2Tile.Value.X > tileX)
{
// try to move right.
var nextLongitude = tile.Boundaries.Right;
var latitude = GetY(nextLongitude);

if (latitude <= tile.Boundaries.Top &&
latitude >= tile.Boundaries.Bottom)
{
tileX += 1;
continue;
}
}
if (point2Tile.Value.X < tileX)
{
// try to move left.
var nextLongitude = tile.Boundaries.Left;
var latitude = GetY(nextLongitude);

if (latitude <= tile.Boundaries.Top &&
latitude >= tile.Boundaries.Bottom)
{
tileX -= 1;
continue;
}
}
}

// if x at the next y is inside the current tile range (minx, maxx), we step y.
if (tileY != point2Tile.Value.Y)
{
if (point2Tile.Value.Y > tileY)
{
// try to move down.
var nextLatitude = tile.Boundaries.Bottom;
var longitude = GetX(nextLatitude);

if (longitude >= tile.Boundaries.Left &&
longitude <= tile.Boundaries.Right)
{
tileY += 1;
continue;
}
}
if (point2Tile.Value.Y < tileY)
{
// try to move up.
var nextLatitude = tile.Boundaries.Top;
var longitude = GetX(nextLatitude);

if (longitude >= tile.Boundaries.Left &&
longitude <= tile.Boundaries.Right)
{
tileY -= 1;
continue;
}
}
}
}

yield return point2Tile.Value;
yield break;

double GetX(double y)
{
if (isMoreVertical) return a * y + b;
return (y - b) / a;
}

double GetY(double x)
{
if (isMoreVertical) return (x - b) / a;
return a * x + b;
}
}
}
92 changes: 92 additions & 0 deletions src/TilesMath/TileStatic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
namespace TilesMath;

public readonly partial struct Tile
{
/// <summary>
/// Creates a new tile from x-y coordinate and zoom level.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="zoom"></param>
/// <returns></returns>
public static Tile Create(int x, int y, int zoom)
{
return new Tile(x, y, (byte)zoom);
}

/// <summary>
/// Creates a new tile from its global id.
/// </summary>
/// <param name="globalId">The global id.</param>
/// <returns>The tile equivalent to the global id.</returns>
public static Tile FromGlobalId(long globalId)
{
var (x, y, z) = GlobalTileId.From(globalId);

return new Tile(x, y, (byte)z);
}

/// <summary>
/// Creates a new tile from its local id.
/// </summary>
/// <param name="localId">The local id.</param>
/// <param name="zoom"></param>
/// <returns>The tile equivalent to for the local id at the given zoom level.</returns>
public static Tile FromLocalId(int localId, int zoom)
{
var (x, y) = LocalTileId.From(localId, zoom);

return new Tile(x, y, (byte)zoom);
}

/// <summary>
/// Calculates the maximum local id for the given zoom level.
/// </summary>
/// <param name="zoom">The zoom level.</param>
/// <returns>The maximum local id for the given zoom level</returns>
public static int MaxLocalId(int zoom)
{
return LocalTileId.Max(zoom);
}

/// <summary>
/// Creates the tile at the given WGS84 coordinates and zoom level.
/// </summary>
/// <param name="longitude">The longitude.</param>
/// <param name="latitude">The latitude.</param>
/// <param name="zoom">The zoom-level.</param>
/// <returns>The tile at the given location and zoom level.</returns>
public static Tile AtLocation(double longitude, double latitude, int zoom)
{
var (x, y) = TileGeo.ForLocation(longitude, latitude, zoom);

return new Tile(x, y, (byte)zoom);
}

/// <summary>
/// Creates the tile at the given WGS84 coordinates and zoom level.
/// </summary>
/// <param name="longitude">The longitude.</param>
/// <param name="latitude">The latitude.</param>
/// <param name="zoom">The zoom-level.</param>
/// <returns>The tile at the given location and zoom level.</returns>
public static Tile? TryAtLocation(double longitude, double latitude, int zoom)
{
var result = TileGeo.TryForLocation(longitude, latitude, zoom);
if (result == null) return null;

var (x, y) = result.Value;
return new Tile(x, y, (byte)zoom);
}

/// <summary>
/// Enumerates all the tiles below the line represented by the given coordinate sequence.
/// </summary>
/// <param name="line">The line.</param>
/// <param name="zoom">The zoom level.</param>
/// <returns>The tiles.</returns>
public static IEnumerable<Tile> BelowLine(IEnumerable<(double longitude, double latitude)> line, int zoom)
{
return TileExtensions.EnumerableTilesForLine(line, zoom);
}
}
2 changes: 1 addition & 1 deletion src/TilesMath/TilesMath.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageVersion>0.0.4</PackageVersion>
<PackageVersion>0.0.5</PackageVersion>
<Title>TilesMath</Title>
<Authors>ANYWAYS BV</Authors>
<Description>A tiny library for tiles math.</Description>
Expand Down
Loading

0 comments on commit 62668ae

Please sign in to comment.