Point along a line

7650
9
Jump to solution
06-02-2015 11:34 AM
thejuskambi
Occasional Contributor III

I am creating a web application, using javascript api and the requirement is to get a point on the line based on certain distance. If you have worked with ArcObject, then you would have used the QueryPoint method in IPolyline. Does anybody know how this can be achieved in javascript api.

Thanks in advance.

0 Kudos
1 Solution

Accepted Solutions
JoelBennett
MVP Regular Contributor

So I noticed I answered a question you didn't ask...I should keep that in mind next time somebody does the same to me.

Anyhow, try this, which is also entirely client side.  I've attached a copy since this forum software eliminates my extra line breaks.

Note that I've tested this against ESRI's Measure widget, and concluded the Measure widget doesn't work properly.  I've hand calculated the distance between two points after acquiring their coordinates from the map, and it's quite a bit different from what the Measure tool measures between those points. I tested with data in Web Mercator...not sure if that would make a difference.

As such, I think this works properly.  It assumes a few things, like a valid polyline with at least one path that has at least 2 points.  It returns null if the distance is greater than the path's length, or if the distance is negative.

function distanceBetweenPoints(x1, y1, x2, y2) {
 return Math.sqrt(Math.pow(x2 - x1, 2) + (Math.pow(y2 - y1, 2)));
}
function getPointAlongLine(polyline, distance, pathIndex) {
 if (!pathIndex)
  pathIndex = 0;
 if (!distance)
  distance = 0;
 if ((pathIndex >= 0) && (pathIndex < polyline.paths.length)) {
  var path = polyline.paths[pathIndex];
  var x1, x2, x3, y1, y2, y3;
  var travelledDistance = 0;
  var pathDistance;
  var distanceDiff;
  var angle;
  if (distance === 0)
   return polyline.getPoint(pathIndex, 0);
  else if (distance > 0) {
   for (var i = 1; i < path.length; i++) {
    x1 = path[i-1][0];
    y1 = path[i-1][1];
    x2 = path[0];
    y2 = path[1];
    pathDistance = this._distanceBetweenPoints(x1, y1, x2, y2);
    travelledDistance += pathDistance;
    if (travelledDistance === distance)
     return polyline.getPoint(pathIndex, i);
    else if (travelledDistance > distance) {
     distanceDiff = pathDistance - (travelledDistance - distance);
     angle = Math.atan2(y2-y1, x2-x1);
     x3 = distanceDiff * Math.cos(angle);
     y3 = distanceDiff * Math.sin(angle);
     return new Point(x1 + x3, y1 + y3, polyline.spatialReference);
    }
   }
  }
 }
 return null;
}

View solution in original post

9 Replies
RobertScheitlin__GISP
MVP Emeritus

Thejus,

  The JS API does not contain any functionality like this currently. What I suggest is create a GP service for this.

thejuskambi
Occasional Contributor III

Hello Robert,

I am aware the js api does not have such functionality currently. But, isnt there a way to achieve this without having to use GP service. some sort of calculation or sort. Surely somebody would have needed or implemented this functionality.

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Thejus,

   I have seen a couple of people ask this question over the last couple of years and no one has come up with a way to do it clientside (unless they just choose not to share).

0 Kudos
JoelBennett
MVP Regular Contributor

Based on code I found here:

c# - get closest point to a line - Stack Overflow

I have implemented the following function, which has been working well.  As you can see, it is entirely client-side, and I have assigned the function to the Polyline "type" so I can simply call polyline.getClosestPoint(etc), but you can make it a standalone function like:

function getClosestPoint(polyline, point) {

//code

}

...and changing "this" to "polyline".

   Polyline.prototype.getClosestPoint = function(point) {
    var closestDistance = Number.MAX_VALUE;
    var closestPoint = null;
    var abapProduct;
    var sqMagnitude;
    var distance;
    var vectorAP;
    var vectorAB;
    var pointA;
    var pointB;
    var pointC;
    for (var pathIndex = 0; pathIndex < this.paths.length; pathIndex++) {
     for (var pointIndex = 1; pointIndex < this.paths[pathIndex].length; pointIndex++) {
      pointA = this.getPoint(pathIndex, pointIndex);
      pointB = this.getPoint(pathIndex, pointIndex - 1);
      vectorAP = [point.x - pointA.x, point.y - pointA.y];
      vectorAB = [pointB.x - pointA.x, pointB.y - pointA.y];
      sqMagnitude = Math.pow(vectorAB[0], 2) + Math.pow(vectorAB[1], 2);
      abapProduct = (vectorAB[0] * vectorAP[0]) + (vectorAB[1] * vectorAP[1]);
      distance = abapProduct / sqMagnitude;
      if (distance < 0)
       pointC = pointA;
      else if (distance > 1)
       pointC = pointB;
      else
       pointC = new Point(pointA.x + (vectorAB[0] * distance), pointA.y + (vectorAB[1] * distance), this.spatialReference);
      distance = pointC.distanceTo(point);
      if (distance < closestDistance) {
       closestDistance = distance;
       closestPoint = pointC;
      }
     }
    }
    return closestPoint;
   };

					
				
			
			
				
			
			
				
			
			
			
			
			
			
		
JoelBennett
MVP Regular Contributor

I just realized that included a call to another custom function - distanceTo.  Here's the function definition:

   Point.prototype.distanceTo = function(point) {
    return Math.sqrt(Math.pow(this.x - point.x, 2) + (Math.pow(this.y - point.y, 2)));
   };
0 Kudos
JeffJacobson
Occasional Contributor III

There is a library called turf that is supposed to allow you to do this. (Note that I haven't tried it myself, though.) Since turf uses GeoJSON, you can use Terraformer to convert from ArcGIS geometries to GeoJSON.

JoelBennett
MVP Regular Contributor

So I noticed I answered a question you didn't ask...I should keep that in mind next time somebody does the same to me.

Anyhow, try this, which is also entirely client side.  I've attached a copy since this forum software eliminates my extra line breaks.

Note that I've tested this against ESRI's Measure widget, and concluded the Measure widget doesn't work properly.  I've hand calculated the distance between two points after acquiring their coordinates from the map, and it's quite a bit different from what the Measure tool measures between those points. I tested with data in Web Mercator...not sure if that would make a difference.

As such, I think this works properly.  It assumes a few things, like a valid polyline with at least one path that has at least 2 points.  It returns null if the distance is greater than the path's length, or if the distance is negative.

function distanceBetweenPoints(x1, y1, x2, y2) {
 return Math.sqrt(Math.pow(x2 - x1, 2) + (Math.pow(y2 - y1, 2)));
}
function getPointAlongLine(polyline, distance, pathIndex) {
 if (!pathIndex)
  pathIndex = 0;
 if (!distance)
  distance = 0;
 if ((pathIndex >= 0) && (pathIndex < polyline.paths.length)) {
  var path = polyline.paths[pathIndex];
  var x1, x2, x3, y1, y2, y3;
  var travelledDistance = 0;
  var pathDistance;
  var distanceDiff;
  var angle;
  if (distance === 0)
   return polyline.getPoint(pathIndex, 0);
  else if (distance > 0) {
   for (var i = 1; i < path.length; i++) {
    x1 = path[i-1][0];
    y1 = path[i-1][1];
    x2 = path[0];
    y2 = path[1];
    pathDistance = this._distanceBetweenPoints(x1, y1, x2, y2);
    travelledDistance += pathDistance;
    if (travelledDistance === distance)
     return polyline.getPoint(pathIndex, i);
    else if (travelledDistance > distance) {
     distanceDiff = pathDistance - (travelledDistance - distance);
     angle = Math.atan2(y2-y1, x2-x1);
     x3 = distanceDiff * Math.cos(angle);
     y3 = distanceDiff * Math.sin(angle);
     return new Point(x1 + x3, y1 + y3, polyline.spatialReference);
    }
   }
  }
 }
 return null;
}
thejuskambi
Occasional Contributor III

This is exactly what I was looking for. Thanks Joel.

0 Kudos
FredSpataro
Occasional Contributor III

Hi Joel,

Thanks for posting this function.  It should be in the API. 

FYI the measurement widget works with "geodesic" math so that you get "true/real world" lengths and areas.  The Web Mercator system makes a "nice" picture for web maps but it's really bad for calculating length and area (almost double at the US level latitudes). 

0 Kudos