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

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

Related

Find Distance using Latitude/Longitude using Neo4J

I have a CSV consisting of Places of Interest in one table and Other table consists of office locations of a company. Both the tables consist of Latitude and Longitude information as well.
Structure of CSV file consisting of Places of Interest
POI_Name Longitude Latitude City
POI_1 77.573957 12.970125 Bangalore
POI_2 77.579886 13.009582 Bangalore
POI_3 77.546688 13.023931 Chennai
Similarly we have a CSV file with office locations of a company
Office Longitude Latitude City
Office_1 78.324445 12.970125 Bangalore
Office_2 77.254555 13.234444 Chennai
Office_3 76.098438 14.135567 Bangalore
Both tables consists of thousands of records. Now I want to create a query in Neo4J that will give me top 5 Nearest place of interest to the office location (Passed as a parameter in the query) in the decreasing order at the run time.
As the distance bewteen two nodes every time will be the same, I recommend you parse all places vs all offices and create a "distance" relation with a value of distance in km or whatever between the nodes(placei,officej), then you only query the first n nearest nodes from another node (then you can run a query like: MATCH (p:Place)-[d:DISTANCE]->(o:Office{myoffice}) RETURN p,o,d ORDER BY d.km ASC LIMIT 5). You will save time and computational cost.
Anyway you can use something like the next query:
MATCH (p:Place), (o:Office {id:myoffice}) RETURN distance(point({latitude: p.latitude, longitude: p.longitude}), point({latitude: o.latitude, longitude: o.longitude})) / 1000.0 as km, p, o ORDER BY km ASC LIMIT 5

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;

finding locations within a particular distance using db2

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.

ElasticSearch Script Field Returns Incorrect Longitude Value

When I add:
fields: [ "doc['Location'].lon" ]
to my query, the longitude value is different that what is shown in the document source.
Here you can see the results of a query where I fetched the doc['Latitude'].lon and .lat, and the _source.Latitude to compare:
https://gist.github.com/d9533170f1f50fd27e87 (note - these have been passed through json_decode in PHP, but the data is the same before using json_decode.)
I first noticed this when I was using "doc['field_name'].distance(lat, lon)" to try and add the distance as a field to my query. I tried both the "script_fields" and "fields" keys and each had the same result.
UPDATE: I noticed that the "doc['Location'].lon" is returning what I thought should be the doc['Location'].lat (the lat and lon are switched.)
The problem was that when using GeoJSON format (or using lat/long as an array) you have to switch the order of lat/lng to lng/lat.
I am rebuilding my index, but, in order to work around this I have used this query for now:
doc['Location'].distance( lon + 180, lat ) // Temporary bandaid
Once I've rebuilt the index with the correct values I'll switch back to:
doc['field_name'].distance(lat, lon) // The correct method

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