Use PostGIS GiST index for spatial bounds queries

This commit is contained in:
Graham McIntire 2026-02-20 07:26:29 -06:00
parent 6c2b664333
commit bcb3556bf6
No known key found for this signature in database
2 changed files with 49 additions and 37 deletions

View file

@ -121,17 +121,18 @@ defmodule Aprsme.Packets.QueryBuilder do
@spec within_bounds(Ecto.Query.t(), list(number())) :: Ecto.Query.t()
def within_bounds(query, [west, south, east, north])
when is_number(west) and is_number(south) and is_number(east) and is_number(north) do
# Handle antimeridian crossing (e.g., west=170, east=-170)
# Use ST_MakeEnvelope with && operator to leverage GiST spatial index on location column
if west > east do
# Handle antimeridian crossing with two envelopes: [west, 180] and [-180, east]
from p in query,
where: p.has_position == true,
where: p.lat >= ^south and p.lat <= ^north,
where: p.lon >= ^west or p.lon <= ^east
where:
fragment("? && ST_MakeEnvelope(?, ?, 180, ?, 4326)", p.location, ^west, ^south, ^north) or
fragment("? && ST_MakeEnvelope(-180, ?, ?, ?, 4326)", p.location, ^south, ^east, ^north)
else
from p in query,
where: p.has_position == true,
where: p.lat >= ^south and p.lat <= ^north,
where: p.lon >= ^west and p.lon <= ^east
where: fragment("? && ST_MakeEnvelope(?, ?, ?, ?, 4326)", p.location, ^west, ^south, ^east, ^north)
end
end

View file

@ -5,26 +5,22 @@ defmodule Aprsme.Packets.QueryBuilderTest do
alias Aprsme.Packet
alias Aprsme.Packets.QueryBuilder
alias Aprsme.PacketsFixtures
describe "within_bounds/2" do
test "filters packets within normal bounds" do
# Create test packets
{:ok, inside} =
Aprsme.Repo.insert(%Packet{
inside =
PacketsFixtures.packet_fixture(%{
sender: "TEST-1",
lat: 40.5,
lon: -73.5,
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second)
lat: Decimal.new("40.5"),
lon: Decimal.new("-73.5")
})
{:ok, _outside} =
Aprsme.Repo.insert(%Packet{
_outside =
PacketsFixtures.packet_fixture(%{
sender: "TEST-2",
lat: 50.0,
lon: -73.5,
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second)
lat: Decimal.new("50.0"),
lon: Decimal.new("-73.5")
})
# Test normal bounds [west, south, east, north]
@ -36,32 +32,25 @@ defmodule Aprsme.Packets.QueryBuilderTest do
end
test "handles antimeridian crossing bounds" do
# Create test packets
{:ok, west_side} =
Aprsme.Repo.insert(%Packet{
west_side =
PacketsFixtures.packet_fixture(%{
sender: "WEST-1",
lat: 0.0,
lon: 175.0,
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second)
lat: Decimal.new("0.0"),
lon: Decimal.new("175.0")
})
{:ok, east_side} =
Aprsme.Repo.insert(%Packet{
east_side =
PacketsFixtures.packet_fixture(%{
sender: "EAST-1",
lat: 0.0,
lon: -175.0,
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second)
lat: Decimal.new("0.0"),
lon: Decimal.new("-175.0")
})
{:ok, _middle} =
Aprsme.Repo.insert(%Packet{
_middle =
PacketsFixtures.packet_fixture(%{
sender: "MIDDLE-1",
lat: 0.0,
lon: 0.0,
has_position: true,
received_at: DateTime.truncate(DateTime.utc_now(), :second)
lat: Decimal.new("0.0"),
lon: Decimal.new("0.0")
})
# Test antimeridian crossing bounds [west=170, south=-10, east=-170, north=10]
@ -87,4 +76,26 @@ defmodule Aprsme.Packets.QueryBuilderTest do
assert QueryBuilder.within_bounds(original_query, ["a", "b", "c", "d"]) == original_query
end
end
describe "weather_only/1" do
test "filters to weather packets using has_weather column" do
_weather =
PacketsFixtures.packet_fixture(%{
sender: "WX-QBT",
temperature: 72.0,
has_weather: true
})
_non_weather =
PacketsFixtures.packet_fixture(%{
sender: "NOWX-QBT",
comment: "No weather"
})
query = QueryBuilder.weather_only(Packet)
results = Aprsme.Repo.all(query)
assert Enum.all?(results, fn p -> p.has_weather == true end)
end
end
end