-1

I'm developing a GPS project I allow user to draw (using google) a shape(rectangle, polygon, circle, line) and save it in database

SQL Fiddle

Now my requirement is to determine if a given point within anyone in the table and return the id else return 0.

the trick is I save multiple shapes (Type column determine the shape type 2 is a rectangle- 4 is polygon)

What I have tried

Example

Note: I will be using this from C# and pass point as parameter

Community
  • 1
  • 1
Maher Khalil
  • 529
  • 1
  • 15
  • 28
  • Will not be easy because you didn't normilize the points data.. Using comma separated values is never the answer in databases.. for the question you will need the function STContains ( https://learn.microsoft.com/en-us/sql/t-sql/spatial-geography/stcontains-geography-data-type ) – Raymond Nijland Dec 25 '17 at 11:24
  • will that function work with all shapes – Maher Khalil Dec 25 '17 at 11:33
  • I tend to use STIntersects myself (it's commutative, which I like), but to answer your question about the ability of these functions to operate on different types of polygons, yes they do. – Ben Thul Dec 25 '17 at 11:39
  • i have updated the sqlfiddle link added a circle how to convert its points to CIRCULARSTRING (Diamter| Point) and the line it will never be interset i need some proximity – Maher Khalil Dec 25 '17 at 11:48
  • are you using entity framework? – Dawood Awan Dec 25 '17 at 13:54
  • i don't see the relation but no that is a windows service with direct access (sql command , connection ,etc) – Maher Khalil Dec 25 '17 at 14:00
  • @MaherKhalil , what is the reason for not saving the polygons as Spatial Data Types? – Dawood Awan Dec 25 '17 at 14:11
  • If you save them as text everytime you want to search for an intersect you will have to convert them to Spatial Data Types - and reason I asked about EF is because EF has DBGeography types, making it easy for you to perform the intersect operation - i.e. var interesctingPolygons = context.youPolygonTable.Where(s => s.YOUR_POLYGON_GEOGRAPHY.Intersects(YOUR_POINT)).ToList() – Dawood Awan Dec 25 '17 at 14:19
  • what is the reason for not saving the polygons as Spatial Data Types? because it is different types i get them by JavaScript as whole string (from google map) – Maher Khalil Dec 25 '17 at 14:21
  • @MaherKhalil the shape doesn't matter- you can write a method to easily convert them from Spatial to Lat/Lngs when you want to plot them on the Map - you can use polygon for circle, square, rectangle, or triangle AND use PolyLine for lines - but save them as Spatial data types - – Dawood Awan Dec 25 '17 at 14:24
  • what is the field type ? could you give a tutorial link or example to save shapes as polygon – Maher Khalil Dec 25 '17 at 14:41
  • ok assuming i used the Spatial Data Types still the question how to get whether the point within the shape – Maher Khalil Dec 25 '17 at 14:42
  • @MaherKhalil I've posted an answer below which includes converting from lat/lngs to WKT and spatial type and also from spatial to Lat/Lngs - hope it helps you field type is GEOGRAPHY in SQL introduced in 2008 SQL - https://learn.microsoft.com/en-us/sql/t-sql/spatial-geography/spatial-types-geography – Dawood Awan Dec 25 '17 at 14:58

1 Answers1

1

First of all I would suggest you save the Polygons as Spatial Data Types

In JavaScript you can easily convert the drawn polygons to a List/Array of Lat/Lngs

for a circle you need the radius and the center lat/lng of the circle

something like this:

var circleCenter = null;
var circlePolygon = null;

// set draw options on google map
mapObject.setOptions({ draggableCursor: 'crosshair' });

var drawGeofenceEvent = google.maps.event.addListener(mapObject, 'click', event => {

    var myLatLng = event.latLng;

    // create a marker for center
    circleCenter = new google.maps.Marker({
        map: mapObject,
        position: myLatLng,
        draggable: true
    });

    // create a temp circle polygon to plot on map
    circlePolygon = new google.maps.Circle({
        radius: 150,
        editable: true,
        draggable: true
    });

    circlePolygon.setMap(mapObject);

    circlePolygon.bindTo('center', circleCenter, 'position');

    google.maps.event.removeListener(drawGeofenceEvent);
});


// after drawing the geofence you want to get the lat/longs along the circumference
var circleCoordinates = this.googleCircleToPolylineArray(this.circleCenter.position, this.circlePolygon.getRadius() * 0.000621371, 1);


// copied from Stackoverflow - will post the link when I find it
googleCircleToPolylineArray(point, radius, dir) {
    var lat = point.lat();
    var lng = point.lng();
    var d2r = Math.PI / 180; // degrees to radians
    var r2d = 180 / Math.PI; // radians to degrees
    var earthsradius = 3963; // 3963 is the radius of the earth in miles
    // ReSharper disable once AssignedValueIsNeverUsed
    var points = 32;
    //var radius = 1;             // radius in miles
    // find the raidus in lat/lon
    var rlat = (radius / earthsradius) * r2d;
    var rlng = rlat / Math.cos(lat * d2r);
    var extp = new Array();
    for (var i = 0; i < points + 1; i++) {
        var theta = Math.PI * (i / (points / 2));
        var ex = lng + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
        var ey = lat + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
        extp.push({ Latitude: ey, Longitude: ex }); //   new google.maps.LatLng(ey, ex)
    }
    return extp;
};


// then you would POST the circleCoordinates to the Server to save as a spatial data type

Then on the server you would convert the co-ordinates to a Well-Known-Text (WKT) Wikipedia - for Polygon use the following, when I say Polygon I mean circle, rectangle, triangle, hexagon etc. Anything which has the same start and end point (is closed).

for a line you would use the LINESTRING WKT

public static DbGeography CreatePolygon(Coordinate[] latLongs)
{
    //POLYGON ((73.232821 34.191819,73.233755 34.191942,73.233653 34.192358,73.232843 34.192246,73.23269 34.191969,73.232821 34.191819))
    var polyString = "";
    foreach (var point in latLongs)
    {
        polyString += point.Longitude + " " + point.Latitude + ",";
    }
    polyString = polyString.TrimEnd(',');
    polyString = string.Format("POLYGON(({0}))", polyString);
    var polygonFromText = DbGeography.PolygonFromText(polyString, DbGeography.DefaultCoordinateSystemId);
    return polygonFromText;
}

Reverse From DbGeography to Coordinates

public static List<Coordinate> PolygonToGeoPoints(DbGeography sptGeofenceArea)
 {
     var points = new List<Coordinate>();
     string polygonText = sptGeofenceArea.ProviderValue.ToString();
     polygonText = polygonText.Replace("POLYGON", "");
     polygonText = polygonText.Replace("(", "").Replace(")", "").Trim();
     var polPoints = polygonText.Split(',');
     foreach (var point in polPoints)
     {
         var latlong = point.Trim().Split(' ');
         points.Add(new Coordinate { Latitude = double.Parse(latlong[1]), Longitude = double.Parse(latlong[0]) });
     }
     return points;
 }

I use Entity Framework and DbGeography types, I save the polygon as a spatial data type in the database.

You can edit the above code to return the Well-Known-Text (WKT) instead of the DbGeography data type.

Then once the spatial data type is stored in the database all you have to do is convert the point you want to check to a spatial data type OR WKT

WKT - SQL VERSION

DECLARE @point GEOGRAPHY;
SET @point = geography::Point(47.653, -122.358, 4326)


Select
    *
From Polygons 

where POLYGON.STIntersects(@point) = 1

SPATIAL TYPE - ENTITY FRAMEWORK

DbGeography point;

dbCOntext.Polygons.Where(s => point.Intersects(s.Polygon)).ToList();

EDIT

There is a common problem when creating the Polygon types - the points have to be in a certain order otherwise you would have a Polygon covering the entire earth except the required Polygon - to overcome that you can use the following -

LINK

    #region
    //https://www.exceptionnotfound.net/fixing-sql-server-spatial-not-a-valid-instance-of-geography-errors-in-c-sharp/
    private static DbGeography CreatePolygon(string wellKnownText)
    {
        //First, get the area defined by the well-known text using left-hand rule
        var sqlGeography = SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId);

        if(!sqlGeography.STIsValid())
            throw new Exception("Invalid polygon, please draw the polygon again.");

        sqlGeography = sqlGeography.MakeValid();

        //Now get the inversion of the above area
        var invertedSqlGeography = sqlGeography.ReorientObject();

        //Whichever of these is smaller is the enclosed polygon, so we use that one.
        if (sqlGeography.STArea() > invertedSqlGeography.STArea())
        {
            sqlGeography = invertedSqlGeography;
        }
        return DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);
    }
    #endregion

    public static DbGeography CreatePolygon(Coordinate[] latLongs)
    {
        //POLYGON ((73.232821 34.191819,73.233755 34.191942,73.233653 34.192358,73.232843 34.192246,73.23269 34.191969,73.232821 34.191819))
        var polyString = "";
        foreach (var point in latLongs)
        {
            polyString += point.Longitude + " " + point.Latitude + ",";
        }
        polyString = polyString.TrimEnd(',');
        polyString = string.Format("POLYGON(({0}))", polyString);

        var dbGeographyPolygon = CreatePolygon(polyString);

        return dbGeographyPolygon;


        //var polygonFromText = DbGeography.PolygonFromText(polyString, DbGeography.DefaultCoordinateSystemId);
        //return polygonFromText;

    }
Dawood Awan
  • 7,051
  • 10
  • 56
  • 119
  • can you update the answer adding the polyline case ? and one more thing in the line case the point will never intersect with the line (due to accuracy ) so how to get the distance from the point to the line (if less than 20 meter it is on road ) thanks and it is still accepted answer – Maher Khalil Dec 25 '17 at 15:08
  • look into STBuffer command of spatial data types in SQL - https://learn.microsoft.com/en-us/sql/t-sql/spatial-geography/stbuffer-geography-data-type – Dawood Awan Dec 25 '17 at 15:12
  • doing the linestring should be easy based on this link - I don't have the code for LineString - https://learn.microsoft.com/en-us/sql/t-sql/spatial-geography/stgeomfromtext-geography-data-type – Dawood Awan Dec 25 '17 at 15:18
  • also there is a common problem with creating polygons - they have to be in a certain order - to overcome that check my edit – Dawood Awan Dec 25 '17 at 15:23
  • Hi i followed your code and the result is exception latitude values must be between 90,-90 POLYGON((542.6222157318555 32.233196704171725,533.1756161850183 113.3644404829773,505.1988450673772 191.37785606228906,459.7670340034294 263.27543163182156,398.62610201390913 326.29418368306256,324.12566082694406 378.01233691600623,239.1287206170204 416.4423917057849,146.90166612957233 440.1075025986505,50.98873134702447 448.0982326545373,-44.924203435523395 – Maher Khalil Dec 26 '17 at 09:29
  • data from google lat:32.158821005022965 ,lng:32.158821005022965 raduis:21853.108153519275 – Maher Khalil Dec 26 '17 at 09:32
  • sorry lng:51.03267665952444 – Maher Khalil Dec 26 '17 at 09:43
  • the radius from google is meters i should make it miles as the earthrad var am i corret – Maher Khalil Dec 26 '17 at 09:54