Find all by distance from a reference point
In my Grails 1.3.7 application, I have a Building entity with a Double latitude and a Double longitude. I'm implementing a simple search engine in order to find all the Building instances with a given distance of the point where the user is (latitude and longitude in decimal degrees). I found this http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL which is good because I'm using a MySQL database. And the bounding box approximation is excellent for me as I need to perform additional filtering and calculations and I just need a finder that narrows down the number of instances I'm filtering. My question is: has anybody already implemen开发者_如何学Cted this kind of search in a Grails environment and how?
I have implemented something with similar requirements before, and I used a HQL query. It was a while ago and I remember it took me quite a while to read up and figure out, so hopefully saves you some time.
This does a select based on a current location (a simple lat long container object), and a "name" (startswith). It selects the domain object (venue) and also the miles away from the current location. It sorts by miles away ascending. Note I added a "road factor" fudge to approximate road distances.
def getVenuesInArea(venueName, location, miles, optionsMap)
{
def max = optionsMap?.max ?: 10
def offset = optionsMap?.offset ?: 0
if (venueName == null) venueName = ""
venueName += '%'
double roadFactor = 1.20 // add 20% for the roads, instead of as crow flies...
def query
def results
def countQuery = """ select count( distinct v)
from Venue as v
WHERE
v.name like :venueName AND
( acos
(
sin(radians(:lat))
* sin(radians(v.location.latitude))
+ cos(radians(:lat))
* cos(radians(v.location.latitude))
* cos(radians(v.location.longitude) - radians(:lon))
) * 3956.1676 * :roadFactor < :distance
)
"""
def count = Venue.executeQuery(countQuery, [venueName:venueName, lat:location.latitude, lon:location.longitude, distance:miles, roadFactor:roadFactor])[0]
query = """ select distinct v,
(
acos
(
sin(radians(:lat))
* sin(radians(v.location.latitude))
+ cos(radians(:lat))
* cos(radians(v.location.latitude))
* cos(radians(v.location.longitude) - radians(:lon))
)
* 3956.1676 * :roadFactor
) as milesAway
from Venue as v
WHERE
v.name like :venueName AND
( acos
(
sin(radians(:lat))
* sin(radians(v.location.latitude))
+ cos(radians(:lat))
* cos(radians(v.location.latitude))
* cos(radians(v.location.longitude) - radians(:lon))
) * 3956.1676 * :roadFactor < :distance
)
order by
(
acos
(
sin(radians(:lat))
* sin(radians(v.location.latitude))
+ cos(radians(:lat))
* cos(radians(v.location.latitude))
* cos(radians(v.location.longitude) - radians(:lon))
)
* 3956.1676 * :roadFactor
)
asc,
v.name
"""
results = Venue.executeQuery( query, [venueName:venueName, lat:location.latitude, lon:location.longitude, distance:miles, roadFactor:roadFactor, max:max, offset:offset])
def venues = []
MathContext mc = new MathContext(2)
results.each
{ result ->
VenueWithDetails venueDetails = new VenueWithDetails( venue:result[0], milesFrom:new BigDecimal(result[1]).round(mc) )
venues.add(venueDetails)
}
return [venues:venues, count:count]
}
This was done on grails version 1.3.4, but pretty sure it should work fine for 1.3.7.
Hope that helps, Chris.
精彩评论