I'd like to represent a large square as a polygon in a SQL Server database. This polygon spans almost the entire map.
Here are the boundary co-ordinates (latitude, longitude)
North-East Corner: { 83.4871233224387, 63.5599894667969 }
South-West Corner: { -3.62943382709019, 86.0599894667969 }
Here's what it looks like on a map (these are the map bounds for a zoomed-out google map)
Here's what SQL Server thinks when I try to draw a polygon:
declare #p5 sys.geography;
set #p5 = geography::STGeomFromText('POLYGON((86.0599894667969 -3.62943382709019, 63.5599894667969 -3.62943382709019, 63.5599894667969 83.4871233224387, 86.0599894667969 83.4871233224387, 86.0599894667969 -3.62943382709019))', 4326);
select #p5
It thinks I'm not following the left hand rule properly, but I am, I just want an absolutely massive polygon.
If I use reorientobject, I get the inverse, which is also not what I want:
Here are those two points plotted on a map for reference:
How can I resolve this issue?
I can't explain what's going on here in an elegant manner, but I was able to hack my way to a polygon that's pretty close to what you're looking for:
declare #p1 geography = geography::STGeomFromText('
POLYGON((
63 83,
0 83,
0 -3,
63 -3,
63 83
))', 4326)
,#p2 geography = geography::STGeomFromText('
POLYGON((
86 83,
86 -3,
180 -3,
180 83,
86 83
))', 4326)
,#p3 geography = geography::STGeomFromText('
POLYGON((
180 83,
180 -3,
-90 -3,
-90 83,
180 83
))', 4326)
,#p4 geography = geography::STGeomFromText('
POLYGON((
-90 83,
-90 -3,
0 -3,
0 83,
-90 83
))', 4326);
select #p1.STUnion(#p2).STUnion(#p3).STUnion(#p4).STAsText();
Edit: I did have some time to sleep on it and came up with a reasonable interpretation of the behavior without it being a bug in SQL. For any four such points (that is, the corners of a box), they define four distinct shapes. I'll describe them in terms of the specific case we're dealing with here, but I think it generalizes.
The four shapes are: the small box that the OP obtained when calling ReorientObject on the original polygon, its complement (aka the original polygon), the desired shape, and its complement.
Moreover, note that it doesn't matter what order you specify points in a ring in. Which is really what we're describing here (a ring that is). Geographic polygons needn't be, and frankly in the general case probably aren't, squares.
Once you accept the latter, that it's easy to obtain only two of the four shapes isn't that far of a leap. Note too that of the four polygons that those four points define, three of them are really big. So, I'm guessing that SQL has a heuristic that is able to figure out which of those is smallest because frankly that's what's more likely to be defined in a real world application. Then, depending on the order in which the points were defined, you either get that polygon (i.e. the smallest of the four) or its complement.
Like Ben, I can't explain what's happening. I double, treble and even quadruple checked the coordinate order and couldn't find anything to determine why it creates the globe with a hole (essentially). Your order was SW -> SE -> NE -> NW -> WS - exactly as I would have done it and I suspect this may be a bug on SQL's part.
One thing that did immediately alarm me however was that those coordinates were not for that view on a google map - trust me - I've been working with them far too long. By estimation, your coordinates should have been something nearer:
SW: Long 78, Lat 7
NE: Long 44 Lat 83.
Your google map showing the markers showed why your ReorientObject() call actually produced the correct result (even though you shouldn't have needed to reorient anything).
Never-the-less, there is a better way to create the bounding box here:
-- Define NE
declare #ne geography = geography::Point(83.4871233224387, 63.5599894667969, 4326);
- Define SW
declare #sw geography = geography::Point(-3.62943382709019, 86.0599894667969, 4326);
-- Define BOX
declare #box geography = geography::STGeomFromWKB(geometry::STGeomFromWKB(#ne.STUnion(#sw).STAsBinary(), 4326).STEnvelope().STAsBinary(), 4326);
select #box -- shows polygon
select #box.ToString -- shows WKT
It's interesting to see that the coordinate order is defined as if the geography were a hole - it goes SE -> SW -> NW -> NE -> SE, keeping the interior on the right. This would be right for GEOMETRY, but not GEOGRAPHY and yet it produces the right answer. This is why I think it may be a bug? - Or I'm missing something.
Lastly, to explain creating the box:
Take the union of both points (creating a single geometry of MULTIPOINT type)
Convert the MULTIPOINT to geometry type
Use the geometry type's STEnvelope() method to create a bounding box containing the extremities
Convert to BOX (POLYGON) back to geography type
I hope that helps you (and anyone else).
Related
I can calculate the distance between two points using:
SELECT ST_Distance(
ST_GeomFromText('SRID=4326;POINT(54.5972850 -5.930119)')
, ST_GeomFromText('SRID=4326;POINT(54.516827 -5.958130)'),
false);
However, my goal is to create a rough circular zone (this can be square, hexagon, octagon .etc) around each point and then check if the zones overlap.
I am looking at ST_Overlaps as a possible solution but I am not sure how to convert these points into polygons to be compared. My ideal result would be something like:
SELECT ST_Overlaps(
ST_CreateCircularPolygon(geom1, 1000, 6)
ST_CreateCircularPolygon(geom2, 10000, 4)
);
Where:
ST_CreateCircularPolygon(geomerty, metreRadius, numberOfRadialPoints (e.g. 6 creates a hexagonal polygon))
Any guidance would be much appreciated!
You can use the quad_seg parameter of st_buffer to specify the number of segments per quarter of a circle. That is, the total number of segments in the output will be a factor of 4.
To produce a square:
select st_asText(st_buffer(st_geomFromText('Point(10 10)'), 1, 'quad_segs=1'));
st_astext
------------------------------------------------------
POLYGON((11 10,10 9,9 10,9.99999999999999 11,11 10))
(1 row)
Octagon:
select st_asText(st_buffer(st_geomFromText('Point(10 10)'), 1, 'quad_segs=2'));
st_astext
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
POLYGON((11 10,10.7071067811865 9.29289321881345,10 9,9.29289321881345 9.29289321881345,9 10,9.29289321881345 10.7071067811865,9.99999999999999 11,10.7071067811865 10.7071067811866,11 10))
(1 row)
Since you want to work in meters but have unprojected coordinates, you can cast your geometry to geography, apply a buffer in meters and cast back to geometry. Let's note that st_buffer in geography will internally cast to a geometry in UTM, do the buffer, then cast back to geography (a lot of casting, but it's handy!)
That being said, a square is not a circle and it sounds very very wrong to assume otherwise. The orientation of the square is not obvious: should a corner be at the north? or should a segment be facing norht? or should the square be rotated? by how much?
You will save yourself a lot of trouble by using a real circle. In this case, don't use st_buffer at all, nor st_distance but rather st_dwithin which can leverage spatial indexes
I understand that you cannot check if a geography point touches for example a geography polygon (see here) using something like this, which is possible in the geometry world:
WHERE A.Geo.STTouches(#s0) = 1
Is there a way to achieve this in the geography world? I guess a solution would be to transform the geography polygon + geography point into their geometry equivalents?
PS:
This should also work with complex polygons - such as this one with holes:
POLYGON ((99.999999999999986 5.3926387744713564E-14, 101.00000000000001 5.3926387744713564E-14, 100.99999999999987 1.0000000000000155, 100.00000000000013 1.0000000000000155, 99.999999999999986 5.3926387744713564E-14), (100.20000000000003 0.19999999999992918, 100.19999999999989 0.79999999999990257, 100.80000000000011 0.79999999999990234, 100.79999999999998 0.19999999999992912, 100.20000000000003 0.19999999999992918))
I'm assuming that my statement of your wanting to track whether or not the given point intersects with the boundary of your polygon. The below should do the trick:
SELECT #point.STIntersects(#polygon.RingN(1));
In essence, you're getting the outer boundary with RingN(1) (I'm making an assumption that this is a simple polygon, so the first ring should be the outer boundary) and then checking whether that intersects with the point you care about.
Edit: If you want to check if a given point lies on the boundary of any ring in the given polygon, something like this should do the trick:
select n.n as [IntersectedRingNumber],
#polygon.RingN(n) as [IntersectedRing]
from dbo.Numbers as n
where n <= #polygon.NumRings()
and #point.STIntersects(#polygon.RingN(n)) = 1;
So I have a polygon representing a rectangular bounding box covering a geographic wiht lat/lng coordinates using a kilometer based unit system.
The user wan't the application to find all matching objects within the bounding box + say 10 kilometers from outside any edge. Finding objects within the box is fine, but how do I expand it on all 4 sides by the specified number of kilometers?
Update:
Used the solution below with the following observations:
A negative number is required to make the bounding box expand. The purple box is after the buffering. The green box is the original bounding box. The red box is the original viewport.
I believe the results in SQL are also the wrong orientation since it finds results in London etc. Perhaps a bug in EF as I have to invert the lat and lng values to get Google maps to draw this polygon the right way up.
Take a look at the STBuffer method. It would go something like this:
declare #g geography;
--set #g to your bounding box here
set #g = #g.STBuffer(10000);
select *
from dbo.yourTable
where #g.STContains( yourGeographyColumn );
I'm facing a problem regarding the order of the lat/lng in the following query, it works if i put the coordinates in the wrong order (lng,lat). However geography::Point should take the lat then lng, this is very weird. I tried checking the lat/lng on google maps, and its in the correct position, the polygone in a clockwise order, everything looks fine, the query is as follows:
DECLARE #branch_id int = 0;
DECLARE #point geography;
SET #point = geography::Point(31.3353608924066, 30.0798141318826, 4326);
SELECT B.ID, B.DeliveryZone, A.LatLong
FROM [dbo].[Branches] AS B
INNER JOIN [dbo].Addresses AS A ON B.AddressID = A.ID
WHERE B.ServiceProviderID = 2 AND
B.Deleted = 0 AND
B.DeliveryZone.STBuffer(1000).STIntersects( #point ) > 0 ;
The SQL Server documentation for Point is inconsistent. It says:
Lat - Is a float expression representing the x-coordinate of the Point being generated.
Latitude is generally used as a y-coordinate, that is, how far north or south you are from the equator.
The same inconsistency is given for Long:
Long - Is a float expression representing the y-coordinate of the Point being generated.
Of course, longitude is usually intepreted as an x-coordinate, that is, how far east or west you are from the prime meridian.
Also, lat and lng aren't used by all SRIDs, just ones that define unprojected coordinate systems like 4326, so "Lat" and "Long" are poor choices for generic parameter names in the documentation.
Based on these issues in the docs, it's most likely that for EPSG 4326, the database expects coordinates in (x,y) order, so (lng, lat), not (lat, lng). This also is consistent with the way most other spatial systems define coordinates in EPSG 4326.
I'm playing with the new geography column in SQL Server 2008 and the STGeomFromText function. Here is my code (works with AdventureWorks2008)
DECLARE #region geography;
set #region = geography::STGeomFromText('POLYGON((
-80.0 50.0, -90.0 50.0,
-90.0 25.0, -80.0 25.0,
-80.0 50.0))', 4326);
SELECT #region;
My question is about the 4326 in the code. It is supposed to be a spacial Reference ID. When I go to MSDN there isn't a lot on it. If I change the value to 56 I get an error telling me the value must be in the sys.spatial_reference_systems table.
You can look at that table by executing:
select * from sys.spatial_reference_systems
There is a well_known_text column in that table, but it doesn't tell me much. The value for 4326 is:
GEOGCS["WGS 84", DATUM["World Geodetic System 1984", ELLIPSOID["WGS 84", 6378137, 298.257223563]], PRIMEM["Greenwich", 0], UNIT["Degree", 0.0174532925199433]]
Can anyone explain this mystery to me? What is the SRID?
So I ended up talking with an ex-military guy yesterday who was a radar/mapping specialist.
Basically, he knew exactly what that number (4326) was, where it came from, and why it is there.
It is an industry standard for computing geography. The problem is that the earth is not a perfect sphere (it bulges in the middle), and SRID 4326 accounts for that.
As I stated, the table sys.spatial_reference_systems lists all of the code and what they are. But the short version is that you are really only going to use 4326 unless you have a very specific reason to use something different.
SRID = Spatial Reference IDentifier
coordinates must use the same SRID to be comparable. otherwise you'd end up comparing kilometeres and miles. or something similar.
There are a lot of systems to map the earth. For example you want to map some state in USA. You can set the most south-east point as 0,0 and map all other spatial coordinates according to this point. On the other hand you may want to map some spatial data that span all over the map. In any case you must choose some point as 0,0. In addition you must select some sort of measurement unit: miles/kilometers/degrees/some other magical unit that suits you better. Over the years a lot of such systems where developed. Each has its own zero point, its own coordinates, its own rules about if the earth is flat or not. SRID or SRS is the id of such system. Using this id you can map point expressed in one system to another system, although sometimes it involves some pretty complex math.
And about 4326 SRID. It also called "WGS 84"
(http://en.wikipedia.org/wiki/World_Geodetic_System) system. It's the most common system to represent point on spherical(not flat) earth. It uses degree,minute,second notation and its x and y coordinates are usually called latitude and longitude.
Most used non-spherical earth projection is called UTM. You can read about it here: http://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system
Anyway, as long you are not doing any spatial conversions from one system to other, you don't really care about the system that you data uses.
I have found this website: http://spatialreference.org/ref/epsg/4326/ quite helpful in understanding the SRID you intend to use. It provides a handy map, some bounding box information and other links.
For other SRIDs simply change the digits at the end of the URL to what you are after.
The distance returned depends on the "Spatial Reference Identifier (SRID)" you define for your geography types.
In the example below, the default SRID of 4336 is used, see the second argument of STGeomFromText. This means the distance returned is in meters, you find this via querying the catalog view spatial_reference_systems i.e. select srs.unit_of_measure from sys.spatial_reference_systems as srs where srs.spatial_reference_id = 4326
As an alternative to STGeomFromText, you can use parse which assumes a SRID of 4326 and you don't have to specify one explicitly.
When calculating the distance between two points, you must use the same SRID for both geography types else you get an error. Example:
DECLARE #address1 GEOGRAPHY
DECLARE #address2 GEOGRAPHY
DECLARE #distance float
SET #address1 = GEOGRAPHY::STGeomFromText ('point(53.046908 -2.991673)',4326)
SET #address2 = GEOGRAPHY::STGeomFromText ('point(51.500152 -0.126236)',4326)
SET #distance = #address1.STDistance(#address2)
SELECT #distance --this is the distance in meters