SQL Spatial Query - Determine Which Polygon A Lat/Long Point Falls Into - sql

Via ArcMap, I imported a Feature Class into my SQL2019 Server. No issues and the polygons are displaying properly in the 'spatial results' tab when I check. Inside that feature class, there are three distinct shapes (Lets call the field tblGeo.AREA).
I have another table with LAT/LNG coordinate points (tblPoint.LAT, tblPoint.LNG).
Using the two tables (tblGeo and tblPoint), how can I determine which AREA field the coordinate falls into (if any)?
tblGeo:
Field Name
Field Type
Sample
GID
INT
1,2,3...
SHAPE
GEOMETRY
0x2569... or 0x110F...
GEOAREA
VARCHAR(50)
Washington, New York,...
tblPoint:
Field Name
Field Type
Sample
PID
INT
1,2,3...
LOCATION
VARCHAR(100)
White House
LAT
DECIMAL(9,6)
38.897957
LNG
DECIMAL(9,6)
-77.036560
Desired Output
PID
Location
Lat
Lng
GeoArea
1
White House
38.897957
-77.036560
Washington
2
Empire State Building
40.748817
-73.985428
New York
...
...
...
...

Sample input and output data would be nice.
You'll need to convert LAT and LNG to a geometry point.
Assuming LAT and LNG are DECIMAL(9, 6)...
select g.name as AreaName
, p.name as PointName
from tblGeo g
right outer join tblPoint p on g.AREA.STContains(geometry::Point(p.LAT, p.LNG, 0)) = 1
I could check my work if you provided sample data.

Related

Get distance in km in Postgis

I have 2 tables: City and River.
City: ID, city_name, the_geom (poin)
River: id, name, the_geom (multiline)
For each river I want to know the city farthest away and its distance in km.
For example...
I have the query
Select city1.city_name, st_distance(city1.the_geom, river1.the_geom)
from city city1, river river1
where
river1.name = 'Albany'
order by st_distance(city1.the_geom, river1.the_geom) desc
limit 1
In this case, I get the city farthest away from the Albany River.
Is this the best way to query?
I get this result:
City_name; distance
"Topeka"; 13.2534798131185
But I don't know if the result is in km... If it isn't... How can I get the result in km??
The units returned by st_distance are in spatial ref units - assuming you are using lat/lon (EPSG 4326) those units will be decimal degrees.
You have two choices - project to a suitable coordinate system in metres, or use the geography type.
Here is your query using geography:
SELECT city1.city_name,
st_distance(city1.the_geom::geography, river1.the_geom::geography) /1000 -- convert m to km
FROM city city1, river river1
WHERE
river1.name = 'Albany'
ORDER by st_distance(city1.the_geom::geography, river1.the_geom::geography) desc
LIMIT 1
(Note: I am not sure of the value of casting to geography in the order by statement).

Getting all Buildings in range of 5 miles from specified coordinates

I have database table Building with these columns: name, lat, lng
How can I get all Buildings in range of 5 miles from specified coordinates, for example these:
-84.38653999999998
33.72024
My try but it does not work:
SELECT ST_CONTAINS(
SELECT ST_BUFFER(ST_Point(-84.38653999999998,33.72024), 5),
SELECT ST_POINT(lat,lng) FROM "my_db"."Building" LIMIT 50
);
https://docs.aws.amazon.com/athena/latest/ug/geospatial-functions-list.html
Why are you storing x,y in separated columns? I strongly suggest you to store them as geometry or geography to avoid unnecessary casting overhead in query time.
That being said, you can compute and check distances in miles using ST_DWithin or ST_Distance:
(Test data)
CREATE TABLE building (name text, long numeric, lat numeric);
INSERT INTO building VALUES ('Kirk Michael',-4.5896,54.2835);
INSERT INTO building VALUES ('Baldrine',-4.4077,54.2011);
INSERT INTO building VALUES ('Isle of Man Airport',-4.6283,54.0804);
ST_DWithin
ST_DWithin returns true if the given geometries are within the specified distance from another. The following query searches for geometries that are in 5 miles radius from POINT(-4.6314 54.0887):
SELECT name,long,lat,
ST_Distance('POINT(-4.6314 54.0887)'::geography,
ST_MakePoint(long,lat)) * 0.000621371 AS distance
FROM building
WHERE
ST_DWithin('POINT(-4.6314 54.0887)'::geography,
ST_MakePoint(long,lat),8046.72); -- 8046.72 metres = 5 miles;
name | long | lat | distance
---------------------+---------+---------+-------------------
Isle of Man Airport | -4.6283 | 54.0804 | 0.587728347062174
(1 row)
ST_Distance
The function ST_Distance (with geography type parameters) will return the distance in meters. Using this function all you have to do is to convert meters to miles in the end.
Attention: Distances in queries using ST_Distance are computed in real time and therefore do not use the spatial index. So, it is not recommended to use this function in the WHERE clause! Use it rather in the SELECT clause. Nevertheless the example below shows how it could be done:
SELECT name,long,lat,
ST_Distance('POINT(-4.6314 54.0887)'::geography,
ST_MakePoint(long,lat)) * 0.000621371 AS distance
FROM building
WHERE
ST_Distance('POINT(-4.6314 54.0887)'::geography,
ST_MakePoint(long,lat)) * 0.000621371 <= 5;
name | long | lat | distance
---------------------+---------+---------+-------------------
Isle of Man Airport | -4.6283 | 54.0804 | 0.587728347062174
(1 row)
Mind the parameters order with ST_MakePoint: It is longitude,latitude.. not the other way around.
Demo: db<>fiddle
Amazon Athena equivalent (distance in degrees):
SELECT *, ST_DISTANCE(ST_GEOMETRY_FROM_TEXT('POINT(-84.386330 33.753746)'),
ST_POINT(long,lat)) AS distance
FROM building
WHERE
ST_Distance(ST_GEOMETRY_FROM_TEXT('POINT(-84.386330 33.753746)'),
ST_POINT(long,lat)) <= 5;
First thing first. If possible use Postgis not amazon-athena. Looking on documentation athena looks like the castrated version of a spatial tool.
First - Install postgis.
CREATE EXTENSION postgis SCHEMA public;
Now create geometry(if you want to use metric SRID like 3857 for example) or geography (if you want use degree SRID like 4326) column for your data.
alter table building add column geog geography;
Then transform your point data (lat,long) data to geometry/geography:
update building
set geog=(ST_SetSRID(ST_MakePoint(lat,long),4326)::geography)
Next create spatial index on it
create index on buildings using gist(geog);
Now you are ready for action
select *,
st_distance(geog, ST_makePoint(-84.386,33.72024))/1609.34 dist_miles
from building
where st_dwithin(geog, ST_makePoint(-84.38653999999998,33.72024),5*1609.34);
Few words of explenations:
Index is useful if you have many records in your table.
ST_Dwithin uses index when st_distance doesn't so ST_dwithin will make your query much faster on big data sets.
For aws Athena , try to use this for calculte aprox distance in degrees
decimal_degree_distance = 5000.0 * 360.0 / (2.0 * pi() * cos( radians(latitud) ) * 6400000.0)
where 5000.0 y distance in meters
is good for near ecuador places

Incorrect results returned by postgres

I ran the following commands in posgresql 9.6:
./bin/createdb testSpatial
./bin/psql -d testSpatial -c "CREATE EXTENSION postgis;"
create table test(name character varying(250), lat_long character varying(90250), the_geom geometry);
\copy test(name,lat_long) FROM 'test.csv' DELIMITERS E'\t' CSV HEADER;
CREATE INDEX spatial_gist_index ON test USING gist (the_geom );
UPDATE test SET the_geom = ST_GeomFromText(lat_long,4326);
On running: select * from test; I get the following output:
name | lat_long
|
the_geom
------+-----------------------------------------------------------------------------------------------------------------------------------------
---------------------------------------------------+--------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------
A | POLYGON((-0.061225 -128.427791,-0.059107 -128.428264,-0.056311 -128.428911,-0.054208 -128.426510,-0.055431 -128.426324,-0.057363 -128.42
6124,-0.059315 -128.425843,-0.061225 -128.427791)) | 0103000020E61000000100000008000000D42B6519E258AFBFBE50C076B00D60C07DE9EDCF4543AEBFBC41B456B
40D60C08063CF9ECBD4ACBFA1BC8FA3B90D60C07BF65CA626C1ABBF58AD4CF8A50D60C0BF805EB87361ACBFFFAF3A72A40D60C0B83A00E2AE5EADBF4D81CCCEA20D60C01F1153228
95EAEBF60C77F81A00D60C0D42B6519E258AFBFBE50C076B00D60C0
B | POINT(1.978165 -128.639779)
| 0101000020E61000002D78D15790A6FF3F5D35CF11791460C0
(2 rows)
After this I ran a query: To find all "name" which are within 5 meters of each other. For doing so, I wrote the following command.
testSpatial=# select s1.name, s2.name from test s1, test s2 where ST_DWithin(s1.the_geom, s2.the_geom, 5);
name | name
------+------
A | A
A | B
B | A
B | B
(4 rows)
To my surprise I am getting incorrect output as "A" and "B" are 227.301 km away from each other (as calculated using haversine distance here: http://andrew.hedges.name/experiments/haversine/). Can someone please help me understand as to where am I going wrong.
You have defined your geometry as follows
the_geom geometry
ie, it's not geography. But the ST_DWithin docs say
For Geometries: The distance is specified in units defined by the
spatial reference system of the geometries. For this function to make
sense, the source geometries must both be of the same coordinate
projection, having the same SRID.
For geography units are in meters and measurement is defaulted to
use_spheroid=true, for faster check, use_spheroid=false to measure
along sphere.
So you are actually searching for places that are within 5 degrees of each other. A degree is roughly equal to 111km so you are looking for places that are about 550 km from each other rather than 5 meters.
Additionally, it doesn't make much sense to store strings like POINT(1.978165 -128.639779) in your table. It's completely redundant. It's information that can be generated quite easily from the geography column.

Find records where lat long values are different for the same base address

I need help writing a query in Oracle SQL Developer which finds where one attribute matches but the corresponding attributes are different. For example, I need to find records where address fields match, but then the latitude and longitude column have different values than the corresponding records.
This is an example of what I am talking about (record #3 is the problem):
ID Address latitude longitude
1 1104 West St 35.3 -90.1
2 1104 West St 35.3 -90.1
3 1104 West St 36.4 -94.2
The point of this is to find where lat long values are different for the same base address. The reason multiple records like this exist is because they are different units in the building, thanks in advance.
Use analytic functions, if you want to see the detailed records. The idea is to calculate the minimum and maximum latitude and longitude for each address and then compare them:
select ID, Address, latitude, longitude
from (select t.*,
min(latitude) over (partition by address) as minlat,
max(latitude) over (partition by address) as maxlat,
min(longitude) over (partition by address) as minlong
max(longitude) over (partition by address) as maxlong
from table t
) t
where minlat <> maxlat or minlong <> maxlong
order by address, id;
If the values are actually stored as floating point numbers, then you might want a little bit of wiggle room:
where (maxlat - minlat) > 0.00001 or (maxlong - minlong) > 0.00001
(Note: abs() isn't needed because I know which is bigger and which is smaller.)

Load matrix from file into PostgreSQL table

I have a file universities.txt which looks like this:
Alabama
Air University
Alabama A&M University
Alabama State University
Concordia College-Selma
Faulkner University
Huntingdon College
Jacksonville State University
Judson College
Miles College
Oakwood College
Samford University
Southeastern Bible College
Southern Christian University
Spring Hill College
Stillman College
Talladega College
University of North Alabama
University of South Alabama
University of West Alabama
Alaska
Alaska Bible College
Alaska Pacific University
Sheldon Jackson College
University of Alaska - Anchorage
University of Alaska - Fairbanks
University of Alaska - Southeast
Arizona
American Indian College of the Assemblies of God
Arizona State University
Arizona State University East
Arizona State University West
DeVry University-Phoenix
Embry-Riddle Aeronautical University
Grand Canyon University
Northcentral University
Northern Arizona University
.. and so on, where in this case Alabama, Alaska and Arizona are locations and everything else are universities. What I want to do is load the location into a table called Location and the Universities into a table called University, where the Id of the Location table is a FK to the University table, like this:
CREATE TABLE Location (
Id SERIAL PRIMARY KEY,
Name TEXT
);
CREATE TABLE University (
Id SERIAL PRIMARY KEY,
Location INTEGER REFERENCES Location (Id) NOT NULL,
Name TEXT
);
So what I want to do in Postgres is something like this:
for (int i=0 until i = universities.size() i++){
//each entry in the universities vector is a tuple with the first entry being the country/state
//and the second entry being a vector of the universities as String's
Vector tuple = (Vector)universities.get(i);
//insert into location table
String state = (String)tuple.get(0);
Vector u = (Vector)tuple.get(1);
for(int j=0; until j =u.size(); j++){
//insert into university table with i as FK to location table
Anyone knows how to do this?
Here is a pure SQL solution.
Use COPY to import your file into a temporary table and one DML statement with data modifying CTEs (requires PostgreSQL 9.1 or later) to do the rest. Should be fast for both steps:
Temporary table with a single text column ( dropped automatically at end of session):
CREATE TEMP TABLE tmp (txt text);
Import data from file:
COPY tmp FROM '/path/to/file.txt'
If you are doing this from a remote client, use the meta command \copy of psql instead.
My solution depends on the data format displayed in the question. I.e.: there is an empty row before and after a city. I assume actual empty strings in the import file. Make sure to have a leading row with an empty string before the first city to avoid a special case.
Rows will be inserted in order. I use that for the following window functions without ordering.
WITH x AS (
SELECT txt
,row_number() OVER () AS rn
,lead(txt) OVER () = '' AND
lag(txt) OVER () = '' AS city
FROM tmp -- don't remove empty rows just yet
), y AS (
SELECT txt, city
,sum(city::int) OVER w AS id
FROM x
WHERE txt <> '' -- remove empty rows now
WINDOW w AS (ORDER BY rn)
), l AS (
INSERT INTO location (id, name)
SELECT id, txt
FROM y
WHERE city
), u AS (
INSERT INTO university u (location, name)
SELECT id, txt
FROM y
WHERE NOT city
)
SELECT setval('location_id_seq', max(id))
FROM y;
Voilá.
CTE x marks cities based on an empty string value in the rows before and after them.
CTE y adds a running sum of cities (id), thereby forming a perfectly valid id for each city and its unis.
CTEs l and u do the inserting, which is now easy.
The final SELECT sets the next value for the sequence attached to location.id. We have not been using it, so we must set it to the current maximum value or we run into duplicate key errors with future INSERTs to location.
Transforming the original into a table is the safest way... then you can just upload it with COPY.
BEGIN { bl=0; body=0; header=""; }
$0 == "" && body==1 && header!="" { header=""; body=0; bl=1; next; }
$0 == "" && body==0 { bl=1; next; }
$0 != "" && header=="" { header=$0; bl=0; next; }
$0 != "" && bl==1 && header!="" { body=1; print header, ",", $0 }
something like that AWK would turn your file into a table which you could then upload with a straight psql copy statement:
COPY university_data_file_table FROM awk-mashed-file;
then you could transform that table into your separate ones:
CREATE TABLE country AS SELECT DISTINCT country FROM university_data_file_table;
CREATE TABLE university AS SELECT country.id, udft.university FROM country, university_data_file_table udft WHERE udft.country = country.country;
Something like that would be easily scriptable with a psql script. As I say, you do have to do the initial transform.