DocsHex.pm

LocationSimulator

Simulate GPS location data for development and testing. Supports synthetic GPS generation and GPX track replay, built on a pluggable pipeline architecture inspired by Phoenix.

Installation

def deps do
[
{:location_simulator, "~> 1.0"}
]
end

Architecture

The library is built around three composable layers:

1. Pipeline DSL (Spark)

Declare your simulation pipeline at compile time using LocationSimulator.Pipeline:

defmodule MyApp.Simulation do
use LocationSimulator.Pipeline
source :synthetic, direction: :north, elevation: 50
plug LocationSimulator.Plug.Callback, callback: MyApp.GpsHandler
end

2. Plugs (LocationSimulator.Plug behaviour)

Each plug transforms a pipeline map (config, state, gps, halted, assigns).

Built-in plugs:

PlugPurpose
LocationSimulator.Plug.SyntheticGpsGenerates next synthetic GPS point (handles direction & elevation)
LocationSimulator.Plug.GpxReplayReplays the next GPX track point
LocationSimulator.Plug.CallbackDispatches events to your LocationSimulator.Event callback
LocationSimulator.Plug.SleepRate-limits between events

3. Callback (LocationSimulator.Event behaviour)

Your application code receives GPS events via a callback module implementing start/2, event/2, stop/2.

API call flow

sequenceDiagram
participant CallbackModule
participant PlugChain
participant Worker
participant Api
Api->>Worker: start(pipeline_mod, config)
Worker->>PlugChain: run per event
PlugChain->>PlugChain: SyntheticGps / GpxReplay
PlugChain->>PlugChain: Callback
PlugChain->>CallbackModule: event/2

Usage

Quick start (legacy API)

LocationSimulator.start()
defmodule MyApp.Tracker do
use LocationSimulator.Pipeline
source :synthetic, direction: :north, elevation: 100, elevation_way: :up
plug LocationSimulator.Plug.Callback, callback: MyApp.GpsHandler
end
LocationSimulator.start(MyApp.Tracker, worker: 3, event: 100, interval: 1_000)

With a plain config map (legacy API)

LocationSimulator.start(%{
worker: 3,
event: 100,
interval: 1000,
random_range: 0,
direction: :north,
elevation: 100,
callback: MyApp.GpsHandler
})

Source types

:synthetic

Generates fake GPS coordinates that travel in a given direction.

defmodule MyApp.Synthetic do
use LocationSimulator.Pipeline
source :synthetic, direction: :north_east, elevation: 50, elevation_way: :up
plug LocationSimulator.Plug.Callback, callback: MyApp.Handler
end

Supported directions: :north, :south, :east, :west, :north_east, :north_west, :south_east, :south_west, :random.

Elevation options: elevation_way: :up | :down | :no_up_down.

:gpx

Replays a GPX track file. Uses :gpx_time or a fixed interval between points.

defmodule MyApp.GpxReplay do
use LocationSimulator.Pipeline
source :gpx, gpx_file: "data/*.gpx"
plug LocationSimulator.Plug.Callback, callback: MyApp.Handler
end
LocationSimulator.start(MyApp.GpxReplay, worker: 3, interval: :gpx_time)

Writing a callback module

Implement LocationSimulator.Event:

defmodule MyApp.GpsHandler do
@behaviour LocationSimulator.Event
@impl true
def start(config, _state), do: {:ok, config}
@impl true
def event(config, %{gps: gps}) do
IO.inspect(gps, label: "GPS")
{:ok, config}
end
@impl true
def stop(config, state) do
IO.puts("Done: #{state.success} ok, #{state.failed} failed")
{:ok, config}
end
end

Writing a custom plug

Implement LocationSimulator.Plug:

defmodule MyApp.LoggingPlug do
@behaviour LocationSimulator.Plug
@impl true
def init(opts), do: opts
@impl true
def call(pipeline, _opts) do
IO.puts("GPS event: #{inspect(pipeline.gps)}")
pipeline
end
end
defmodule MyApp.Pipeline do
use LocationSimulator.Pipeline
source :synthetic, direction: :north
plug MyApp.LoggingPlug
plug LocationSimulator.Plug.Callback, callback: MyApp.Handler
end

Starting at a fixed position

source :synthetic, started_gps: {20.9599, 107.0666, 0}

Group stop

Workers can be grouped and stopped together:

LocationSimulator.start(MyApp.Pipeline, worker: 4, group_id: :my_group)
# later:
LocationSimulator.stop(:my_group)

Runtime config reference

KeyType / ValuesDefault
workerpos_integer()1
eventpos_integer() | :infinity1
intervalpos_integer() | :gpx_time100 ms
random_rangenon_neg_integer()10 ms jitter
directionsee source DSL:random
elevationinteger()0
elevation_way:up | :down | :no_up_down:no_up_down
callbackmodule implementing EventLocationSimulator.LoggerEvent
group_idany()nil
gpx_fileglob stringnil
started_gps{lat, lon, ele}nil

Example

mix deps.get
iex -S mix
iex(1)> LocationSimulator.start()