Geodetic coordinates: How to project a point on a line on the same meridian - postgis

Given a geodetic segment, e.g., from Brussels to Moscow, and a geodetic point, e.g., Berlin, which can be expressed in PostGIS as follows
select geography 'Linestring(4.35 50.85,37.617222 55.755833)', geography 'Point(13.405 52.52)';
I need to find the point on the segment that is exactly at the same meridian of the given point, i.e., their bearing is 0 degrees.
Any idea how to obtain this ?
For example, using PostGIS, I create a segment from Berlin to the North by taking Berlin's longitude with the maximum of the two latitudes of the initial segment and compute the intersection of the two segments as follows
select st_astext(st_intersection(geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 52.52,13.405 55.755833)'))
st_astext
----------------------------------------------
POINT(13.407059592483968 53.163047143541235)
As can be seen the longitude is not exactly the same and the bearing would not be 0.
with test(inter) as (
select st_intersection(geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 52.52,13.405 55.755833)') )
SELECT degrees(bearing(geography 'Point(13.405 52.52)', inter)) from test;
degrees
---------------------
0.11002408958628832
where bearing is computed using the traditional formula (e.g., https://gist.github.com/jeromer/2005586)

I find the postgis intersection result in you example surprising (and probably wrong). Doing the inverse of your query, it seems to me that there is a numerical precision issue:
select st_intersects(geography 'POINT(13.407059592484 53.1630471435412)', geography 'Linestring(13.405 52.52,13.405 55.755833)')
--FALSE
select st_intersects(geography 'POINT(13.405 53.1630471435412)', geography 'Linestring(13.405 52.52,13.405 55.755833)')
-- TRUE

This strangeness is caused by how intersections are computed when dealing with geographies.
The st_intersection doc says:
Geography: For geography this is really a thin wrapper around the geometry implementation. It first determines the best SRID that fits the bounding box of the 2 geography objects (if geography objects are within one half zone UTM but not same UTM will pick one of those) (favoring UTM or Lambert Azimuthal Equal Area (LAEA) north/south pole, and falling back on mercator in worst case scenario) and then intersection in that best fit planar spatial ref and retransforms back to WGS84 geography.
Using your coordinates and peeking at the code, we can see the that a LAEA projection is used. If you were to extend the south-north line, a world mercator projection would be used - and the result would be better!
select _ST_BestSRID(geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 52.52,13.405 55.755833)');
_st_bestsrid
--------------
999247
(1 row)
select _ST_BestSRID(geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 42.52,13.405 65.755833)');
_st_bestsrid
--------------
999000
(1 row)
select st_astext(st_intersection(geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 42.52,13.405 65.755833)'));
st_astext
--------------------------------
POINT(13.405 52.2417230551345)
(1 row)
But even though, re-projection over the entire lines length is occuring, which can affect the output. The solution is therefore to subdivide the inputs into many small segments, so the reprojected lines are more aligned with the original ones
select st_astext(st_intersection( ST_Segmentize(geography 'Linestring(4.35 50.85,37.617222 55.755833)',1000),
ST_Segmentize(geography 'Linestring(13.405 52.52,13.405 55.755833)',1000)));
st_astext
------------------------------------------
POINT(13.4050000115689 53.2633912803655)
(1 row)

Berlin is located North of the line between Bruxelles and Moscow. So your two segments don't intersect each other. Instead of considering the line from Berlin to the highest latitude of the first segment you should directly consider a line going from the North pole to the South Pole, which gives you get the correct answer :
select st_astext(
st_intersection(
geography 'Linestring(4.35 50.85,37.617222 55.755833)',
geography 'Linestring(13.405 89.999999,13.405 -89.999999)'))
-- POINT(13.405 52.2417230551345)

Related

How to resolve a latitude/longitude to a polygon in postgis

I have a list of 13000 places (with latitude and longitude) --- in table : place.
I have a list of 22000 polygons ---- in another table called place_polygon.
I need to try and resolve the pois to the polygons that they belong to.
This is the query that I wrote :
select * from stg_place.place a
left join stg_place.place_polygon b on
ST_Within(ST_GeomFromText('SRID=4326;POINT('||a.longitude||' '||a.latitude||')'),b.geom);
also tried :
select * from stg_place.place a
left join stg_place.place_polygon b on
ST_Intersects(ST_GeomFromText('SRID=4326;POINT('||a.longitude||' '||a.latitude||')'),b.geom);
It's running forever.
But, if I put a filter in the query, then it runs very fast for a single record.
select * from stg_place.place a
left join stg_place.place_polygon b on
ST_Within(ST_GeomFromText('SRID=4326;POINT('||a.longitude||' '||a.latitude||')'),b.geom)
where a.id = <id>;
I also tried writing a stored procedure and tried to loop through a cursor to only do for one record at a time. That also didn't help. The program ran overnight with no signs of ending.
Is there a better way to solve this? (not necessarily in postgis, but in python geopy etc... ? )
(Should I consider indexing the tables?)
First of all use geography type for your data instead of lat long columns. Why geography, not geometry? Because you use SRID=4326 and with geography type, it will be much easier if you want for example calculate distance in meters then with geometry type which will calculate in degrees for this SRID.
To create geography with your lat long column use function st_setsrid(st_makepoint(long,lat),4326)::geography
Ok. Now answering your question on your actual structure
I have a list of 13000 places (with latitude and longitude) --- in table : place. I have a list of 22000 polygons ---- in another table called place_polygon. I need to try and resolve the pois to the polygons that they belong to.
This is the query that I wrote :
select *
from stg_place.place a
left join stg_place.place_polygon b on
ST_DWithin (st_setsrid(st_make_point(long,lat),4326),b.geom,0);
I used ST_DWithin() instead of ST_Within() because on an older version of Postgres+PostGIS (for sure 9.6 and below) it guarantees of using a spatial index on geoms if created.

PostGIS ST_Expand point by meters and bounding box

I don't think something is right want max and min lat long of the 100 meter buffer.
SELECT ST_Asgeojson(
ST_Expand(
ST_GeomFromEWKT('SRID=4326;POINT(-88.33 36.33)')
,100
)
);
Think it is calculating by 100 degrees of lat and long.
This gis.se question shows what I'm wanting to do. Answer 5 graphically depicts what I'm wanting to do.
I'm trying to get the
coordinates: [
[-80.425, 46.437],
[-71.516, 46.437],
[-71.516, 37.936],
[-80.425, 37.936]`
to add it to the map.
Yes, of course... As in documentation of ST_Expand
Units are in the units of the spatial reference system in use denoted
by the SRID.
EPSG:4326 is in degrees so it's exanding by 100 degrees.
I suggest you use ST_Buffer function with geography datatype:
SELECT ST_Asgeojson(ST_Buffer((ST_GeomFromEWKT('SRID=4326;POINT(-88.33 36.33)'))::geography,100));
And if you need rectangle you have to add ST_Envelope with geography::geometry
SELECT ST_Asgeojson(ST_Envelope(ST_Buffer((ST_GeomFromEWKT('SRID=4326;POINT(-88.33 36.33)'))::geography,100)::geometry));
If you need to use this in select from table you might need to reproject your geometry because cast ::geography needs a LatLon CRS, so it'll look like this:
SELECT ST_Asgeojson(ST_Envelope(ST_Buffer((ST_Transform(x.the_geom,4326)::geography,100)::geometry));

I tried all ways, but still my area is calculated wrongly in Postgis

I created a very simple polygon in the middle of Germany to demonstrate my problem.
You can visualize it in geojsonlint using the following GeoJSON
{"type":"Polygon","coordinates":[[
[10.439844131469727,51.17460781257472],
[10.430574417114258,51.1753073564544],
[10.429565906524658,51.17179607723465],
[10.438792705535889,51.170706315523866],
[10.439372062683105,51.17267055874809],
[10.43975830078125,51.17439256616884],
[10.439844131469727,51.17460781257472]]]G}
When calculating the surface with online tools (e.g. http://www.daftlogic.com/projects-google-maps-area-calculator-tool.htm, but I tried several),
I get the following numbers (these are based on a similar drawing of the polygon, but not the exact same one, as I couldn't copy it over to these tools):
276583.39 m²
0.28 km²
68.35 acres
27.66 hectares
2977118.86 feet²
0.08 square nautical miles
Now I want to calculate these areas using POSTGIS, but I always get wrong and not matching numbers.
First I started without transformation using the examples given here:
http://postgis.net/docs/ST_Area.html
SELECT ST_Area(the_geom) As sqft, ST_Area(the_geom)*POWER(0.3048,2) As sqm
FROM (SELECT ST_GeomFromText('
POLYGON ((51.17460781257472 10.439844131469727,
51.1753073564544 10.430574417114258,
51.17179607723465 10.429565906524658,
51.170706315523866 10.438792705535889,
51.17267055874809 0.439372062683105,
51.17439256616884 10.43975830078125,
51.17460781257472 10.439844131469727))',4326) ) As foo(the_geom);
--> sqft = 3.52643124351653e-05 and sqm = 3.27616182873666e-06
How can I interprete these numbers?
Then I tried to transform it to WGS 84 / UTM zone 33N 32633
SELECT ST_Area(the_geom) As sqft, ST_Area(the_geom)*POWER(0.3048,2) As sqm
FROM (SELECT ST_Transform(ST_GeomFromText('
POLYGON ((51.174661624019286 10.440187454223633,
51.17067940750161 10.438899993896484,
51.17197097486416 10.429544448852539,
51.17536116708255 10.430488586425781,
51.174661624019286 10.440187454223633))',4326),32633) ) As foo(the_geom);
--> sqft = 662918.939349234 and sqm = 61587.1847391195
But even these numbers don't come close.
The coordinates of the polygon were accidentally loaded as lat,lon instead of lon, lat.
http://postgis.net/2013/08/18/tip_lon_lat
says
In spatial databases spatial coordinates are in x = longitude, and y = latitude
I converted the coordinates into EPSG: 31467, see epsg:31467 which is projected to meters and applies to the area of Germany covered by your geometry.
select st_area(st_transform(st_setsrid(st_geomfromtext('POLYGON((10.439844131469727
51.17460781257472,10.430574417114258 51.1753073564544,10.429565906524658
51.17179607723465,10.438792705535889 51.170706315523866, 10.439372062683105
51.17267055874809, 10.43975830078125 51.17439256616884, 10.439844131469727
51.17460781257472))'),4326),31467));
and got the answer: 274442.27 m2 which is within 0.007% of your original answer.
Measurements are usually more accurate in projected coordinate systems that use a geoid appropriate to that region. If you run this query on the spatial reference system table in Postgis for that projection:
select * from spatial_ref_sys where srid=31467;
you will see some more details, such as the fact that it uses the Bessel 1841 spheroid.
EDIT: your original geojson has coordinates in x/y, but for some reason you flipped them when putting them into Postgis.

ST_ConvexHull(ST_Collect(pointgeom)) returns points, linestrings and polygons

I have a table full of point geometries that display normally in GIS clients. I want to group the points on an attribute (groupid) and create convex hulls around those. Seems straightforward, but getting an unexpected result: a 'geometry' field with mix of points, linestrings and polygons. I was expecting only polygons, and maybe for groups with a count of 1 to be ignored. Any ideas?
The query:
SELECT groupid, ST_ConvexHull(ST_Collect(geom))) As hull_geom into hulledpoints
FROM somepoints
GROUP BY groupid;
The easiest way to ensure that you only have polygons is to ensure that you only take the convex hull of groups where the groupid count is greater than 2.
SELECT groupid, ST_ConvexHull(ST_Collect(geom))) As hull_geom into hulledpoints
FROM somepoints
GROUP BY groupid
HAVING count(groupid)>2;
This is because the convex hull of two points is a linestring, while the convex hull of a point is that same point, eg,
select ST_AsText(ST_ConvexHull(ST_Collect(St_MakePoint(1,1),St_MakePoint(2,2))));
which returns LINESTRING(1 1,2 2).
If you wanted to use a geometric rather than a sql approach, you could check the returned hulls were polygons. The following example returns nothing, as although there are three points, two are coincident, so the convex hull will still be a linestring.
with hulls as (select ST_ConvexHull(ST_Collect(Array[St_MakePoint(1,1),
St_MakePoint(2,2), St_MakePoint(2,2)])) as hull)
select * from hulls where ST_GeometryType(hull)='ST_Polygon';
In your original example, you would write the above query along the lines of,
with hulls as (SELECT groupid, ST_ConvexHull(ST_Collect(geom)) As hull_geom
FROM somepoints GROUP BY groupid)
select * from hulls where ST_GeometryType(hull_geom)='ST_Polygon';
ad
"getting an unexpected result: a 'geometry' field with mix of points, linestrings and polygons."
That's easy to test: The convex hull of a point is a point
select st_astext(st_convexhull(st_makepoint(1,2)))
st_astext
POINT(1 2)
and for two points, it's a line
select st_astext(st_convexhull(st_collect(st_makepoint(1,2),st_makepoint(2,2))))
st_astext
LINESTRING(1 2,2 2)
If you have more GIS-related questions, feel free to post at http://gis.stackexchange.com.

2D Binding Box Query

Group,
I have table columns ProductID, latitude,longitude,timestampGMT and looking for a query to count how many Id's are within this bounding box or envelope.
Any helpful suggestions.
SQL Server 2008 supports GEOGRAPHY datatype.
You should store lat, lon in a single column of this datatype, create a SPATIAL index over it and use it in a query:
SELECT m.*
FROM mytable
ON coords.STDistance(#mypoint) <= #mydistance
You are looking for the "Great Circle" distance formula
http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=81360
Should point you to the correct SQL algorithm
This question shouldn't be a community wiki. Switch it if you can. Either way, here is your answer.
#Quassnoi gave a great solution for SQL 2008, but you're asking for 2003, right? 2003 doesn't have the same geography support as 2008. You'll have to roll your own as I did. It's not hard, depending on the level of accuracy that you require. Here's a scalar-function that I came up with based on a popular formula for calculating distance between two sets of coordinates:
-- =====================================================================
-- Author: Byron Sommardahl
-- Create date: June 15, 2007
-- Description: Calculates the distance between two sets of coordinates.
-- ======================================================================
CREATE FUNCTION [dbo].[Distance]
(
#lat1 float,
#long1 float,
#lat2 float,
#long2 float
)
RETURNS float
AS
BEGIN
RETURN (3958*3.1415926*sqrt((#lat2-#lat1)*(#lat2-#lat1) + cos(#lat2/57.29578)*cos(#lat1/57.29578)*(#long2-#long1)*(#long2-#long1))/180);
END
GO
To use this, just feed in your coords using a SELECT:
SELECT dbo.Distance(MyPlace.Lat, MyPlace.Long, TheirPlace.Lat, TheirPlace.Long);
Then, you could just check whether a product is within a certain radius of your focal point. Not exactly bounding box, but it gets you moving in the right direction.
You may also want to take a look at this:
SQL Server ZipCode Latitude Longitude Proximity Distance Search

Resources