diff --git a/lib/aprs/db_test.ex b/lib/aprs/db_test.ex index 1ebacc4..bed5c91 100644 --- a/lib/aprs/db_test.ex +++ b/lib/aprs/db_test.ex @@ -117,7 +117,15 @@ defmodule Aprs.DbTest do IO.puts("Fetching #{limit} most recent packets...") try do - packets = Repo.all(from(p in Packet, where: p.has_position == true, order_by: [desc: p.received_at], limit: ^limit)) + packets = + Repo.all( + from(p in Packet, + where: p.has_position == true, + order_by: [desc: p.received_at], + limit: ^limit, + select: %{p | lat: fragment("ST_Y(?)", p.location), lon: fragment("ST_X(?)", p.location)} + ) + ) IO.puts("Found #{length(packets)} recent packets") diff --git a/lib/aprs/packets.ex b/lib/aprs/packets.ex index 4a9aa98..a0d652b 100644 --- a/lib/aprs/packets.ex +++ b/lib/aprs/packets.ex @@ -4,7 +4,6 @@ defmodule Aprs.Packets do """ import Ecto.Query, warn: false - # import Geo.PostGIS alias Aprs.Packet alias Aprs.Repo @@ -149,6 +148,7 @@ defmodule Aprs.Packets do |> filter_by_callsign(opts) |> filter_by_map_bounds(opts) |> limit_results(opts) + |> select_with_virtual_coordinates() Repo.all(query) end @@ -169,6 +169,12 @@ defmodule Aprs.Packets do Repo.one(query) end + # Adds a select clause to populate virtual lat/lon fields from PostGIS geometry. + defp select_with_virtual_coordinates(query) do + from p in query, + select: %{p | lat: fragment("ST_Y(?)", p.location), lon: fragment("ST_X(?)", p.location)} + end + # Query building helpers # Handle both start_time and end_time defp filter_by_time(query, %{start_time: start_time, end_time: end_time}) do @@ -267,10 +273,14 @@ defmodule Aprs.Packets do defp filter_by_map_bounds(query, %{bounds: [min_lon, min_lat, max_lon, max_lat]}) when not is_nil(min_lon) and not is_nil(min_lat) and not is_nil(max_lon) and not is_nil(max_lat) do + # Create a bounding box polygon for PostGIS spatial query + bbox_wkt = + "POLYGON((#{min_lon} #{min_lat}, #{max_lon} #{min_lat}, #{max_lon} #{max_lat}, #{min_lon} #{max_lat}, #{min_lon} #{min_lat}))" + from p in query, where: p.has_position == true, - where: p.lat >= ^min_lat and p.lat <= ^max_lat, - where: p.lon >= ^min_lon and p.lon <= ^max_lon + where: not is_nil(p.location), + where: fragment("ST_Within(?, ST_GeomFromText(?, 4326))", p.location, ^bbox_wkt) end defp filter_by_map_bounds(query, _), do: query @@ -358,6 +368,7 @@ defmodule Aprs.Packets do |> where([p], p.received_at >= ^one_hour_ago) |> order_by([p], asc: p.received_at) |> limit(500) + |> select_with_virtual_coordinates() |> Repo.all() end diff --git a/lib/aprs_web/live/map_live/enhanced.ex b/lib/aprs_web/live/map_live/enhanced.ex index ab858c8..3508c3a 100644 --- a/lib/aprs_web/live/map_live/enhanced.ex +++ b/lib/aprs_web/live/map_live/enhanced.ex @@ -390,16 +390,19 @@ defmodule AprsWeb.MapLive.Enhanced do defp fetch_packets_in_bounds(bounds, limit) do cutoff_time = DateTime.add(DateTime.utc_now(), -@packet_retention_minutes * 60, :second) + # Create a bounding box polygon for PostGIS spatial query + bbox_wkt = + "POLYGON((#{bounds["west"]} #{bounds["south"]}, #{bounds["east"]} #{bounds["south"]}, #{bounds["east"]} #{bounds["north"]}, #{bounds["west"]} #{bounds["north"]}, #{bounds["west"]} #{bounds["south"]}))" + Repo.all( from(p in Packet, where: p.has_position == true, where: p.received_at >= ^cutoff_time, - where: p.lat >= ^bounds["south"], - where: p.lat <= ^bounds["north"], - where: p.lon >= ^bounds["west"], - where: p.lon <= ^bounds["east"], + where: not is_nil(p.location), + where: fragment("ST_Within(?, ST_GeomFromText(?, 4326))", p.location, ^bbox_wkt), order_by: [desc: p.received_at], - limit: ^limit + limit: ^limit, + select: %{p | lat: fragment("ST_Y(?)", p.location), lon: fragment("ST_X(?)", p.location)} ) ) end @@ -561,17 +564,20 @@ defmodule AprsWeb.MapLive.Enhanced do end defp fetch_historical_packets(bounds, start_time, end_time) do + # Create a bounding box polygon for PostGIS spatial query + bbox_wkt = + "POLYGON((#{bounds["west"]} #{bounds["south"]}, #{bounds["east"]} #{bounds["south"]}, #{bounds["east"]} #{bounds["north"]}, #{bounds["west"]} #{bounds["north"]}, #{bounds["west"]} #{bounds["south"]}))" + Repo.all( from(p in Packet, where: p.has_position == true, where: p.received_at >= ^start_time, where: p.received_at <= ^end_time, - where: p.lat >= ^bounds["south"], - where: p.lat <= ^bounds["north"], - where: p.lon >= ^bounds["west"], - where: p.lon <= ^bounds["east"], + where: not is_nil(p.location), + where: fragment("ST_Within(?, ST_GeomFromText(?, 4326))", p.location, ^bbox_wkt), order_by: [asc: p.received_at], - limit: 1000 + limit: 1000, + select: %{p | lat: fragment("ST_Y(?)", p.location), lon: fragment("ST_X(?)", p.location)} ) ) end diff --git a/lib/aprs_web/live/map_live/index.ex b/lib/aprs_web/live/map_live/index.ex index c8a2bde..68dbe4b 100644 --- a/lib/aprs_web/live/map_live/index.ex +++ b/lib/aprs_web/live/map_live/index.ex @@ -60,7 +60,7 @@ defmodule AprsWeb.MapLive.Index do Endpoint.subscribe("aprs_messages") # Only do IP geolocation in non-test environments - if Mix.env() != :test do + if Application.get_env(:aprs, :disable_aprs_connection, false) != true do IO.puts("Socket is connected, attempting to get IP location") # Get IP-based location on initial load IO.puts("Connect info: #{inspect(socket.private[:connect_info])}")