Below, I’ll mention why I think it’s broken and the small change required to “fix” it.
Alas, it doesn’t work very well on current versions of MySQL (and judging by a
comment on the page, it didn’t work meaningfully back in 2006): it defines a
new function distance
from two POINT
s to a DOUBLE
. For some bizarre
reason, though, it round
s it’s result to 0 decimal places. This makes it
pretty useless for local POINT
s in a coarse metric like degrees (where 1° of
latitude is around 111km) when your POINT
s are local: you’ll often get a
whole lots of 0
s in your results:
Thankfully, it’s easy to fix: just remove the call to round
(or, I suppose,
give it an accuracy rather than let it default to 0 places).
To create the
function simply
run the following command somehow – I prefer the mysql
command-line, others
[phpMyAdmin][], and still more will put it in an XML file to be interpreted by
a rule engine which is called as part of a semi-automated deployment process
(these are the people that enjoy using Java):
-- Switch delimiter so the ; will work in the function body
DELIMITER $$
-- Create the function
CREATE FUNCTION `distance`(a POINT, b POINT) RETURNS double
DETERMINISTIC COMMENT 'Calculate the distance between two points'
BEGIN
RETURN glength(linestringfromwkb(linestring(asbinary(a), asbinary(b))));
END$$
-- Switch the delimiter back to ;
DELIMITER ;
You’ll notice that this is calling both asbinary
(to convert WKB values into
internal MySQL values) and then linestringfromwkb
(to convert WKB into
internal MySQL values). Exactly why linestring
takes MySQL values and
returns a WKB value, I’m not sure, but it does. If you need to support WKT
inputs, then you’ll wind up calling conversion functions four or five times
per query.
Convincing Django to call this new function is another matter all together.
After a day and a half of trying to understand the GeoDjango back-ends, I gave
up (see this thread on the GeoDjango mailing
list
for a little more information) and just used the extra()
method to add a
call to the function as a new column and order by
it:
= Postcode.objects.get(postcode='0200')
pc = SortedDict([('distance', 'distance(location, geomfromtext(%s))')])
sel = (pc.location.wkt,)
sel_p = Locations.objects.extra(select=sel, select_params=sel_p,
locations =['distance']) order_by
DATABASE_HOST
option in the
project settings file1:
= '/tmp/mysql.dev.sock' DATABASE_HOST
This works properly and reliably for Django itself but, on my system at least,
it also breaks the manage.py dbshell
command: rather than starting and
connecting to the correct database, the mysql
errors out with the message
ERROR 2005 (HY000): Unknown MySQL server host ‘/tmp/mysql.dev.sock’ (1)
The reason for this should be fairly obvious: /tmp/mysql.dev.sock
is not, in
fact, a host name. In fact, this whole solution seems pretty wacky to me (why
put a value that is distinctly not a host name in the “hostname” value?).
The correct way to specify a UNIX socket for the MySQL client libraries to
connect to is using the DATABASE_OPTIONS
([most of] the options can be seen
in the MySQLdb API documentation):
= {
DATABASE_OPTIONS 'unix_socket' : '/tmp/mysql.dev.sock',
}
Doing so ensures that Django is able to connect (using a UNIX socket on the
local host) and that the mysql
shell is able to connect (also using a UNIX
socket on the local host). Everything works, everyone is happy, and all of our
options have values that actually make sense. Hoorah!
See, for example, the documentation for DATABASE_HOST↩︎