Packages

Fast polyline encoding/decoding powered by Rust

Current section

Files

Jump to
rolyrine lib rolyrine.ex
Raw

lib/rolyrine.ex

defmodule Rolyrine do
@moduledoc """
Fast polyline encoding/decoding using Rust NIFs.
Implements Google's Polyline Algorithm Format with flexible coordinate formats.
## Supported Coordinate Formats
| Format | Example |
|--------|---------|
| `{lon, lat}` tuples | `[{-120.2, 38.5}, ...]` |
| `%{longitude:, latitude:}` | `[%{longitude: -120.2, latitude: 38.5}, ...]` |
| `%{lon:, lat:}` | `[%{lon: -120.2, lat: 38.5}, ...]` |
| `%{lng:, lat:}` | `[%{lng: -120.2, lat: 38.5}, ...]` |
| `%{x:, y:}` | `[%{x: -120.2, y: 38.5}, ...]` |
## Examples
iex> Rolyrine.encode([{-120.2, 38.5}, {-120.95, 40.7}])
"_p~iF~ps|U_ulLnnqC"
iex> Rolyrine.decode("_p~iF~ps|U_ulLnnqC")
[{-120.2, 38.5}, {-120.95, 40.7}]
iex> Rolyrine.decode("_p~iF~ps|U_ulLnnqC", format: :longitude_latitude)
[%{longitude: -120.2, latitude: 38.5}, %{longitude: -120.95, latitude: 40.7}]
"""
@version "0.1.3"
use RustlerPrecompiled,
otp_app: :rolyrine,
crate: "rolyrine",
base_url: "https://github.com/sephianl/rolyrine/releases/download/v#{@version}",
force_build: System.get_env("ROLYRINE_BUILD") in ["1", "true"],
version: @version,
targets: [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
"x86_64-pc-windows-gnu"
],
nif_versions: ["2.15", "2.16", "2.17"]
@default_precision 5
# ============================================================================
# DECODE
# ============================================================================
@doc """
Decode a polyline string into a list of coordinates.
## Options
* `:precision` - decimal precision (default: 5)
* `:format` - output format (default: `:tuple`)
- `:tuple` - `[{lon, lat}, ...]`
- `:tuple_reversed` - `[{lat, lon}, ...]`
- `:longitude_latitude` - `[%{longitude: lon, latitude: lat}, ...]`
- `:lon_lat` - `[%{lon: lon, lat: lat}, ...]`
- `:lng_lat` - `[%{lng: lon, lat: lat}, ...]`
- `:xy` - `[%{x: lon, y: lat}, ...]`
## Examples
iex> Rolyrine.decode("_p~iF~ps|U_ulLnnqC")
[{-120.2, 38.5}, {-120.95, 40.7}]
iex> Rolyrine.decode("_p~iF~ps|U_ulLnnqC", format: :lon_lat)
[%{lon: -120.2, lat: 38.5}, %{lon: -120.95, lat: 40.7}]
iex> Rolyrine.decode("_izlhA~rlgdF_{geC~ywl@", precision: 6)
[{-120.2, 38.5}, {-120.95, 40.7}]
"""
def decode(polyline, opts \\ [])
def decode("", _opts), do: []
def decode(polyline, opts) do
precision = Keyword.get(opts, :precision, @default_precision)
format = Keyword.get(opts, :format, :tuple)
case decode_nif(polyline, precision) do
{:error, _} = error -> error
coords -> to_format(coords, format)
end
end
defp to_format({:error, _} = error, _format), do: error
defp to_format(coords, :tuple), do: coords
defp to_format(coords, :tuple_reversed) do
Enum.map(coords, fn {lon, lat} -> {lat, lon} end)
end
defp to_format(coords, :longitude_latitude) do
Enum.map(coords, fn {lon, lat} -> %{longitude: lon, latitude: lat} end)
end
defp to_format(coords, :lon_lat) do
Enum.map(coords, fn {lon, lat} -> %{lon: lon, lat: lat} end)
end
defp to_format(coords, :lng_lat) do
Enum.map(coords, fn {lon, lat} -> %{lng: lon, lat: lat} end)
end
defp to_format(coords, :xy) do
Enum.map(coords, fn {lon, lat} -> %{x: lon, y: lat} end)
end
# ============================================================================
# ENCODE
# ============================================================================
@doc """
Encode a list of coordinates into a polyline string.
Accepts multiple coordinate formats - auto-detected from first element.
## Options
* `:precision` - decimal precision (default: 5)
## Examples
iex> Rolyrine.encode([{-120.2, 38.5}, {-120.95, 40.7}])
"_p~iF~ps|U_ulLnnqC"
iex> Rolyrine.encode([%{longitude: -120.2, latitude: 38.5}, %{longitude: -120.95, latitude: 40.7}])
"_p~iF~ps|U_ulLnnqC"
iex> Rolyrine.encode([{-120.2, 38.5}, {-120.95, 40.7}], precision: 6)
"_izlhA~rlgdF_{geC~ywl@"
"""
def encode(coords, opts \\ [])
def encode([], _opts), do: ""
def encode([{_lon, _lat} | _] = coords, opts) do
encode_tuples(coords, opts)
end
def encode([%{longitude: _, latitude: _} | _] = coords, opts) do
coords
|> Enum.map(fn %{longitude: lon, latitude: lat} -> {lon, lat} end)
|> encode_tuples(opts)
end
def encode([%{lon: _, lat: _} | _] = coords, opts) do
coords
|> Enum.map(fn %{lon: lon, lat: lat} -> {lon, lat} end)
|> encode_tuples(opts)
end
def encode([%{lng: _, lat: _} | _] = coords, opts) do
coords
|> Enum.map(fn %{lng: lon, lat: lat} -> {lon, lat} end)
|> encode_tuples(opts)
end
def encode([%{x: _, y: _} | _] = coords, opts) do
coords
|> Enum.map(fn %{x: lon, y: lat} -> {lon, lat} end)
|> encode_tuples(opts)
end
defp encode_tuples(coords, opts) do
precision = Keyword.get(opts, :precision, @default_precision)
encode_nif(coords, precision)
end
# ============================================================================
# NIF STUBS
# ============================================================================
defp decode_nif(_polyline, _precision), do: :erlang.nif_error(:nif_not_loaded)
defp encode_nif(_coords, _precision), do: :erlang.nif_error(:nif_not_loaded)
end