I'm working on migrating a .Net framework application to .Net Core and I need to support running on Linux.
The application needs to calculate the intersection of polygons and very long lines on the Earths surface, and so it uses Geography objects as apposed to Geometry to take into account the Earth's elliptical shape.
For this we use Microsoft.SqlServer.Types, which lets us do the following:
// Line from New York to Paris
SqlGeography line = SqlGeography.STGeomFromText(new System.Data.SqlTypes.SqlChars("LINESTRING(40.730610 -73.935242, 48.864716 2.349014)"), 4326);
// Polygon in the Atlantic
SqlGeography polygon = SqlGeography.STGeomFromText(new System.Data.SqlTypes.SqlChars("POLYGON((60 -40, 60 -20, 30 -20, 30 -40, 60 -40))"), 4326);
// Contains the two locations where the line intersects with the polygon
SqlGeography intersection = line.STIntersection(polygon);
The problem is that Microsoft.SqlServer.Types only works on Windows. How can I get the same result in a way that will also compile and run on Linux?
I've looked into NetTopologySuite but it seems to only support geometry calculations
Not sure if you are using EFCore or the NetTopology Suite for handling geography data in your project - but all these are already supported in that package.
Microsoft
Nuget
As you are using the WellKnownText format you can use the Docs from here:
Github Docs
I can't post the source code of my solution but an example of how to use it would be:
public class GeographyHelper
{
private static GeometryFactory _geometryFactory
{
get
{
return NetTopologySuite.NtsGeometryServices.Instance.CreateGeometryFactory(4326);
}
}
public bool TestIntersectsAPolygon(double latitude, double longitude)
{
var wellKnownText = "YOUR POLYGON WKT";
var point = _geometryFactory.CreatePoint(new Coordinate(longitude, latitude));
var rdr = new NetTopologySuite.IO.WKTReader();
var geometry = rdr.Read(wellKnownText);
var polygon = _geometryFactory.CreatePolygon(geometry.Coordinates);
var intersects = polygon.Intersects(point);
return intersects ;
}
}
Related
I am using the Angus Johnson's Clipper Library in .NET/C# and I would like to do several concentric offset on a Polygon with the ClipperOffset class. (http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Classes/ClipperOffset/_Body.htm).
I want to fill my polygon like the kangaroo (from Angus Johnson's Homepage).
I have written a function to do that, but it needs a big calculation time if I call it 10 times for example :
static public Polygon doOffset(Polygon p, double offset_nm)
{
// Offset to grow up the forbidden polygon
Polygons solution = new Polygons();
ClipperOffset co = new ClipperOffset();
co.AddPath(p, JoinType.jtRound, EndType.etClosedPolygon);
co.Execute(ref solution, offset_nm);
return solution[0];
}
Do you know a more efficient way to do that ? Thanks.
Kangaroo
I have two GPS Coordinates i wanted to calculate distance between those but the result on SQL server is entirely different from result in c# I googled and found that the both approaches return distance in meters but this difference is driving me crazy.
SQL SERVER Query
select geography::Point(25.3132666, 55.2994054 ,4326)
.STDistance(geography::Point(25.25434, 55.32820,4326)) as Distance;
Web API
String format = "POINT(25.25434 55.32820)";
DbGeography myLocation = DbGeography.PointFromText(format, 4326);
var users = context.Users.Select(u => new
{
fullName = u.name,
lat = u.location.Latitude,
lng = u.location.Longitude,
distance = myLocation.Distance(u.location)
}).ToList();
Response
,{"fullName":"jons smith","lat":25.3132666,"lng":55.2994054,"distance":4133581.8647264037}
Thanks in advance.
Check latitude and longitude order in the WKT representation of the point in SQL when defining the point, they are as follow GEOGRAPHY::POINT(Latitude, Longitude, srid):
SELECT GEOGRAPHY::Point(25.3132666,55.2994054 ,4326)
.STDistance(GEOGRAPHY::Point(25.25434, 55.32820,4326)) as distance;
//distance: 7142.94965953253
But when defining DBGeography in C# code, the order is different:"POINT(Longitude Latitude)"
String format = "POINT(55.32820 25.25434)";
DbGeography myLocation = DbGeography.PointFromText(format, 4326);
var users = context.Users.Select(u => new
{
fullName = u.name,
lat = u.location.Latitude,
lng = u.location.Longitude,
distance = myLocation.Distance(u.location)
}).ToList();
//distance: 7142.949659532529
Also you should check those locations inserted in the Users table. Make sure when inserting they have been correctly inserted. Otherwise they will be somewhere else not the location of your Users
More:
SELECT GEOGRAPHY::Point(25, 55 ,4326).Lat //25
DbGeography.PointFromText("POINT(25 55)", 4326).Latitude.Value //55
Microsoft.SqlServer.Types.SqlGeography.STPointFromText(
new System.Data.SqlTypes.SqlChars("POINT(25 55)"), 4326).Lat.Value;
//55
What is WKT and where does it come from?
It's a Well-Known Text representation of different geometry types that is introduce by OGC in the Simple Feature Access specification and all software vendors are advised to follow it in the sake of the compatibility. This specification shows us how to define point, line (linestring), polygon and some other geometry types as text (WKT) and binary (WKB).
SQL Server do not completely follow this specification and we see the result of not following standards, causes such problems in different components of even the same company.
Switch the Lat/Lng in in API Version. In the API, it should go Lng Lat
select geography::Point(25.3132666, 55.2994054 ,4326).STDistance(geography::Point(25.25434, 55.32820,4326)) as Distance
Returns
7142.94965953253
This is where I flipped one Lat/Lng usinq my UDF
Select [dbo].[udf-Geo-Meters](55.2994054,25.3132666 ,25.25434,55.32820)
Returns
4135883.9028193
The issue is very minor but tricky
SQL SERVER Query
geography::Point(25.3132666, 55.2994054 ,4326)
SQL Server defines a point such that first value is latitude and second value is longitude.
Web API
String format = "POINT(25.25434 55.32820)";
DbGeography myLocation = DbGeography.FromText(format);
C# defines a point from above format in such a way that first value is longitude and second value is latitude
I need to convert UTM to DMS. For example: x 6518585.31 y 13343143.32 -> degrees minutes seconds.
People refer to this library esri.arcgis.defensesolutions.dll, but I can't find where to download it.
Try the ArcGIS Runtime SDK for .NET. You can download and use it at no cost for coordinate conversion. Sample code is available, but here's the relevant code for what you need to do. You can use http://spatialreference.org/ref/ to find the WKID for your UTM zone; I'm using 32642, which is the WKID for UTM zone 42N based on WGS84.
var utmSpatialReferenceWkid = 32642;//UTM zone 42N based on WGS84
var pointUtm = new MapPoint(6518585.31, 13343143.32, utmSpatialReferenceWkid);
var pointLonLat = GeometryEngine.Project(pointUtm, SpatialReference.Wgs84);
var longitude = pointLonLat.X;
var latitude = pointLonLat.Y;
The defensesolutions DLL you mentioned is older technology, and you need an ArcGIS Desktop or Engine license to use it, which incurs a cost. Use ArcGIS Runtime instead.
coordinate system "Meters" to "Degrees Minutes Seconds"
ISpatialReferenceFactory srEnv = new SpatialReferenceEnvironmentClass();
var wgsIn = srEnv.CreateESRISpatialReferenceFromPRJFile(#"C:\111.prj");
var wgsOut = srEnv.CreateESRISpatialReferenceFromPRJFile(#"C:\222.prj");
var point = new PointClass();
point.PutCoords(3304534.9530999996, 6859385.3066000007);
point.Project(wgsIn);
var dmsCoord = new DMSCoordinate
{
Precision = esriCoordinatePrecision.esriCPOneMeter,
InputSpatialReference = wgsIn,
OutputSpatialReference = wgsOut,
Point = point
};
var dsd = dmsCoord.String;
dmsCoord.String Returns incorrect coordinates
You can use CoordinateSharp for exactly this.
Example
UniversalTransverseMercator utm = new UniversalTransverseMercator("T", 32, 233434, 234234);
Coordinate c = UniversalTransverseMercator.ConvertUTMtoLatLong(utm);
Console.WriteLine(c); //Outputs DMS formatted coordinate by default.
You can also access individual lat/long properties within the Coordinate object.
A little about the application;
The application allows the user to draw and save polygons onto bing maps WPF api.
The piece of code we are interested in is finding weather a point is whithin the polygon or not. The following function simply loops through the LocationCollection of the polygon on the bing map, and creates a SqlGeography object (OpenGisGeographyType.Polygon) which is an instance of a polygon.
We then convert the mouse click into SqlGeography object (OpenGisGeographyType.Point) by latitude and longitude and use the SqlGeography.STIntersection to find if our point lies within the polygon.
As seen in the picture, even when the point is outside the polygon, SqlGeography.STIntersection still returns a point of intersection. (You can tell this in the picture as I set a label to "Within Delivery Area" or "Customer our of Area" depending on what polygonSearch() function returned.
The right example in the picture has the expected results when a location is tested within the polygon.
The left example in the picture contains the unexpected results - Which indicates that a point is within a polygon , when it clearly is not!
NOTES:
I Use SqlGeography (myShape) to put the shapes on the map, so I know
the shape is being constructed with proper verticies.
I use SqlGeography (myPoint) to put the pin on the map, so I know the
pin is being tested at the correct verticies.
THIS ONLY FAILS ON LARGE POLYGONS
Below I give the peice of code which creates the polygon in memory, as well as converts the mouse click event to lat, longitude. ( I have included the polygon verticies in comments so that this can be looked at without the need of the bing api, just replace the for loop with the comments above it ) Although, you will need to reference Microsoft.SqlServer.Types.dll to create the SqlGeography objects. Its free with SQL Express 2008 , and can be found in C:\Program Files (x86)\Microsoft SQL Server\100\SDK\Assemblies
public bool polygonSearch2(LocationCollection points, double lat, double lon)
{
SqlGeography myShape = new SqlGeography();
SqlGeographyBuilder shapeBuilder = new SqlGeographyBuilder();
// here are the verticies for the location collection if you want to hard code and try
//shapeBuilder.BeginFigure(47.4275329011347, -86.8136038458706);
//shapeBuilder.AddLine(36.5102408627967, -86.9680936860962);
//shapeBuilder.AddLine(37.4928909385966, -80.2884061860962);
//shapeBuilder.AddLine(38.7375329179818, -75.7180936860962);
//shapeBuilder.AddLine(48.0932596736361, -83.7161405610962);
//shapeBuilder.AddLine(47.4275329011347, -86.8136038458706);
//shapeBuilder.EndFigure();
//shapeBuilder.EndGeography();
// Here I just loop through my points collection backwards to create the polygon in the SqlGeography object
for (int i = points.Count - 1; i >= 0; i--)
{
if (i == 0)
{
shapeBuilder.AddLine(points[i].Latitude, points[i].Longitude);
shapeBuilder.EndFigure();
shapeBuilder.EndGeography();
continue;
}
if (i == points.Count - 1)
{
shapeBuilder.SetSrid(4326);
shapeBuilder.BeginGeography(OpenGisGeographyType.Polygon);
shapeBuilder.BeginFigure(points[i].Latitude, points[i].Longitude);
continue;
}
else
{
shapeBuilder.AddLine(points[i].Latitude, points[i].Longitude);
}
}
myShape = shapeBuilder.ConstructedGeography;
// Here I am creating a SqlGeography object as a point (user mouse click)
SqlGeography myPoint = new SqlGeography();
SqlGeographyBuilder pointBuilder = new SqlGeographyBuilder();
pointBuilder.SetSrid(4326);
pointBuilder.BeginGeography(OpenGisGeographyType.Point);
// Should pass, which it does
// Lat: lat = 43.682110574649791 , Lon: -79.79005605528323
// Should fail, but it intersects??
// Lat: 43.682108149690094 , Lon: -79.790037277494889
pointBuilder.BeginFigure(lat, lon);
pointBuilder.EndFigure();
pointBuilder.EndGeography();
myPoint = pointBuilder.ConstructedGeography;
SqlGeography result = myShape.STIntersection(myPoint);
if (result.Lat.IsNull)
return false;
else
return true;
}
Any help at all is much appreciated, I am starting to drive my boss nuts with this problem >.<
Could this have anything to do with the SRID?
I fixed this by converting all my polygon lat / long to a Point object on the screen using LocationToViewPortpoint function, as well as the point I'm testing for intersection, and use the X and Y values instead of lat / long in my STIntersects.
IPoint pPoint = new ESRI.ArcGIS.Geometry.PointClass();
pPoint.PutCoords(-92.96000, 44.9227); //This should be near Minneapolis
mapControl.CenterAt(pPoint); //mapControl is a AxMapControl
When I run this code the point always ends up near Kansas. Can anyone help me convert lat / longs to an PointClass that will work properly?
I'm using VS2010 ArcEngine 10 C#
There is a lot more to this than you have currently given. Both a lat/long point and your map have a specific spatial reference. If they do not match, it is likely your point will plot in an unexpected way.
The point you are showing is a standard Latitude/Longitude point. Which is likely Nad83 (North American), or WGS84 (World). These are Spatial References with a Geographical Coordinate System. You are likely trying to plot the point on a Projected Coordinate System.
You need to make your MapControl's Spatial Reference match the types of points you are trying to plot.
Since I do not know the Spatial Reference of your Map, I can only give you an example of translating a Lat/Lon into what the MapControl's current spatial reference is.
ISpatialReferenceFactory srFactory = new SpatialReferenceEnvironmentClass();
IGeographicCoordinateSystem gcs = srFactory.CreateGeographicCoordinateSystem((int)esriSRGeoCSType.esriSRGeoCS_WGS1984);
ISpatialReference sr1 = gcs;
IPoint point = new PointClass() as IPoint;
point.PutCoords(-92.96000, 44.9227);
IGeometry geometryShape;
geometryShape = point;
geometryShape.SpatialReference = sr1;
geometryShape.Project(mapControl.SpatialReference);
mapControl.DrawShape(geometryShape);
This takes your point and projects it to the MapControls current spatial reference, then plots the point.
Good Luck.
Here is the code to zoom and center on a lat / long, the above poster was helpful but his solution did not work for me.
mapControl.MapScale = mapControl.MapScale / 2; //for zooming
ISpatialReferenceFactory srFactory = new SpatialReferenceEnvironmentClass(); //move up top later
IGeographicCoordinateSystem gcs = srFactory.CreateGeographicCoordinateSystem((int)esriSRGeoCSType.esriSRGeoCS_WGS1984); //World lat / long format
ISpatialReference sr1 = gcs;
IPoint point = new PointClass();
point.SpatialReference = gcs;
point.PutCoords(-92.96000, 44.9227);
point.Project(mapControl.SpatialReference);
mapControl.CenterAt(point);