Forum OpenACS Q&A: Closest Address Calculation

Collapse
Posted by Sam Dau on
I believe I have come to the right place, at least I hope so.
Greetings all.

I have been trying unsuccessfully to get the address closest to the x/y coordinate pair.
I have created this function in transact-SQL but unfortunately, since we will making spatial search, the stored proc didn't help me that much.
I was wondering if I could get someone to get me a head start.

A user enters x/y coordinate pairs say x=0234.22 and y=236541.21

Based on the x/y coordinate pair, I will like to return the closest address within a 500 ft or 1 mile radius of the x/y coordinate pair.
I don't care how far the closest address is from the x/y coordinate pair as long as it is the closest.

The table structure is more like:
str_num , --street number
str_name, -- street name

x(the_geom) --x coordinate
y(the_geom) -- y coordinate

I tried my best rendition of Jerry's script below but to no avail.

select str_num||' '||name from (select str_num||' '||name, distance(loc, '$X, $Y) as address
                      from roads
                  order by address
                    limit 1) as tbl;

Does anyone know how to approach this?
Thanks in advance

Collapse
Posted by Tom Ayles on

Ignoring any functions, if you have a table definition like:

create table foo_addrs (
  str_num integer,
  str_name varchar(100),
  x float,
  y float
);

Then a query like the following should do the trick (where x0 and y0 are the variables containing the x-y coordinates of the location you are searching from):

select
  sqrt(distance) AS distance,
  str_num || ' ' || str_name AS address
from
  (select (:x0 - x)^2 + (:y0 - y)^2 as distance, *
   from foo_addrs order by distance limit 1) as r

This is all valid for PostgreSQL. I believe PostgreSQL also has a spatial coordinate type designed for this kind of thing.

HTH,
Tom

Collapse
Posted by Sam Dau on
Thanks for the response and code modification.
It seems to be having problem with certain aspects of it.
For instance, looking at the table structure I posted earlier,
the current code I am using looks like this:

select
  sqrt(distance) AS distance,
  str_num || ' ' || name AS address
from
  (select (:x(the_geom)0 - x(the_geom))^2 + (:y(the_geom)0 - y(the_geom))^2 as distance, *
  from address order by distance limit 1) as r

Notice the the syntax x(the_geom)

As you know, in PostGIS we get the x and y of each point shape using the x(geometry) and y(geometry) functions.
However, in our database, the geometry is already in the field called the_geom, so to get the x and y for each point we use it like this: x(the_geom) and y(the_geom).

The question becomes, how can I blend your part of the script: select (:x0 - x)^2 + (:y0 - y)^2 as distance into this: x(the_geom) and y(the_geom)?
I tried (:x0(the_geom) - x(the_geom))^2 + (:y0(the_geom) - y(the_geom))^2 as distance but it is error-ying out on both the :(colon) and the 0.

I must point out that if I use the code this way:

select
  sqrt(distance) AS distance,
  str_num || ' ' || name AS address
from
  (select (x(the_geom) - x(the_geom))^2 + (y(the_geom) - y(the_geom))^2 as distance, *
  from address order by distance limit 1) as r

(Without the colon and 0), it does return an address but I am afraid something significant is missing.

Any further assistance would be appreciated.

Collapse
Posted by Tom Ayles on
I figured you were running this query through OpenACS, what with this being the OpenACS forums 'n' all 😉. The syntax :x0 specifies a bind variable - the value of the variable x0 is substituted into the query in that position by the database driver. So if you want to run the query directly, substitute the :x0 and :y0 parts with the X-Y coordinates of the point you are measuring from. The maths behind this is just Pythogoras theorem - a quick google would find about 34,500 pages if you want to read more.
Collapse
Posted by russ m on
There's a thread at https://openacs.org/forums/message-view?message_id=112408 that covers this problem in terms of finding the closest US zipcode to some given GPS data. The query in that thread should do what you need.
Collapse
Posted by Sam Dau on
hi Russell,

I believe you are referring this code:

  select code from (select code, point_distance(loc, '$X, $Y) as dist
                      from zip
                  order by dist
                    limit 1) as tbl;

I saw that but what is confusing and the same confusion I ran into with Tom's code is that I didn't know that OpenACS is actually a tool used to writing codes such as this.

Rather, I am using postgres and I am having problem translating this bit(point_distance(loc, '$X, $Y) as dist

For instance, since my table layout looks like this:

table name: address
str_num --street number
name --street name

the_geom -- this holds the value for x/y point.

I know they are variables and need translating but I am not quite sure how to translate $X, $Y given that I will be using a query rather than a function?

Any suggestion would be appreciated.

Collapse
Posted by russ m on
OK. Given your table definition, I'd use

select str_num || ' ' || name as full_address,
       point_distance(the_geom, '$X,$Y' ) as dist
  from address
 order by dist
 limit 1
This is assuming that the_geom is defined as the Postgres point datatype.

Where the $X and $Y appear is where the input coordinates from your user should go - it doesn't sound like you're using OpenACS so I can't tell you how to do that in your environment (we would write them as :X and :Y ).

If you have many rows in the address table this will be a slow query. There are several optimisations available (using a bounding box to restrict the rows you consider candidates, replacing point_distance with your own function that doesn't bother taking the square root of the distance) but I'd think you should avoid that sort of thing until you can get the simple but slow query working in your environment.

Collapse
Posted by Sam Dau on
thank you very much russel.
You just touched on something that I am very much interested in.
Someone mentioned bounding box as a way to improve performance.
If you have any links where I can find code snips about bounding box, please let me know.
I have seen an article on bounding boxes but can't figure it how to use that in a code to achieve what I am trying to achieve.

Thanks again for the help

Collapse
Posted by russ m on
My first post in the thread I linked to above adds a bounding box check to Jerry's original query. x_delta_max and y_delta_max are half the respective side lengths of the bounding box, so if your distance unit is feet and you want a 500 foot bounding box you would use 250 for each of x_delta_max and y_delta_max.
Collapse
Posted by Sam Dau on
Please allow me one more question on this topic.

I ran this code per russell's script.

select
  str_num||' '||name AS full_address,
  point_distance(the_geom,2227284.1963667543 ,1435389.6730164126) as dist
  from address order by dist limit 1

I got the following error.

ERROR:  function point_distance(geometry, numeric, numeric) does not exist
HINT:  No function matches the given name and argument types. You may need to add explicit type casts.

Does anyone know what it means and how to resolve it?
Thanks again for all the help

Collapse
Posted by russ m on
Sam - no worries...

the point_distance function takes 2 arguments of type point. What you have there is 3 arguments where the first is indeed a point but the other two are plain numbers - what you need to do is tell Postgres that the 2 real numbers are actually the coordinates of a single point. The way to do this is to enclose the pair on numbers in single quote marks. So what you actually want is this -


select
str_num ||' '|| name AS full_address,
point_distance(the_geom, '2227284.1963667543,1435389.6730164126') as dist
from address order by dist limit 1
note the quote marks!

hope this helps you out...

Collapse
Posted by Sam Dau on
Thank you so much for your help and patience Russell.

I ran the current script:

select
str_num ||' '|| name AS full_address,
point_distance(the_geom, '2227284.1963667543,1435389.6730164126') as dist
from address order by dist limit 1

and now I get this:

ERROR:  function point_distance(geometry, "unknown") does not exist
HINT:  No function matches the given name and argument types. You may need to add explicit type casts.

Collapse
Posted by russ m on
hmmm... well it works for me... are you executing it from psql or from your deployment environment? if you execute "\df point_distance" from within psql do you get a result like this?

                    List of functions
 Result data type |      Name      | Argument data types 
------------------+----------------+---------------------
 double precision | point_distance | point, point
(1 row)
If it works in psql but not in your application then we can't really help you, and if your database isn't recognising the builtin point_distance function we also can't really help you... you should probably talk to the pgsql-general mailing list over at http://www.postgresql.org/lists.html...
Collapse
Posted by russ m on
hang on - it looks like the_geom is of type "geometry" not of type "point"... is that a PostGIS extension? If it is, this (again) isn't the right place to be getting answers... sorry 'bout that...
Collapse
Posted by Sam Dau on
You are right Russell.
the_geom is of type geometry, not of point.
You have been great and I truly appreciate all the efforts.
I will perhaps try the link you just mentioned.
Thanks again for all the help.
Collapse
Posted by Sam Dau on
Ok, I have been able to get the code to work.
Here is the working version:

  SELECT str_num||' ' ||name as full_address,
  distance(the_geom,'POINT(2227284.1963667543 1435389.6730164126)'::geometry) as dist
  FROM address order by dist limit 1

This works fine as far as retrieving the closest address to point.

There is however, a slight problem.
Retrieval is very slow due to the table being very large

This is where bounding boxes come in and I have attempted to use it but it is blowing up real big.

Here is the ugly code with where predicate added.

Does anyone have a clue as to what needs to change here for this to work.
I know this forum caters to OpenACS users but I have been able to resolve the first problem as a result of the pointers I received here.
Hopefully, I can get more pointers.

  SELECT str_num||' ' ||name as full_address,
  distance(the_geom,'POINT(2227284.1963667543 1435389.6730164126)'::geometry) as dist
  FROM address order by dist limit 1
  WHERE the_geom && 'BOX3D(90900 190900, 100100 200100)'::box3d
  AND distance( the_geom( 'POINT(2227284.1963667543 1435389.6730164126)', -1 ) ) < 100

First I don't know how this part of the code:
'BOX3D(90900 190900, 100100 200100)'::box3d
is derived. I mean the values 90900 190900, 100100 200100.
Second, obviously the syntax is wrond or else it would have worked.

Did I mention I am a complete newbie to openGIS/postGIS/postgreSQL? but I am learning real fast.

Collapse
Posted by russ m on
Sam,

There's an example of restricting the query with a bounding box in the thread referenced above but it's for plain Postgres not PostGIS, and since you're using the PostGIS extensions it won't really help. You really should speak to the people on the PostGIS mailing lists since they know the extensions you're using.

http://postgis.refractions.net/mailman/listinfo/postgis-users