finding locations within a particular distance using db2 - geolocation

I am using html5 geolocation api to get my position in latitude and longitude. I want to store them in a table of locations and want to retrieve those locations within a particular distance.
my current latitudes and longitudes are stored in variables "latval", "longval", "distance"
My table is "location"
columns are "location", "lat", "long"
I am using DB2 Express C as database and latitude and longitude columns are set as double type now. What type should I use to store these values and what would be the query to get location names within a distance
Thank you.

It looks like there's an extension for Express C that includes Spatial processing. I've never used it (and can't seem to get access at the moment), so I can't speak to it. I'm assuming that you'd want to use that (find all locations within a radius is a pretty standard query).
If for some reason you can't use the extension, here's what I would do:
Keep your table as-is, or maybe use a float data-type, although please use full attribute names (there's no reason to truncate them). For simple needs, the name of the 'location' can be stored in the table, but you may want to give it a numeric id if more than one thing is at the same location (so actual points are only in there once).
You're also going to want indicies covering latitude and longitude (probably one each way, or one covering each column).
Then, given a starting position and distance, use this query:
SELECT name, latitude, longitude
FROM location
WHERE (latitude >= :currentLatitude - :distance
AND latitude <= :currentLatitude + :distance)
AND (longitude >= :currentLongitude - :distance
AND longitude <= :currentLongitude + :distance)
-- The previous four lines reduce the points selected to a box.
-- This is, or course, not completely correct, but should allow
-- the query to use the indicies to greatly reduce the initial
-- set of points evaluated.
-- You may wish to flip the condition and use ABS(), but
-- I don't think that would use the index...
AND POWER(latitude - :currentLatitude, 2) + POWER(longitude - :currentLongitude, 2)
<= POWER (:distance, 2)
-- This uses the pythagorean theorem to find all points within the specified
-- distance. This works best if the points have been pre-limited in some
-- way, because an index would almost certainly not be used otherwise.
-- Note that, on a spherical surface, this isn't completely accurate
-- - namely, distances between longitude points get shorter the farther
-- from the equator the latitude is -
-- but for your purposes is likely to be fine.
EDIT:
Found this after searching for 2 seconds on google, which also reminded me that :distance would be in the wrong units. The revised query is:
WITH Nearby (name, latitude, longitude, dist) as (
SELECT name, latitdude, longitude,
ACOS(SIN(RADIANS(latitude)) * SIN(RADIANS(:currentLatitude)) +
COS(RADIANS(latitude)) * COS(RADIANS(:currentLatitude)) *
COS(RADIANS(:currentLongitude - longitude))) * :RADIUS_OF_EARTH as dist
FROM location
WHERE (latitude >= :currentLatitude - DEGREES(:distance / :RADIUS_OF_EARTH)
AND latitude <= :currentLatitude + DEGREES(:distance / :RADIUS_OF_EARTH))
AND (longitude >= :currentLongitude -
DEGREES(:distance / :RADIUS_OF_EARTH / COS(RADIANS(:currentLatitude)))
AND longitude <= :currentLongitude +
DEGREES(:distance / :RADIUS_OF_EARTH / COS(RADIANS(:currentLatitude))))
)
SELECT *
FROM Nearby
WHERE dist <= :distance
Please note that wrapping the distance function in a UDF marked DETERMINISTIC would allow it to be placed in both the SELECT and HAVING portions, but only actually be called once, eliminating the need for the CTE.

Related

How do I convert a list of long/lat coordinates in a row to a polygon?

I have a table with three columns, like the below:
Timezone_Name
Longitude
Latitude
America/Chicago
151.2095611
-31.8862892
America/Chicago
152.2095611
-32.8862892
America/Cancun
150.2095611
-34.8862892
America/Cancun
149.2095611
-35.8862892
I have loaded this data into Snowflake as a string. The coordinates field contains pairs of longitude/latitude coordinates, and when these points are combined they represent a polygon for each time zone.
Separately, I have also have a table mapping users to longitude/latitude coordinates. What I'd like to do is convert the coordinates in the table above into a polygon, then use the ST_COVERS function (here: https://docs.snowflake.com/en/sql-reference/functions/st_contains.html) with my user's long/lat coordinates to figure out what time zone they're in.
What I've Tried
Select Timezone_Name
,ST_COLLECT(ST_MAKEPOINT(table.longitude, table.latitude)) as coordinate_points
from table
group by 1
The code above works, and creates a set of geographic points for each timezone. However, when I try to call ST_MAKEPOLYGON on the coordinate_points field, I get an error saying:
"Type MultiPoint is not supported as argument to ST_MAKEPOLYGON"
What am I missing? I thought by calling ST_MAKEPOINT, it was returning a GEOGRAPHY data type?
So there are some important thing to point out, for a polygon the order of the points matters, and here we can see the two generations had different orders, AND the first point is supposed to be the last point, so that the loop is closed.
So if you are using "square" corners, you can build a valid polygon like so, I have altered you data to suit my demo needs, because while converting from your data to this, it was more pain than I need.
with data(tz_name, lon1, lat1, lon2, lat2) as (
select * from values
('America/Chicago', '151.2095611', '-31.8862892', '152.2095611', '-32.8862892'),
('America/Cancun', '150.2095611', '-34.8862892', '149.2095611', '-35.8862892')
)
select tz_name
,'LINESTRING('||lon1||' '||lat1||', '||lon2||' '||lat1||', '||lon2||' '||lat2||', '||lon1||' '||lat2||', '||lon1||' '||lat1||')' as line_str
,to_geography(line_str) as g_ls
,st_makepolygon(g_ls) as poly
from data;
If you have the orders vertex's of your polygon, but the set is not closed, you can grab the last and place it first, like so:
with data(tz_name, _order, lon, lat) as (
select * from values
('America/Chicago', 1, '151.2095611', '-31.8862892'),
('America/Chicago', 2, '152.2095611', '-31.8862892'),
('America/Chicago', 3, '152.2095611', '-32.8862892'),
('America/Chicago', 4, '151.2095611', '-32.8862892')
)
select
tz_name
,listagg(part,', ') within group (order by _order) as p_str
,'LINESTRING('||p_str||')' as line_str
,to_geography(line_str) as g_ls
,st_makepolygon(g_ls) as poly
from (
select
tz_name,
_order,
lon,
lat,
last_value(lon)over(partition by tz_name order by _order) as l_lon,
last_value(lat)over(partition by tz_name order by _order) as l_lat,
iff(_order = 1, l_lon ||' '|| l_lat ||', '|| lon ||' '|| lat, lon ||' '|| lat) as part
from data
)
group by 1

PostGIS: How to find N closest sets of points to a given set?

I am using PostGIS/Rails and have sets of points with geolocations.
class DataSet < ActiveRecord::Base # these are the sets containing the points
has_many :raw_data
# attributes: id , name
end
class RawData < ActiveRecord::Base # these are the data points
belongs_to :data_set
# attributes: id, location which is "Point(lon,lat)"
end
For a given set of points I need to find the N closest sets and their distance;
or alternatively:
For a given max distance and set of points I need to find the N closest sets.
What is the best way to do this with PostGIS?
My versions are PostgreSQL 9.3.4 with PostGIS 2.1.2
The answer on how to find the N-closest neighbours in PostGIS are given here:
Postgis SQL for nearest neighbors
To summarize the answer there:
You need to create a geometry object for your points. If you are using latitude, longitude, you need to use 4326.
UPDATE season SET geom = ST_PointFromText ('POINT(' || longitude || ' ' || latitude || ')' , 4326 ) ;
Then you create an index on the geom field
CREATE INDEX [indexname] ON [tablename] USING GIST ( [geometryfield] );
Then you get the kNN neightbors:
SELECT *,ST_Distance(geom,'SRID=4326;POINT(newLon newLat)'::geometry)
FROM yourDbTable
ORDER BY
yourDbTable.geom <->'SRID=4326;POINT(newLon newLat)'::geometry
LIMIT 10;
Where newLon newLat are the query points coordinates.
This query will take advantage of kNN functionality of the gist index (http://workshops.boundlessgeo.com/postgis-intro/knn.html).
Still the distance returned will be in degrees, not meters (projection 4326 uses degrees).
To fix this:
SELECT *,ST_Distance(geography(geom),ST_GeographyFromText('POINT(newLon newLat)')
FROM yourDbTable
ORDER BY
yourDbTable.geom <->'SRID=4326;POINT(newLon newLat)'::geometry
LIMIT 10;
When you calculate the ST_distance use the geography type. There the distance is always in meters:
http://workshops.boundlessgeo.com/postgis-intro/geography.html
All this functionality will probably need a recent Postgis version (2.0+). I am not sure though.
Check this for reference https://gis.stackexchange.com/questions/91765/improve-speed-of-postgis-nearest-neighbor-query/
EDIT. This covers the necessary steps for one point. For set of points:
SELECT n1.*,n2.*, ST_Distance(n1.geom,n2.geom)
FROM yourDbTable n1, yourDbTable n2
WHERE n1.setId=1 AND n1.setId=2 //your condition here for the separate sets
AND n1.id<>n2.id // in case the same object belong to 2 sets
ORDER BY n1.geom <->n2.geom
LIMIT 20;

Detecting Possible Duplicates in Rails

I have a Rails 3 application that has a model w/ a Name, and a Geographic Location (lat/lng). How would I go about search for possible duplicates in my model. I want to create a cron job or something that checks to see if two objects have a similar name and that they are less than 0.5 miles away from each other. If this matches then we'll flag the objects or something.
I am using Ruby Geocoder and ThinkingSphinx in my application.
Levenshtein is as good a way as any for judging the similarity of two text strings, ie the names.
What i would suggest is to (as well as, or instead of, the single "lat;long" string) store the latitude and longitude seperately. Then you can do an sql query to find other records that are within a certain distance, THEN run the levenshtein on their names. You want to try to run the lev as few times as possible as it's slow.
Then you could do something like this: let's say your model name is "Place":
class Place < ActiveRecord::Base
def nearby_places
range = 0.005; #adjust this to get the proximity you want
#lat and long are fields to hold the latitude and longitude as floats
Place.find(:all, :conditions => ["id <> ? and lat > ? and lat < ? and long > ? and long < ?", self.id, self.lat - range, self.lat + range, self.long - range, self.long + range])
end
def similars
self.nearby_places.select do |place|
#levenshtein logic here - return true if self.name and place.name are similar according to your criteria
end
end
end
I've set range to 0.005 but i've no idea what it should be for 1/2 a mile. Let's work it out: google says one degree of latitude is 69.13 miles, so i guess half a mile in degrees would be 1/(69.13 * 2) which gives 0.0072, so not a bad guess :)
Note that my search logic would return places that are anywhere within a square which is a mile per side, with our current place in the centre. This would potentially include more places than a circle with 1/2 mile radius with our current place in the centre, but it's probably fine as a quick way of getting some nearby places.

searching for closest number in a rails app

Give two parameters which correspond to two attributes on an object how can one find 20 records in a database that are closest to those two numbers.
The parameters you have are x, and y. The object also has those attributes. For example. x = 1, and y = 9999. You need to find the record that is the closest to x and y.
That depends on how you define the distance between two points. If you are using a two-dimensional cartesian coordinate system, this SQL statement will work:
SELECT id, x, y FROM points ORDER BY SQRT(POWER((X-x),2)+POWER((Y-y),2)) ASC LIMIT 20;
Where X,Y are the inputs.
It sounds like you're using geolocated data. If your database backend is Postgres, check to see if you have or can install the PostGIS extensions. This gives you very fast tools which give you searches like 'search for the nearest thing to this point', 'search for everything within this circle', 'search for everything within this square', and so on.
http://postgis.refractions.net/
You would do something like this:
CREATE INDEX [indexname] ON [tablename] USING GIST ( [geometrycolumn] gist_geometry_ops);
Then you can do something like this - find everything within 100 metres of a point:
SELECT * FROM GEOTABLE WHERE
GEOM && GeometryFromText(’BOX3D(900 900,1100 1100)’,-1) AND
Distance(GeometryFromText(’POINT(1000 1000)’,-1),GEOM) < 100;
Examples from the manual.

zipcode distance range formula. Which formula is correct?

I need to find all the zipcodes with a certain range from a zipcode. I have all the zipcode lat/lon in a DB.
I have found two formulas online that vary slightly from each other. Which one is correct?
Formula 1:
def latRange = range/69.172
def lonRange = Math.abs(range/(Math.cos(Math.toRadians(zip.latitude)) * 69.172));
def minLat = zip.latitude - latRange
def maxLat = zip.latitude + latRange
def minLon = zip.longitude - lonRange
def maxLon = zip.longitude + lonRange
Formula 2: (is identical to formula one except for the following:)
def lonRange = Math.abs(range/(Math.cos(zip.latitude) * 69.172));
(The second one does not have Math.toRadians )
After I get the min/max Lat/Lon, I intend to search a DB table using a between criteria. Which formula is correct?
It depends on the units of your lat/long. If they are in degrees, you need to convert to radians.
I would suggest letting the db do all the heavy lifting. Several DBs have geo add-ons, but here are a couple examples: MongoDB, postgres+postgis
If your latitude and longitude data isn't already in radians then you'll need to use the one that converts. You should be aware though that the way things are set up now you'll end up with a square range.
You'll be better off doing the distance calculation in your mysql query, using the Pythagorean theorem to find the distance so you end up with a circular range.
This question should help you get started with that: mySQL select zipcodes within x km/miles within range of y .
If you need to improve it's performance you can index it using Sphinx.

Resources