2

I`ve got a map with a polyline. The server will responds me with a LatLng point on a map. I need to check if the point from the server situated on a polyline edge. If not, i need to find the nearest point on the polyline and place a marker on the nearest point on the polyline. For example, if the server responds me with a point A, i need to place a marker to a point B.

enter image description here

I find a good library http://wtp2.appspot.com/cSnapToRouteDemo.html, but this library is for Google API ver. 2, and i'm using Google API ver. 3. Is there any alternative for Google API ver. 3 ? Thank's.

Edward
  • 589
  • 1
  • 9
  • 18

2 Answers2

2

Found an easier solution.Using turf.js.Just use your polyline coordinates to create turf line and marker position for creating turf point.

then;

var line = turf.lineString(Your polyline coordinates in GeoJson array);
var pt = turf.point([Marker_Lon, Marker_Lat]);
var snapped = turf.pointOnLine(line, pt);
var pstnOnLine = { lat: snapped.geometry.coordinates[1], lng: snapped.geometry.coordinates[0] };
var distToLine = snapped.properties.dist

This way you can calculate the distance and coordinates on your polyline easily.

You can find out more from Turf website.

Bulut Kartal
  • 83
  • 2
  • 20
1

The Google Maps Javascript API has a Geometry library.

The Geometry library has a isLocationOnEdge function. See the documentation.

To determine whether a point falls on or near a polyline, or on or near the edge of a polygon, pass the point, the polyline/polygon, and optionally a tolerance value in degrees to google.maps.geometry.poly.isLocationOnEdge(). The function returns true if the distance between the point and the closest point on the line or edge falls within the specified tolerance. The default tolerance value is 10-9 degrees.

You must include the library this way:

<script type="text/javascript"
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=geometry">
</script>

And if you need more than one library, for example:

<script type="text/javascript"
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=geometry,places">
</script>

Full documentation is here.


If you need to find the point on the Polyline, you can use this ported to v3 version of the library you mentioned in your question:

var mapRoute;

var rtPoints;
var centerMAP = new google.maps.LatLng(-7.402438, 110.446957);

function gLatLngFromEN(e, n) {
    var ogbLL = NEtoLL(e, n);
    var pc = OGBToWGS84(ogbLL.lat, ogbLL.lon, 0);
    return new google.maps.LatLng(pc.lat, pc.lon);
}

function routeMap() {

    mapRoute = new google.maps.Map(document.getElementById('mapRoute'), {
        center: centerMAP,
        zoom: 14,
        mapTypeId: google.maps.MapTypeId.SATELLITE
    });
    mapRoute.setCenter(gLatLngFromEN(469000, 169000), 13);

    var rtPoints = new Array();
    rtPoints.push(gLatLngFromEN(468000, 168000));
    rtPoints.push(gLatLngFromEN(468000, 170000));
    rtPoints.push(gLatLngFromEN(470000, 170000));
    rtPoints.push(gLatLngFromEN(470000, 168000));
    var rtPoly = new google.maps.Polyline({
        path: rtPoints,
        strokeColor: "#0000FF",
        strokeWeight: 3,
        map: mapRoute
    });
    var container = document.createElement("div");
    container.style.fontFamily = 'Arial';
    container.style.fontSize = 'XX-Small';

    var ptr = document.createElement("INPUT");
    ptr.style.width = "100px";
    ptr.type = "Text";
    ptr.readOnly = true;
    ptr.id = "distPtr";
    container.appendChild(ptr);

    document.getElementById("control").appendChild(container);


    google.maps.event.addListener(mapRoute, 'mousemove', function (point) {
        document.getElementById('distPtr').value = Math.round(bdccGeoDistanceToPolyMtrs(rtPoly, point.latLng));
    });

}

google.maps.event.addDomListener(window, 'load', routeMap);


// Code to find the distance in metres between a lat/lng point and a polyline of lat/lng points
// All in WGS84. Free for any use.
//
// Bill Chadwick 2007
// updated to Google Maps API v3, Lawrence Ross 2014

    // Construct a bdccGeo from its latitude and longitude in degrees
    function bdccGeo(lat, lon) 
    {
      var theta = (lon * Math.PI / 180.0);
      var rlat = bdccGeoGeocentricLatitude(lat * Math.PI / 180.0);
      var c = Math.cos(rlat);   
      this.x = c * Math.cos(theta);
      this.y = c * Math.sin(theta);
      this.z = Math.sin(rlat);      
    }
    bdccGeo.prototype = new bdccGeo();

    // internal helper functions =========================================

      // Convert from geographic to geocentric latitude (radians).
    function bdccGeoGeocentricLatitude(geographicLatitude) 
    {
      var flattening = 1.0 / 298.257223563;//WGS84
        var f = (1.0 - flattening) * (1.0 - flattening);
      return Math.atan((Math.tan(geographicLatitude) * f));
    }

    // Convert from geocentric to geographic latitude (radians)
    function bdccGeoGeographicLatitude (geocentricLatitude) 
    {
      var flattening = 1.0 / 298.257223563;//WGS84
        var f = (1.0 - flattening) * (1.0 - flattening);
      return Math.atan(Math.tan(geocentricLatitude) / f);
    }

     // Returns the two antipodal points of intersection of two great
     // circles defined by the arcs geo1 to geo2 and
     // geo3 to geo4. Returns a point as a Geo, use .antipode to get the other point
    function bdccGeoGetIntersection( geo1,  geo2,  geo3,  geo4) 
    {
      var geoCross1 = geo1.crossNormalize(geo2);
      var geoCross2 = geo3.crossNormalize(geo4);
      return geoCross1.crossNormalize(geoCross2);
    }

    //from Radians to Meters
    function bdccGeoRadiansToMeters(rad)
    {
      return rad * 6378137.0; // WGS84 Equatorial Radius in Meters
    }

    //from Meters to Radians
    function bdccGeoMetersToRadians(m)
    {
      return m / 6378137.0; // WGS84 Equatorial Radius in Meters
    }

    // properties =================================================


    bdccGeo.prototype.getLatitudeRadians = function() 
    {
      return (bdccGeoGeographicLatitude(Math.atan2(this.z,
        Math.sqrt((this.x * this.x) + (this.y * this.y)))));
    }

    bdccGeo.prototype.getLongitudeRadians = function() 
    {
      return (Math.atan2(this.y, this.x));
    }

    bdccGeo.prototype.getLatitude = function() 
    {
      return this.getLatitudeRadians()  * 180.0 / Math.PI;
    }

    bdccGeo.prototype.getLongitude = function() 
    {
      return this.getLongitudeRadians()  * 180.0 / Math.PI ;
    }

    // Methods =================================================

        //Maths
    bdccGeo.prototype.dot = function( b) 
    {
      return ((this.x * b.x) + (this.y * b.y) + (this.z * b.z));
    }

        //More Maths
    bdccGeo.prototype.crossLength = function( b) 
    {
      var x = (this.y * b.z) - (this.z * b.y);
      var y = (this.z * b.x) - (this.x * b.z);
      var z = (this.x * b.y) - (this.y * b.x);
      return Math.sqrt((x * x) + (y * y) + (z * z));
    }

    //More Maths
    bdccGeo.prototype.scale = function( s) 
    {
        var r = new bdccGeo(0,0);
        r.x = this.x * s;
        r.y = this.y * s;
        r.z = this.z * s;
      return r;
    }

        // More Maths
    bdccGeo.prototype.crossNormalize = function( b) 
    {
      var x = (this.y * b.z) - (this.z * b.y);
      var y = (this.z * b.x) - (this.x * b.z);
      var z = (this.x * b.y) - (this.y * b.x);
      var L = Math.sqrt((x * x) + (y * y) + (z * z));
      var r = new bdccGeo(0,0);
      r.x = x / L;
      r.y = y / L;
      r.z = z / L;
      return r;
    }

    // point on opposite side of the world to this point
    bdccGeo.prototype.antipode = function() 
    {
      return this.scale(-1.0);
    }






        //distance in radians from this point to point v2
    bdccGeo.prototype.distance = function( v2) 
    {
      return Math.atan2(v2.crossLength(this), v2.dot(this));
    }

    //returns in meters the minimum of the perpendicular distance of this point from the line segment geo1-geo2
    //and the distance from this point to the line segment ends in geo1 and geo2 
    bdccGeo.prototype.distanceToLineSegMtrs = function(geo1, geo2)
    {            

      //point on unit sphere above origin and normal to plane of geo1,geo2
      //could be either side of the plane
      var p2 = geo1.crossNormalize(geo2); 

      // intersection of GC normal to geo1/geo2 passing through p with GC geo1/geo2
      var ip = bdccGeoGetIntersection(geo1,geo2,this,p2); 

      //need to check that ip or its antipode is between p1 and p2
      var d = geo1.distance(geo2);
      var d1p = geo1.distance(ip);
      var d2p = geo2.distance(ip);
      //window.status = d + ", " + d1p + ", " + d2p;
      if ((d >= d1p) && (d >= d2p)) 
        return bdccGeoRadiansToMeters(this.distance(ip));
      else
      {
        ip = ip.antipode(); 
        d1p = geo1.distance(ip);
        d2p = geo2.distance(ip);
      }
      if ((d >= d1p) && (d >= d2p)) 
        return bdccGeoRadiansToMeters(this.distance(ip)); 
      else 
        return bdccGeoRadiansToMeters(Math.min(geo1.distance(this),geo2.distance(this))); 
    }

        // distance in meters from GLatLng point to GPolyline or GPolygon poly
        function bdccGeoDistanceToPolyMtrs(poly, point)
        {
            var d = 999999999;
            var i;
            var p = new bdccGeo(point.lat(),point.lng());
            for(i=0; i<(poly.getPath().getLength()-1); i++)
                 {
                    var p1 = poly.getPath().getAt(i);
                    var l1 = new bdccGeo(p1.lat(),p1.lng());
                    var p2 = poly.getPath().getAt(i+1);
                    var l2 = new bdccGeo(p2.lat(),p2.lng());
                    var dp = p.distanceToLineSegMtrs(l1,l2);
                    if(dp < d)
                        d = dp;    
                 }
             return d;
        }

        // get a new GLatLng distanceMeters away on the compass bearing azimuthDegrees
        // from the GLatLng point - accurate to better than 200m in 140km (20m in 14km) in the UK

        function bdccGeoPointAtRangeAndBearing (point, distanceMeters, azimuthDegrees) 
        {
             var latr = point.lat() * Math.PI / 180.0;
             var lonr = point.lng() * Math.PI / 180.0;

             var coslat = Math.cos(latr); 
             var sinlat = Math.sin(latr); 
             var az = azimuthDegrees* Math.PI / 180.0;
             var cosaz = Math.cos(az); 
             var sinaz = Math.sin(az); 
             var dr = distanceMeters / 6378137.0; // distance in radians using WGS84 Equatorial Radius
             var sind = Math.sin(dr); 
             var cosd = Math.cos(dr);

            return new google.maps.LatLng(Math.asin((sinlat * cosd) + (coslat * sind * cosaz)) * 180.0 / Math.PI,
            (Math.atan2((sind * sinaz), (coslat * cosd) - (sinlat * sind * cosaz)) + lonr) * 180.0 / Math.PI); 
        }

Credits to Bill Chadwick 2007 and Lawrence Ross 2014 for the v3 version. And @geocodezip for finding it.

JSFiddle demo

MrUpsidown
  • 21,592
  • 15
  • 77
  • 131
  • Thank's for your answer. I have already use google.maps.geometry.poly.isLocationOnEdge() function. But my problem is a bit another. With google.maps.geometry.poly.isLocationOnEdge() i just can check if the passed LatLng is on or not on the polyline. And i want to calculate the LatLng of a nearest point on the polyline depends on the responds LatLng from the server. – Edward Mar 02 '16 at 12:09
  • 1
    Did you check [this answer](http://stackoverflow.com/questions/16429562/find-a-point-in-a-polyline-which-is-closest-to-a-latlng)? It shows a ported to v3 version of the library you mentioned in your question. – MrUpsidown Mar 02 '16 at 12:21
  • And sorry, I misread this sentence: *Returns true when the difference between the latitude and longitude of the supplied point, and the closest point on the edge, is less than the tolerance.* as I thought it was also returning that point on the polyline. But that's not the case... – MrUpsidown Mar 02 '16 at 12:22
  • 1
    Yes, i find it just now. I`m trying to solve my problem with it's version. Thank you. – Edward Mar 02 '16 at 12:22
  • @Edward did you come up with any solutions? – Bulut Kartal Sep 07 '17 at 12:51