Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.18.4-otp-28
erlang 28.0.2
elixir 1.19.3-otp-28
erlang 28.2
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ layer = %Layer{
# Add a feature with attributes
layer = Layer.add_feature(
layer,
%Feature{
type: :POINT,
# "Raw" geometry specification as per https://github.com/mapbox/vector-tile-spec/tree/master/2.1#43-geometry-encoding
geometry: [9, 128, 128]
},
Feature.point([128, 128]),
count: 42,
size: "large"
)
Expand Down
95 changes: 95 additions & 0 deletions lib/vector_tile/coordinates.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
defmodule VectorTile.Coordinates do
@moduledoc """
Functions for working with coordinates in vector tiles, including interpolation into a tile's internal coordinate
system and zigzag encoding of parameter integers.
"""

@doc """
Interpolates a point `[x, y]` to a [tile's internal coordinate
system](https://github.com/mapbox/vector-tile-spec/tree/master/2.1#43-geometry-encoding) based on bounding box and
extent.

You can call this function with a `VectorTile.Tile` that has been initialized with a bounding box and an extent, or
provide a keyword list with these keys:

- `:bbox` - list of four numbers representing `[west, south, east, north]`
- `:extent` - integer representing the tile's extent (default is 4096)

## Example

iex> tile = VectorTile.Tile.new(bbox: [10, 0, 20, 10], extent: 4096)
iex> interpolate([15, 10], tile)
[2048, 0]
# x = 2048, because it's halfway between the east and west edges
# y = 0, because it's on the north edge of the tile's bounding box

"""
@spec interpolate(coordinate :: list(integer()), tile :: VectorTile.Tile.t() | Keyword.t()) ::
list(integer())
def interpolate([x, y] = _coordinate, opts) when is_number(x) and is_number(y) do
bbox = Access.fetch!(opts, :bbox)
extent = Access.get(opts, :extent, 4096)

[
interpolate_x(bbox, extent, x),
interpolate_y(bbox, extent, y)
]
end

defp interpolate_x([tile_w, _tile_s, tile_e, _tile_n], extent, x) do
if tile_e == tile_w,
do: 0,
else: ((x - tile_w) / (tile_e - tile_w) * extent) |> floor()
end

defp interpolate_y([_tile_w, tile_s, _tile_e, tile_n], extent, y) do
if tile_n == tile_s,
do: 0,
else: ((tile_n - y) / (tile_n - tile_s) * extent) |> floor()
end

@neg_max -1 * 2 ** 31
@pos_max 2 ** 31

@doc """
Zigzag encodes an
[*ParameterInteger*](https://github.com/mapbox/vector-tile-spec/tree/master/2.1#432-parameter-integers).

Supports values greater than -2^31 and less than 2^31.

## Example

iex> Coordinates.zigzag(0)
0

iex> Coordinates.zigzag(1)
2

iex> Coordinates.zigzag(-1)
1

iex> Coordinates.zigzag(2)
4

iex> Coordinates.zigzag(-2)
3

iex> Coordinates.zigzag(2 ** 31 - 1)
4294967294

iex> Coordinates.zigzag(-1 * (2 ** 31 - 1))
4294967293

iex> Coordinates.zigzag(2 ** 31)
** (FunctionClauseError) no function clause matching in VectorTile.Coordinates.zigzag/1

iex> Coordinates.zigzag(-1 * 2 ** 31)
** (FunctionClauseError) no function clause matching in VectorTile.Coordinates.zigzag/1
"""
@spec zigzag(integer()) :: integer()
def zigzag(value) when is_integer(value) and @neg_max < value and value < @pos_max do
import Bitwise

Bitwise.bxor(value <<< 1, value >>> 31)
end
end
Loading