Given a point, how can I query SQL Server to find the stored polygons that encompass it?
I have a database table that has the polygons of all 50 US states. I need a query that will allow me to search for which states are within 90 miles of that point.
Here is my table structure and data for three states:
CREATE TABLE dbo.States (
State varchar(20) NOT NULL,
CoordinatorEmail varchar(255) NULL,
Borders geography(-1) NULL
);
INSERT INTO States (State, Borders)
VALUES ('Alaska', (geography::STGeomFromText('POLYGON((-141.0205 70.0187,-141.7291 70.1292,-144.8163 70.4515,-148.4583 70.7471,-151.1609 70.7923,-152.6221 71.1470,-153.9954 71.1185,-154.8853 71.4307,-156.7529 71.5232,-157.9449 71.2796,-159.6313 71.2249,-161.8671 70.6363,-163.5809 70.0843,-165.2399 69.3028,-166.8768 69.1782,-168.0414 68.3344,-165.9155 67.6844,-164.6082 67.2933,-164.0149 66.7789,-165.7507 66.5810,-167.5745 66.2867,-168.9862 66.0269,-168.9478 65.4970,-167.4756 65.0420,-167.0142 64.3922,-165.7343 64.0554,-163.2294 64.0193,-162.1143 63.9615,-163.6029 63.6877,-165.3717 63.4530,-166.3715 62.4133,-166.9867 61.6534,-166.4429 60.8556,-167.8381 60.5357,-167.7118 59.5482,-165.8002 59.4115,-164.5972 59.3696,-162.8558 59.1168,-162.5427 58.1185,-160.6421 58.1359,-159.5050 58.0285,-158.8953 57.6336,-159.9060 56.9090,-160.6531 56.3926,-161.8835 56.2342,-162.9822 55.7240,-164.3994 55.2478,-165.3168 54.7753,-167.1075 54.1463,-168.5852 53.5632,-169.9146 53.1402,-169.5959 52.5964,-168.2227 52.9089,-162.7734 54.2139,-159.1452 54.6786,-155.4634 55.6567,-152.1400 57.3510,-150.8203 59.2209,-147.4461 59.7695,-145.9850 60.3521,-144.1544 59.8917,-141.6811 59.8172,-140.5124 59.5225,-138.8548 59.0292,-136.8526 57.9032,-136.0725 56.9157,-134.9794 56.1555,-134.0057 55.3237,-133.6418 54.6341,-130.6261 54.7135,-129.9930 55.2869,-130.0108 55.9869,-130.1083 56.1057,-131.5887 56.6086,-132.8755 57.8404,-133.8423 58.7276,-134.9121 59.3108,-135.4724 59.8020,-136.3445 59.6039,-136.8251 59.1619,-137.6079 59.2441,-139.2119 60.0902,-139.0938 60.3575,-140.0056 60.1866,-140.9999 60.3059,-141.0205 70.0187,-141.0205 70.0187))', 4326)));
INSERT INTO States (State, Borders)
VALUES ('Alabama', (geography::STGeomFromText('POLYGON((-88.1955 35.0041,-85.6068 34.9918,-85.1756 32.8404,-84.8927 32.2593,-85.0342 32.1535,-85.1358 31.7947,-85.0438 31.5200,-85.0836 31.3384,-85.1070 31.2093,-84.9944 31.0023,-87.6009 30.9953,-87.5926 30.9423,-87.6256 30.8539,-87.4072 30.6745,-87.3688 30.4404,-87.5240 30.1463,-88.3864 30.1546,-88.4743 31.8939,-88.1021 34.8938,-88.1721 34.9479,-88.1461 34.9107,-88.1955 35.0041))', 4326)));
INSERT INTO States (State, Borders)
VALUES ('Arkansas', (geography::STGeomFromText('POLYGON((-94.0416 33.0225,-91.2057 33.0075,-91.1989 33.1180,-91.1041 33.1824,-91.1343 33.3053,-91.1646 33.4211,-91.2263 33.4337,-91.2524 33.5403,-91.1797 33.6112,-91.2524 33.6855,-91.1261 33.6946,-91.1412 33.7883,-91.0451 33.7700,-91.0341 33.8328,-91.0863 33.9399,-90.9256 34.0208,-90.9036 34.0856,-90.9586 34.1345,-90.9132 34.1675,-90.8501 34.1380,-90.9325 34.2311,-90.6935 34.3446,-90.5603 34.4409,-90.5548 34.5348,-90.5768 34.5959,-90.5301 34.7213,-90.5328 34.7574,-90.4546 34.8780,-90.3529 34.8454,-90.2911 34.8690,-90.3104 35.0255,-90.2843 35.1154,-90.1772 35.1323,-90.1112 35.1985,-90.1524 35.2826,-90.1332 35.4383,-90.0206 35.5579,-89.9780 35.6740,-89.9547 35.7287,-89.6594 35.9169,-89.6883 35.9658,-89.7130 36.0013,-90.3735 35.9958,-90.2664 36.1268,-90.0934 36.2875,-90.0742 36.3892,-90.1511 36.4180,-90.1566 36.4997,-94.6198 36.4986,-94.4412 35.3801,-94.4893 33.6318,-94.4522 33.6421,-94.4000 33.5597,-94.2462 33.5883,-94.1885 33.5872,-94.0375 33.5345,-94.0430 33.4314,-94.0430 33.0213,-94.0416 33.0225))', 4326)));
I have been trying the following query just to make sure I can get the state of the point (without yet worrying about the 90 mile radius), but I haven't figure this part out yet.
DECLARE #LittleRock geography;
SET #LittleRock = geography::Point(34.742000, -92.276543, 4326);
Select State from States
where States.Borders.STIntersects(#LittleRock) = 1;
SELECT State from States
where States.Borders.STContains(#LittleRock) = 1;
Neither STIntersets() nor STContains() returns anything. Thoughts?
When I ran your code on my local instance, I got the following error (abbreviated):
This operation cannot be completed because the instance is not valid.
Use MakeValid to convert the instance to a valid instance.
Next, I did what the robot told me to do:
UPDATE dbo.States
SET Borders = Borders.MakeValid();
Once that was done, I was able to determine that Little Rock is indeed in Arkansas with both STIntersects() and STContains(). So there was something malformed in at least one of your geography instances.
EDIT
Given the OPs full data set, it has another issue with some of the states. Specifically, 21 states appear to have a ring orientation problem. With Geography polygons, the order in which you specify the vertexes is important. I never remember whether clockwise or counter-clockwise is the right direction. But I do remember the heuristic I use. If the envelope angle for the polygon is greater than 90°, I probably got it wrong. Luckily, that is also easy to correct for.
UPDATE s
SET s.Borders = s.Borders.ReorientObject()
FROM dbo.States AS s
WHERE s.Borders.EnvelopeAngle() > 90
With the raw data set, your Little Rock query returns 22 states. With the update above run, it returns only Arkansas.
Does anybody know what is the problem with my query. I am trying to calculate area using geographical coordinates, but result seems to be too small to be true. 0.00118 sqm. Can anybody help?
SELECT ST_Area(the_geom) As sqm
FROM (SELECT
ST_GeomFromText('POLYGON
(
(14.604514925547997 121.0968017578125,
14.595212295624522 121.08512878417969,
14.567302046916149 121.124267578125,
14.596541266841905 121.14761352539062,
14.604514925547997 121.0968017578125)
)',4326) ) As foo(the_geom)
How accurate should be the calculation?
A solution is to cast GEOMETRY to GEOGRAPHY, which is acceptably accurate for the most use cases:
SELECT ST_Area(the_geom::GEOGRAPHY ) As sqm
FROM (SELECT
ST_GeomFromText('POLYGON
(
(14.604514925547997 121.0968017578125,
14.595212295624522 121.08512878417969,
14.567302046916149 121.124267578125,
14.596541266841905 121.14761352539062,
14.604514925547997 121.0968017578125)
)',4326) ) As foo(the_geom)
The geography type automatically converts degrees to meters.
Depending on your scenario you could also use directly the geography constructor St_GeographyFromText, which accept a WKT string as argument, very similar to ST_GeomFromText
ST_GeographyFromText('POLYGON((14.604514925547997 121.0968017578125,
14.595212295624522 121.08512878417969,
14.567302046916149 121.124267578125,
14.596541266841905 121.14761352539062,
14.604514925547997 121.0968017578125))'
)
I have a collection of lat/long pairs that specify the coordinates of property. An example of such would be this:
(-97.7157864909875 30.406523562241, -97.7165355983733 30.4068866164923, -97.716787930159 30.4064954568619, -97.7168253595963 30.4065335493939, -97.7168568837126 30.4065754656014, -97.7168819885038 30.4066205220471, -97.716901410079 30.4066619534041, -97.7169158736828 30.4066896672646, -97.7169327718411 30.4067163205123, -97.716952004751 30.4067417571518, -97.7169734604111 30.4067658281043, -97.7176225812739 30.4057595267431, -97.7168902352123 30.4054046115321, -97.7164489526149 30.4060840883432, -97.7163106372336 30.4061380633337, -97.7158545237879 30.4059170368804, -97.7158294889129 30.4060371739058, -97.7158101406138 30.4061580975546, -97.7157965112558 30.4062796056817, -97.7157886236446 30.4064014951642, -97.7157864909875 30.406523562241)
Lets say I have maybe 30K or so of these in a table.
Then I have a single lat/lon pair and I need to know which of the polygons (could be more than one) does this point fall inside of.
I have very limited experience with spatial types and I am trying ensure I can properly define indexes on this to gain the speed I need with this query.
Any help you can offer to get me headed in the right direction is greatly appreciated.
I believe I can do it like this:
DECLARE #CurrentPosition geography
declare #Line geography
DECLARE #result geography
SET #CurrentPosition = geography::STGeomFromText('POINT(lat lon)', 4326)
set #Line = geography::STGeomFromText('LINESTRING(point collection)', 4326)
SELECT #result = #Line.STIntersection(#CurrentPosition)
select #result.ToString()
This will help with the actual is it inside the line or not - but the question I am struggling with is how to define the selection statement to perform the above transaction when I am searching the table for the items that intersect.
Thanks
I am using EF with a MSSQL DB and I am struggling with a spatial query:
I am using the user location to search for restaurants ordered by distance here is the code:
DbGeography userLocation = DbGeography.FromText(userLocationWKT, 4326);
return (from branch in DbContext.Branches
let distance = userLocation.Distance(branch.Location)
orderby distance ascending
where branch.Name.ToLower().Contains(searchString.ToLower()) && branch.Location != null
select new BranchAndDistance { Branch = branch, Distance = distance }).ToList();
the user location WKT is POINT (32.78786115814 35.0162102747709). In my test case I am sending a query that returns one restaurant with Lat=32.1300848 & Long=34.7919443 But the distance I am getting is 370237 meters which is way off.
The next image shows the QuickWatch window for the restaurant location details:
Notice that in the ProviderValue text it shows POINT (34.7919443 32.1300848) as if it replaces the Lat and Long but the actual values seem OK
So I conducted some more tests, since I am holding the lat and long values in separate variables I could test the following:
double? distance = Branch.Location.Distance(userLocation);
DbGeography branchLocation = DbGeography.FromText(string.Format("POINT ({0} {1})",
Branch.Latitude,.Branch.Longitude), 4326);
double? distance2 = branchLocation.Distance(userLocation);
The distance variable had the same incorrect distance value of 370237.85926851362
but the distance2 variabel had the correct distance value of 65061.945208184392
How can this be?
I have also checked in the SQL Azure database managment and according to that the Location property (which yields an incorrect distance result) is spot on
Have I missed something? What am I doing wrong???
Your WKT for your user is not what you think it is. Specifically, that is a point that has (latitude, longitude) = (35.0162102747709, 32.78786115814). Here's a little T-SQL to show that:
DECLARE #g geography;
SELECT #g = geography::STPointFromText('POINT (32.78786115814 35.0162102747709)', 4326);
SELECT #g.Lat, #g.Long;
Environment: SQL Server 2012
I'm using an online tool, the only one I could find so far, to plot polygons and points on the earth. http://www.birdtheme.org/useful/googletool.html
I have two tables. One stores "areas" as polygons and the other table stores points amongst other things irrelevant to my question.
For simplicity, I'll just reduce my scenario to sql variables.
In the query below, I'm using the geography data type for well known points of interest.
I drew a polygon around Robben Island, a point in Robben Island and a point in Alcatraz.
DECLARE #robben_island geography = ('POLYGON((18.351803 -33.788421,18.382788 -33.787494,18.386736 -33.820515,18.354464 -33.822369,18.351803 -33.788421))')
DECLARE #point_in_robben_island geography= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geography= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'False', but it's not what I expected
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'True', but it's not what I expected
This query above, if I understand it correctly, tells me that my #point_in_robben_island is not contained in #robben_island, rather my #point_in_alcatraz exists in #robben_island which as we all know, is not true.
Now when I change the data types from geography to geometry, everything works fine, but I'm afraid that if I continue using the geometry data type I might come across a few gotchas. I'm just wondering if I won't be negatively affected by fact that geometry doesn't quite account for earth's curvature. touch wood.
DECLARE #robben_island geometry = ('POLYGON((18.351803 -33.788421,18.382788 -33.787494,18.386736 -33.820515,18.354464 -33.822369,18.351803 -33.788421))')
DECLARE #point_in_robben_island geometry= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geometry= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'True' as it should
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'False' as it should
Now my question is, why does the geography data type return unexpected results while geometry works as expected? Thank you very much.
The geography type is a little bit more restrictive than geometry. It can't cross different hemispheres and the outer ring must be drawn counter-clockwise.
Unfortunately (some find this a good thing), SQL Server 2012 no longer throws an error when you create the invalid geography. You need to invert the order of the points in the Roben Island geometry, like:
DECLARE #robben_island geography = ('POLYGON((18.351803 -33.788421, 18.354464 -33.822369,18.386736 -33.820515, 18.382788 -33.787494, 18.351803 -33.788421))')
DECLARE #point_in_robben_island geography= ('POINT(18.369226 -33.80554)')
DECLARE #point_in_alcatraz geography= ('POINT(-122.423401 37.827006)')
SELECT #robben_island.STContains(#point_in_robben_island) --returns 'True'
SELECT #robben_island.STContains(#point_in_alcatraz) --returns 'False'