2
votes

Note: The problem is not specific to Leaflet, but GIS in general.

I'm trying to draw an arc on a map. I have a function to generate the polygon points and it works on a canvas for example, but not on Lng,Lat map.

The problem is that I cannot figure out how to convert the inner/outer radius from Meters to degrees (as in lng/lat), what I tried so far looks more elliptic than circular.

How to accurately convert meters to longitude or latitude at any point on earth (except the poles)?

Here is what I tried (works) on canvas.

$(document).ready(function() {
    var d_canvas = document.getElementById('canvas');
    var c2 = d_canvas.getContext('2d');
    c2.fillStyle = '#f00';
    c2.beginPath();
    
    var fromDeg = 0;
    var toDeg = 90;
    var fromRad = getAngle(fromDeg);
    var toRad = getAngle(toDeg);
    var segments = 100;
    var step = getAngle(toDeg-fromDeg)/segments;
    var x = 250;
    var y = 250;
    var outR = 250;
    var inR = 230;
    c2.moveTo(x+(Math.sin(fromRad)*inR),y-(Math.cos(fromRad)*inR));
    //c2.moveTo(x,y);
    for (var i = fromRad; i<=toRad; i=i+step){
    	c2.lineTo(x+(Math.sin(i)*inR),y-(Math.cos(i)*inR));
    }
    //c2.closePath();
    for (var i = toRad; i>=fromRad; i=i-step){
    	c2.lineTo(x+(Math.sin(i)*outR),y-(Math.cos(i)*outR));
    }
    c2.lineTo(x+(Math.sin(fromRad)*inR),y-(Math.cos(fromRad)*inR));
    //c2.closePath();
    c2.stroke();
});

function getAngle(deg){
	var val = 2*(deg/360);
 	return Math.PI*val;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="canvas" width="500" height="500"></canvas>

And here is what I tried ( dosn't work well ) on Leaflet map.

	 	var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
	 	  osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
	 	  osm = L.tileLayer(osmUrl, {
	 	    maxZoom: 18,
	 	    attribution: osmAttrib
	 	  });

	 	 // initialize the map on the "map" div with a given center and zoom
	 	var map = L.map('map').setView([59.56667, 150.80000], 12).addLayer(osm);

	 	 // Script for adding marker on map click
	 	L.polygon(getPolygon()).addTo(map);

	 	function getPolygon() {
	 	  var fromDeg = 0;
	 	  var toDeg = 90;
	 	  var fromRad = getAngle(fromDeg);
	 	  var toRad = getAngle(toDeg);
	 	  var segments = 100;
	 	  var step = getAngle(toDeg - fromDeg) / segments;
	 	  var y = 150.84229;
	 	  var x = 59.55416;
	 	  var outR = 0.05; // <------ should be dynamic?
	 	  var inR = 0.025; // <------ this also?
	 	  var polygon = [];
	 	  polygon.push([x + (Math.sin(fromRad) * inR), y + (Math.cos(fromRad) * inR)]);
	 	  for (var i = fromRad; i <= toRad; i = i + step) {
	 	    polygon.push([x + (Math.sin(i) * inR), y + (Math.cos(i) * inR)]);
	 	  }
	 	  //c2.closePath();
	 	  for (var i = toRad; i >= fromRad; i = i - step) {
	 	    polygon.push([x + (Math.sin(i) * outR), y + (Math.cos(i) * outR)]);
	 	  }
	 	  polygon.push([x + (Math.sin(fromRad) * inR), y + (Math.cos(fromRad) * inR)]);
	 	  return polygon;
	 	}

	 	function getAngle(deg) {
	 	  var val = 2 * (deg / 360);
	 	  return Math.PI * val;
	 	}
#map {
  height: 500px;
  width: 80%;
}
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<link href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" rel="stylesheet" />
<script src="http://unpkg.com/leaflet-arc/bin/leaflet-arc.min.js"></script>
<div id="map"></div>
2

2 Answers

6
votes

So your original question is

How to accurately convert meters to longitude or latitude at any point on earth (except the poles)?

But my brain reads that as

Given a [lat, lng] point and a distance d in meters, how to calculate a second [lat2, lng2] point which is d meters away from the first point?

Which, if you know some GIS jargon, is the same as asking

How do I solve the direct geodesic problem?

The answer involves mathematical concepts such as ellipsoids and great circles.

But given that you're working with Javascript and Leaflet, I'll just jump to practical implementations.

If you need a super-accurate answer, you want to have a look at the JS implementation of GeographicLib, and its methods to solve the direct geodesic problem.

If you don't really care about accuracy (and specially do not care about accuracy at the poles), you want to have a look at cheap-ruler, and specifically its destination(p, dist, bearing) method.

There are more solutions, like using a equidistant map projection centered on the point, or some other implementations of the geodesic problems, or some turf.js trickery, or creating the geometries outside of JS with similar methods, or whatever.

This problem has been solved already, so I advise to use any of the existing solutions.

1
votes

This solved the problem

	 	var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
	 	  osmAttrib = '&copy; <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
	 	  osm = L.tileLayer(osmUrl, {
	 	    maxZoom: 18,
	 	    attribution: osmAttrib
	 	  });

	 	// initialize the map on the "map" div with a given center and zoom
	 	var map = L.map('map').setView([59.56667, 150.80000], 12).addLayer(osm);

	 	// Script for adding marker on map click
	 	L.polygon(getPolygon()).addTo(map);

	 	function getPolygon() {
	 	  var fromDeg = 0;
	 	  var toDeg = 120;
	 	  var lat = 59.56667;
	 	  var lon = 150.80000;
	 	  var outR = 200;
	 	  var inR = 180;
	 	  var polygon = [];
	 	  for (var i = fromDeg; i <= toDeg; i++) {
	 	    polygon.push(getPoint(lat, lon, inR, i));
	 	  }
	 	  for (var i = toDeg; i >= fromDeg; i--) {
	 	    polygon.push(getPoint(lat, lon, outR, i));
	 	  }
	 	  polygon.push(getPoint(lat, lon, inR, fromDeg));
	 	  return polygon;
	 	}
        /*************************
        * The solution
        *************************/
	 	function getPoint(lat, lon, r, deg) {
	 	  lat2 = (r / 111230) * Math.cos(deg2rad(deg));
	 	  lat2 += lat;
	 	  lon2 = (r / 111230) * Math.sin(deg2rad(deg));
	 	  lon2 = lon2 * (1 / Math.cos(deg2rad(lat2)));
	 	  lon2 += lon;
	 	  return [lat2, lon2];
	 	}

	 	function deg2rad(deg) {
	 	  return deg * (Math.PI / 180);
	 	}
#map {
  height: 500px;
  width: 80%;
}
<link href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" rel="stylesheet"/>
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<div id="map"></div>