How the TeslaMate Geo-Fence Feature Creates Custom Locations: A Complete Technical Guide
The TeslaMate Geo-fence feature creates custom locations by validating circular boundary parameters through Ecto changesets, persisting them to PostgreSQL inside atomic transactions, and registering them for spatial queries using the within_geofence? macro.
The TeslaMate Geo-fence feature allows users to define named circular zones around specific coordinates to track vehicle presence, monitor charging sessions, and calculate costs. Understanding how this feature creates custom locations requires examining the Elixir LiveView interface, Ecto schema validation, and PostgreSQL spatial query implementation in the teslamate-org/teslamate repository.
Geo-Fence Schema and Data Structure
Every custom location begins as a %GeoFence{} struct defined in lib/teslamate/locations/geo_fence.ex. This Ecto schema maps to the geo_fences table and stores the spatial parameters required to define a circular boundary zone.
The schema captures:
- name: User-defined identifier for the location
- latitude and longitude: Center point coordinates in decimal degrees
- radius: Circular boundary distance in meters (defaults to 20)
- billing fields: Optional cost calculation parameters for charging sessions
# lib/teslamate/locations/geo_fence.ex
%GeoFence{
radius: 20,
latitude: lat,
longitude: lng
}
The Custom Location Creation Pipeline
Creating a custom location follows a three-stage pipeline involving the LiveView interface, changeset validation, and transactional persistence.
Step 1: LiveView Form Initialization
When users initiate geo-fence creation, the TeslaMateWeb.GeoFenceLive.Form module pre-populates a %GeoFence{} struct with either manually entered coordinates or the vehicle's last known position. The form assigns a default radius of 20 meters and maintains the struct in the socket assigns for state management.
# lib/teslamate_web/live/geofence_live/form.ex
%GeoFence{
radius: 20,
latitude: lat,
longitude: lng
}
Step 2: Changeset Validation
Upon form submission, the system validates input through Locations.change_geofence/2 in lib/teslamate/locations.ex. This function constructs an Ecto.Changeset that enforces required fields, validates radius constraints, and sanitizes the name field before permitting database insertion.
# Validation pipeline inside the LiveView
defp validate(params, %{assigns: assigns}) do
changeset = Locations.change_geofence(assigns.geofence, params)
with {:ok, geofence} <- Ecto.Changeset.apply_action(changeset, :update) do
{:ok, geofence, changeset}
end
end
Step 3: Transactional Persistence
The Locations.create_geofence/1 function wraps database operations in Repo.transaction to ensure atomicity. It inserts the geo-fence record using GeoFence.changeset/2, then immediately registers the fence with apply_geofence/1 to activate it for system-wide spatial queries.
# lib/teslamate/locations.ex
def create_geofence(attrs) do
Repo.transaction(fn ->
with {:ok, geofence} <- %GeoFence{} |> GeoFence.changeset(attrs) |> Repo.insert(),
:ok <- apply_geofence(geofence) do
geofence
else
{:error, reason} -> Repo.rollback(reason)
end
end)
end
Spatial Querying with Custom Locations
Once persisted, custom locations function as spatial predicates through the within_geofence? macro in lib/teslamate/custom_expressions.ex. This macro generates PostgreSQL earth distance calculations using earth_box and earth_distance functions to determine if vehicle coordinates fall within a fence's radius.
# lib/teslamate/custom_expressions.ex
defmacro within_geofence?(position, geofence, :right) do
quote do
fragment(
"""
earth_box(ll_to_earth(?::numeric, ?::numeric), ?) @> ll_to_earth(?::numeric, ?::numeric) AND
earth_distance(ll_to_earth(?::numeric, ?::numeric), ll_to_earth(?::numeric, ?::numeric)) < ?
""",
^unquote(geofence).latitude,
^unquote(geofence).longitude,
^unquote(geofence).radius,
unquote(position).latitude,
unquote(position).longitude,
^unquote(geofence).latitude,
^unquote(geofence).longitude,
unquote(position).latitude,
unquote(position).longitude,
^unquote(geofence).radius
)
end
end
Complete LiveView Save Handler Implementation
The following implementation from lib/teslamate_web/live/geofence_live/form.ex demonstrates the complete event handling flow when users save a new custom location:
def handle_event("save", %{"geo_fence" => params}, socket) do
with {:ok, geofence, changeset} <- validate(params, socket),
{:ok, socket} <- show_modal_or_save(geofence, changeset, socket) do
{:noreply, socket}
else
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, changeset: changeset, show_errors: true)}
end
end
defp validate(params, %{assigns: assigns}) do
changeset = Locations.change_geofence(assigns.geofence, params)
with {:ok, geofence} <- Ecto.Changeset.apply_action(changeset, :update) do
{:ok, geofence, changeset}
end
end
defp save(%{assigns: %{action: :new, geofence: _}} = socket) do
Locations.create_geofence(socket.assigns.changeset.params)
end
Summary
- The Geo-fence feature creates custom locations using a circular boundary model defined by latitude, longitude, and radius parameters stored in the
geo_fencestable. - LiveView forms in
lib/teslamate_web/live/geofence_live/form.exhandle UI state and coordinate pre-population, defaulting to a 20-meter radius for new locations. - Ecto changesets via
Locations.change_geofence/2validate all inputs before database insertion, ensuring data integrity for spatial calculations. - Transactional safety in
Locations.create_geofence/1guarantees that geo-fences are only activated after successful database persistence and registration. - The
within_geofence?macro enables efficient spatial queries using PostgreSQL's earth distance functions to match vehicle positions against custom locations without loading entire tables into memory.
Frequently Asked Questions
What database does TeslaMate use for storing geo-fence custom locations?
TeslaMate uses PostgreSQL with spatial extensions to store geo-fence data. The within_geofence? macro leverages PostgreSQL-specific functions including earth_box, earth_distance, and ll_to_earth to perform efficient spherical distance calculations on the database server, minimizing application memory usage when checking vehicle positions against multiple custom locations.
Can geo-fences overlap, and how does TeslaMate handle multiple custom locations at the same coordinates?
Yes, overlapping geo-fences are permitted. The system treats each custom location as an independent record in the geo_fences table. When querying vehicle positions, the application checks against all active fences using the within_geofence? predicate, allowing a single coordinate to match multiple custom locations simultaneously for different tracking purposes such as home charging and workplace monitoring.
What is the default radius when creating a new geo-fence in TeslaMate?
The default radius is 20 meters. This value is hardcoded in the TeslaMateWeb.GeoFenceLive.Form module when initializing a new %GeoFence{} struct. Users can modify this value through the web interface before saving the custom location to adjust the detection boundary for their specific use case, such as expanding coverage for large parking structures or reducing it for precise driveway detection.
How does TeslaMate validate geo-fence data before saving?
Validation occurs through Ecto.Changeset operations in Locations.change_geofence/2 defined in lib/teslamate/locations.ex. The changeset validates required fields (name, latitude, longitude, radius), enforces radius limits, and sanitizes the name field. Invalid submissions return error tuples that the LiveView handles by re-rendering the form with validation errors displayed to the user, preventing malformed data from reaching the database.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →