Use PostGIS GiST index for spatial bounds queries
This commit is contained in:
parent
6c2b664333
commit
bcb3556bf6
2 changed files with 49 additions and 37 deletions
|
|
@ -121,17 +121,18 @@ defmodule Aprsme.Packets.QueryBuilder do
|
||||||
@spec within_bounds(Ecto.Query.t(), list(number())) :: Ecto.Query.t()
|
@spec within_bounds(Ecto.Query.t(), list(number())) :: Ecto.Query.t()
|
||||||
def within_bounds(query, [west, south, east, north])
|
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
|
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
|
if west > east do
|
||||||
|
# Handle antimeridian crossing with two envelopes: [west, 180] and [-180, east]
|
||||||
from p in query,
|
from p in query,
|
||||||
where: p.has_position == true,
|
where: p.has_position == true,
|
||||||
where: p.lat >= ^south and p.lat <= ^north,
|
where:
|
||||||
where: p.lon >= ^west or p.lon <= ^east
|
fragment("? && ST_MakeEnvelope(?, ?, 180, ?, 4326)", p.location, ^west, ^south, ^north) or
|
||||||
|
fragment("? && ST_MakeEnvelope(-180, ?, ?, ?, 4326)", p.location, ^south, ^east, ^north)
|
||||||
else
|
else
|
||||||
from p in query,
|
from p in query,
|
||||||
where: p.has_position == true,
|
where: p.has_position == true,
|
||||||
where: p.lat >= ^south and p.lat <= ^north,
|
where: fragment("? && ST_MakeEnvelope(?, ?, ?, ?, 4326)", p.location, ^west, ^south, ^east, ^north)
|
||||||
where: p.lon >= ^west and p.lon <= ^east
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,22 @@ defmodule Aprsme.Packets.QueryBuilderTest do
|
||||||
|
|
||||||
alias Aprsme.Packet
|
alias Aprsme.Packet
|
||||||
alias Aprsme.Packets.QueryBuilder
|
alias Aprsme.Packets.QueryBuilder
|
||||||
|
alias Aprsme.PacketsFixtures
|
||||||
|
|
||||||
describe "within_bounds/2" do
|
describe "within_bounds/2" do
|
||||||
test "filters packets within normal bounds" do
|
test "filters packets within normal bounds" do
|
||||||
# Create test packets
|
inside =
|
||||||
{:ok, inside} =
|
PacketsFixtures.packet_fixture(%{
|
||||||
Aprsme.Repo.insert(%Packet{
|
|
||||||
sender: "TEST-1",
|
sender: "TEST-1",
|
||||||
lat: 40.5,
|
lat: Decimal.new("40.5"),
|
||||||
lon: -73.5,
|
lon: Decimal.new("-73.5")
|
||||||
has_position: true,
|
|
||||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, _outside} =
|
_outside =
|
||||||
Aprsme.Repo.insert(%Packet{
|
PacketsFixtures.packet_fixture(%{
|
||||||
sender: "TEST-2",
|
sender: "TEST-2",
|
||||||
lat: 50.0,
|
lat: Decimal.new("50.0"),
|
||||||
lon: -73.5,
|
lon: Decimal.new("-73.5")
|
||||||
has_position: true,
|
|
||||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Test normal bounds [west, south, east, north]
|
# Test normal bounds [west, south, east, north]
|
||||||
|
|
@ -36,32 +32,25 @@ defmodule Aprsme.Packets.QueryBuilderTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles antimeridian crossing bounds" do
|
test "handles antimeridian crossing bounds" do
|
||||||
# Create test packets
|
west_side =
|
||||||
{:ok, west_side} =
|
PacketsFixtures.packet_fixture(%{
|
||||||
Aprsme.Repo.insert(%Packet{
|
|
||||||
sender: "WEST-1",
|
sender: "WEST-1",
|
||||||
lat: 0.0,
|
lat: Decimal.new("0.0"),
|
||||||
lon: 175.0,
|
lon: Decimal.new("175.0")
|
||||||
has_position: true,
|
|
||||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, east_side} =
|
east_side =
|
||||||
Aprsme.Repo.insert(%Packet{
|
PacketsFixtures.packet_fixture(%{
|
||||||
sender: "EAST-1",
|
sender: "EAST-1",
|
||||||
lat: 0.0,
|
lat: Decimal.new("0.0"),
|
||||||
lon: -175.0,
|
lon: Decimal.new("-175.0")
|
||||||
has_position: true,
|
|
||||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
{:ok, _middle} =
|
_middle =
|
||||||
Aprsme.Repo.insert(%Packet{
|
PacketsFixtures.packet_fixture(%{
|
||||||
sender: "MIDDLE-1",
|
sender: "MIDDLE-1",
|
||||||
lat: 0.0,
|
lat: Decimal.new("0.0"),
|
||||||
lon: 0.0,
|
lon: Decimal.new("0.0")
|
||||||
has_position: true,
|
|
||||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
# Test antimeridian crossing bounds [west=170, south=-10, east=-170, north=10]
|
# 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
|
assert QueryBuilder.within_bounds(original_query, ["a", "b", "c", "d"]) == original_query
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue