Parametric equation to place a leaflet marker on t

2019-04-25 08:46发布

问题:

I am working on an application where I have the center of a circle and the radius and I am plotting the circle with the help of Leaflet.

I placed a marker on the north most end of the circumference and made it draggable.

var circle = L.circle(coords, radius).addTo(map);

convertRadiusToLatitude = parseInt(response.radius)/111111;

var coordsOnRadius = [parseFloat(response.lat) + convertRadiusToLatitude, parseFloat(response.long)];
var markerOnRadius = L.marker(coordsOnRadius, {draggable: true}).addTo(map);

Now, this adds the marker to the circumference and now I wanted it to be draggable only on the circumference itself for which I used the parametric equation.

Parametric equation

x = Xc + R * cos(theta)
y = Yc + R * sin(theta)

Code for dragging

markerOnRadius.on('drag', function(e){

    bearing = marker.getLatLng().bearingTo(markerOnRadius.getLatLng());

    var markerOnRadiusX = parseFloat(response.lat)  + ((0.000009 * parseFloat(response.radius)) * Math.cos( toRad(bearing) ));
    var markerOnRadiusY = parseFloat(response.long) + ((0.000009 * parseFloat(response.radius)) * Math.sin( toRad(bearing) ));

    markerOnRadius.setLatLng([markerOnRadiusX, markerOnRadiusY]);
});

The bearingTo method:

L.LatLng.prototype.bearingTo = function(other) {
    var d2r  = L.LatLng.DEG_TO_RAD;
    var r2d  = L.LatLng.RAD_TO_DEG;
    var lat1 = this.lat * d2r;
    var lat2 = other.lat * d2r;
    var dLon = (other.lng-this.lng) * d2r;
    var y    = Math.sin(dLon) * Math.cos(lat2);
    var x    = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLon);
    var brng = Math.atan2(y, x);
    brng = parseInt( brng * r2d );
    brng = (brng + 360) % 360;
    return brng;
};

Issue

When I start dragging the marker, this code is working fine and brings it back to the circumference at the bearing at which the marker is dragged to. But there is one problem, the coords on the circumference are slightly off and in terms of longitude. When the bearing is 0 (north), the coords are perfect, but when it is 90 (east), the longitude is slightly less that it should for the marker to be at the circumference.

Again at 180 (south), coords are perfect, but at 270 (west), the longitude calculated is slightly less and the marker tends towards the radius again.

So basically if you visualize the marker being dragged, it starts perfectly on the north end and starts coming inside the circle slightly increasing with the bearing till it reacher 90 and then starts going towards the circumference again till 180 when it is perfect again.

It forms more like a ellipse if you get the gist of it.

Could anyone tell me why is longitude coming a little off and why the marker moves in an elliptical path. Has it something to do with the world coordinates and window coordinates. Or are my equations slightly off somewhere?

回答1:

It does look like a projection issue. In your dragging code you are basically doing

 lat = a + r cos(baring)
 long = b + r sin(baring)

giving a circle in the Lat-Long coordinates. This would work fine if you were at the equator with Mercator projection. You will get more distortion as you move further towards the polls.

Assume you are using the defaults for Leaflet reference doc You have the EPSG3857 Web Mercator coordinates.

If you want to ensure you have a exact circle it will be better to work using screen coordinates. You can get these using methods on the ICRS objects. First get the coordinate system L.CRS.EPSG3857 and use the latLngToPoint and pointToLatLng methods.

var crs = L.CRS.EPSG3857;
var zoom = ...; // how you calculate your zoom factor

markerOnRadius.on('drag', function(e){

    var markerLL = marker.getLatLng()
    var morLL    = markerOnRadius.getLatLng();
    var markerP  = crs.latLngToPoint(markerLL,zoom);
    var morP     = crs.latLngToPoint(morLL,zoom);
    // get the distance between the two points
    var dist = markerP.distanceTo(morP);
    // Get the vector from center to point
    var A = morP.subtract(markerP);
    // scale so its of the desired length
    var B = A. multiplyBy( factor / dist);
    // Add on the center
    var C = markerP.add(B);
    // Convert back to LatLong
    var D = crs.pointToLatLng(C,zoom);
    markerOnRadius.setLatLong(D);
});