find midpoint M of an path arc from A to B:
diagram:
i have :
point A(x,y)
point B(x,y)
radius of the arc
i tried following code but getPointAtLength is deprecated.
var myarc = document.getElementById("myarc");
// Get the length of the path
var pathLen = myarc.getTotalLength();
console.log(pathLen);
// How far along the path to we want the position?
var pathDistance = pathLen * 0.5;
console.log(pathDistance);
// Get the X,Y position
var midpoint = myarc.getPointAtLength(pathDistance)
console.log(myarc.getAttribute("d"));
console.log(midpoint);
Geometric calculation:
Сalculalate vector
AB = B - A (AB.x = B.x - A.x, similar for Y)
It's length
lAB = sqrt(AB.x*AB.x + AB.y*AB.y)
Normalized vector
uAB = AB / lAB
Middle point of chord
mAB = (A + B)/2
Arrow value
F = R - sqrt(R*R - lAB*lAB/4)
Now middle of arc:
M.x = mAB.x - uAB.Y * F
M.y = mAB.y + uAB.X * F
Note that there are two points (you need to know circle center orientation relatice to AB), for the second one change signs of the second terms
Related
One way to apply links to text is to use the force.links() array for the text elements and centre the text on the midpoint of the link.
I have some nodes with bidirectional links, which I've rendered as paths that bend at their midpoint to ensure it's clear that there's two links between the two nodes.
For these bidirectional links, I want to move the text so that it sits correctly over the bending path.
To do this, I've attempted to calculate the intersection(s) of a circle centred on the centre point of the link and a line running perpendicular to the link that also passes through its centre. I think in principal this makes sense, and it seems to be half working, but I'm not sure how to define which coordinate returned through calculating the intersections to apply to which label, and how to stop them jumping between the curved links when I move the nodes around (see jsfiddle - https://jsfiddle.net/sL3au5fz/6/).
The function for calculating coordinates of text on arcing paths is as follows:
function calcLinkTextCoords(d,coord, calling) {
var x_coord, y_coord;
//find centre point of coords
var cp = [(d.target.x + d.source.x)/2, (d.target.y + d.source.y)/2];
// find perpendicular gradient of line running through coords
var pg = -1 / ((d.target.y - d.source.y)/(d.target.x - d.source.x));
// define radius of circle (distance from centre point text will appear)
var radius = Math.sqrt(Math.pow(d.target.x - d.source.x,2) + Math.pow(d.target.y - d.source.y,2)) / 5 ;
// find x coord where circle with radius 20 centred on d midpoint meets perpendicular line to d.
if (d.target.y < d.source.y) {
x_coord = cp[0] + (radius / Math.sqrt(1 + Math.pow(pg,2)));
} else {
x_coord = cp[0] - (radius / Math.sqrt(1 + Math.pow(pg,2)));
};
// find y coord where x coord is x_text and y coord falls on perpendicular line to d running through midpoint of d
var y_coord = pg * (x_coord - cp[0]) + cp[1];
return (coord == "x" ? x_coord : y_coord);
};
Any help either to fix the above or propose another way to achieve this would be appreciated.
Incidentally I've tried using textPath to line my text up with my links but I don't find that method to be performant when displaying upward of 30-40 nodes and links.
Update: Amended above function and now works as intended. Updated fiddle here:https://jsfiddle.net/o82c2s4x/6/
You can calculate the projection of the chord to x and y axis and add it to the source node coordinates:
function calcLinkTextCoords(d,coord) {
//find chord length
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var chord = Math.sqrt(dx*dx + dy*dy);
//Saggita
// since radius is equal to chord
var sag = chord - Math.sqrt(chord*chord - Math.pow(chord/2,2));
//Find the angles
var t1 = Math.atan2(sag, chord/2);
var t2 = Math.atan2(dy,dx);
var teta = t1+t2;
var h = Math.sqrt(sag*sag + Math.pow(chord/2,2));
return ({x: d.source.x + h*Math.cos(teta),y: d.source.y + h*Math.sin(teta)});
};
Here is the updated JsFiddle
I'm trying to make a project where the user can draw arrows in a canvas and i need a curved line for that.
As you know one quadratic curve is represented by something like that:
M 65 100 Q 300, 100, 300, 20
Where the first two numbers(65, 100) represents the starting point coordinates, the last two (300,20) represents the ending point coordinates(arrow end).
I need to calculate the middle two numbers based on the first and second point, to make a nice looking curved line.
The first point will have the coordinates from mousedown and the second point from mouseup.
For now i'm using like this.
function addCurve(Ax, Ay, Bx, By){
canvas.add(new fabric.Path('M '+ Ax +' '+ Ay +' Q 100, 100, '+ Bx +', '+ By +'', { fill: '', stroke: 'red' }));
}
addCurve(100,0,200,0);
So, how to calculate the middle point coordinates to get an uniform curve?
I'm also using fabric.js in this project.
First start with the two end points
x1 = ? // start point
y1 = ?
x2 = ? // end point
y2 = ?
To get the mid point
mx = (x1 + x2) / 2;
my = (y1 + y2) / 2;
You will need the vector from first to second point
vx = x2 - x1;
vy = y2 - y1;
The line at 90deg (clockwise or right) from the start and end points is
px = -vy; // perpendicular
py = vx;
The line is the same length as the distance between the two points. The quadratic curve will extend out half the distance that the control point is from the line. So if we want the curve to be 1/4 out by length then half the p vector and add to mid point
cx = mx + px / 2; // get control point
cy = my + py / 2;
If you want the curve to bend the other way
cx = my - px / 2;
cy = my - py / 2;
Or you can write it with the curve amount as a var
var curveAmount = 0.25; // How far out the curve is compared to the line length
cx = my - px * (curveAmount * 2);
cy = my - py * (curveAmount * 2);
Make curveAmount bigger for more curve, smaller for less. Zero for no curve at all and negative to bend the other way.
I need to draw a line in the following manner:
For now, it will be only drawn in code, no user input.
My question is, how to draw perpendiculars to a line, if I draw it point by point? (Obviously, this will be the case, because drawing with bezier curves will not give me the possibility to somehow impact the drawing).
The closest answer I found was possibly this one, but I can't reverse the equations to derive C. Also there is no length of the decoration mentioned, so I think this will not work as I'd like it to.
Find the segment perpendicular to another one is quite easy.
Say we have points A, B.
Compute vector AB.
Normalize it to compute NAB (== the 'same' vector, but having a length of 1).
Then if a vector has (x,y) as coordinates, its normal vector has (-y,x) as coordinates, so
you can have PNAB easily (PNAB = perpendicular normal vector to AB).
// vector AB
var ABx = B.x - A.x ;
var ABy = B.y - A.y ;
var ABLength = Math.sqrt( ABx*ABx + ABy*ABy );
// normalized vector AB
var NABx = ABx / ABLength;
var NABy = ABy / ABLength;
// Perpendicular + normalized vector.
var PNABx = -NABy ;
var PNABy = NABx ;
last step is to compute D, the point that is at a distance l of A : just add l * PNAB to A :
// compute D = A + l * PNAB
var Dx = A.x + l* PNAB.x;
var Dy = A.y + l *PNAB.y;
Updated JSBIN :
http://jsbin.com/bojozibuvu/1/edit?js,output
Edit :
A second step is to draw the decorations at regular distance, since it's Christmas time, here's how i would do it :
http://jsbin.com/gavebucadu/1/edit?js,console,output
function drawDecoratedSegment(A, B, l, runningLength) {
// vector AB
var ABx = B.x - A.x;
var ABy = B.y - A.y;
var ABLength = Math.sqrt(ABx * ABx + ABy * ABy);
// normalized vector AB
var NABx = ABx / ABLength;
var NABy = ABy / ABLength;
// Perpendicular + normalized vector.
var PNAB = { x: -NABy, y: NABx };
//
var C = { x: 0, y: 0 };
var D = { x: 0, y: 0 };
//
drawSegment(A, B);
// end length of drawn segment
var endLength = runningLength + ABLength;
// while we can draw a decoration on this line
while (lastDecorationPos + decorationSpacing < endLength) {
// compute relative position of decoration.
var decRelPos = (lastDecorationPos + decorationSpacing) - runningLength;
// compute C, the start point of decoration
C.x = A.x + decRelPos * NABx;
C.y = A.y + decRelPos * NABy;
// compute D, the end point of decoration
D.x = C.x + l * PNAB.x;
D.y = C.y + l * PNAB.y;
// draw
drawSegment(C, D);
// iterate
lastDecorationPos += decorationSpacing;
}
return ABLength;
}
All you need is direction of curve (or polyline segment) in every point, where you want to draw perpendicular.
If direction vector in point P0 is (dx, dy), then perpendicular (left one) will have direction vector (-dy, dx). To draw perpendicular with length Len, use this pseudocode:
Norm = Sqrt(dx*dx + dy*dy) //use Math.Hypot if available
P1.X = P0.X - Len * dy / Norm
P1.Y = P0.Y + Len * dx / Norm
P.S. If you know direction angle A, then direction vector
(dx, dy) = (Cos(A), Sin(A))
and you don't need to calculate Norm, it is equal to 1.0
Is there an easy way to get the lat/lng of the intersection points (if available) of two circles in Google Maps API V3? Or should I go with the hard way?
EDIT : In my problem, circles always have the same radius, in case that makes the solution easier.
Yes, for equal circles rather simple solution could be elaborated:
Let's first circle center is A point, second circle center is F, midpoint is C, and intersection points are B,D. ABC is right-angle spherical triangle with right angle C.
We want to find angle A - this is deviation angle from A-F direction. Spherical trigonometry (Napier's rules for right spherical triangles) gives us formula:
cos(A)= tg(AC) * ctg(AB)
where one symbol denote spherical angle, double symbols denote great circle arcs' angles (AB, AC). We can see that AB = circle radius (in radians, of course), AC = half-distance between A and F on the great circle arc.
To find AC (and other values) - I'll use code from this excellent page
var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
and our
AC = c/2
If circle radius Rd is given is kilometers, then
AB = Rd / R = Rd / 6371
Now we can find angle
A = arccos(tg(AC) * ctg(AB))
Starting bearing (AF direction):
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);
Intersection points' bearings:
B_bearing = brng - A
D_bearing = brng + A
Intersection points' coordinates:
var latB = Math.asin( Math.sin(lat1)*Math.cos(Rd/R) +
Math.cos(lat1)*Math.sin(Rd/R)*Math.cos(B_bearing) );
var lonB = lon1.toRad() + Math.atan2(Math.sin(B_bearing)*Math.sin(Rd/R)*Math.cos(lat1),
Math.cos(Rd/R)-Math.sin(lat1)*Math.sin(lat2));
and the same for D_bearing
latB, lonB are in radians
The computation the "hard" way can be simplified for the case r1 = r2 =: r. We still first have to convert the circle centers P1,P2 from (lat,lng) to Cartesian coordinates (x,y,z).
var DEG2RAD = Math.PI/180;
function LatLng2Cartesian(lat_deg,lng_deg)
{
var lat_rad = lat_deg*DEG2RAD;
var lng_rad = lng_deg*DEG2RAD;
var cos_lat = Math.cos(lat_rad);
return {x: Math.cos(lng_rad)*cos_lat,
y: Math.sin(lng_rad)*cos_lat,
z: Math.sin(lat_rad)};
}
var P1 = LatLng2Cartesian(lat1, lng1);
var P2 = LatLng2Cartesian(lat2, lng2);
But the intersection line of the planes holding the circles can be computed more easily. Let d be the distance of the actual circle center (in the plane) to the corresponding point P1 or P2 on the surface. A simple derivation shows (with R the earth's radius):
var R = 6371; // earth radius in km
var r = 100; // the direct distance (in km) of the given points to the intersections points
// if the value rs for the distance along the surface is known, it has to be converted:
// var r = 2*R*Math.sin(rs/(2*R*Math.PI));
var d = r*r/(2*R);
Now let S1 and S2 be the intersections points and S their mid-point. With s = |OS| and t = |SS1| = |SS2| (where O = (0,0,0) is the earth's center) we get from simple derivations:
var a = Math.acos(P1.x*P2.x + P1.y*P2.y + P1.z*P2.z); // the angle P1OP2
var s = (R-d)/Math.cos(a/2);
var t = Math.sqrt(R*R - s*s);
Now since r1 = r2 the points S, S1, S2 are in the mid-plane between P1 and P2. For v_s = OS we get:
function vecLen(v)
{ return Math.sqrt(v.x*v.x + v.y*v.y + v.z*v.z); }
function vecScale(scale,v)
{ return {x: scale*v.x, y: scale*v.y, z: scale*v.z}; }
var v = {x: P1.x+P2.x, y: P1.y+P2.y, z:P1.z+P2.z}; // P1+P2 is in the middle of OP1 and OP2
var S = vecScale(s/vecLen(v), v);
function crossProd(v1,v2)
{
return {x: v1.y*v2.z - v1.z*v2.y,
y: v1.z*v2.x - v1.x*v2.z,
z: v1.x*v2.y - v1.y*v2.x};
}
var n = crossProd(P1,P2); // normal vector to plane OP1P2 = vector along S1S2
var SS1 = vecScale(t/vecLen(n),n);
var S1 = {x: S.x+SS1.x, y: S.y+SS1.y, z: S.z+SS1.z}; // S + SS1
var S2 = {x: S.x-SS1.x, y: S.y-SS2.y, z: S.z-SS1.z}; // S - SS1
Finally we have to convert back to (lat,lng):
function Cartesian2LatLng(P)
{
var P_xy = {x: P.x, y:P.y, z:0}
return {lat: Math.atan2(P.y,P.x)/DEG2RAD, lng: Math.atan2(P.z,vecLen(P_xy))/DEG2RAD};
}
var S1_latlng = Cartesian2LatLng(S1);
var S2_latlng = Cartesian2LatLng(S2);
Yazanpro, sorry for the late response on this.
You may be interested in a concise variant of MBo's approach, which simplifies in two respects :
firstly by exploiting some of the built in features of the google.maps API to avoid much of the hard math.
secondly by using a 2D model for the calculation of the included angle, in place of MBo's spherical model. I was initially uncertain about the validity of this simplification but satisfied myself with tests in a fork of MBo's fiddle that the errors are minor at all but the largest of circles with respect to the size of the Earth (eg at low zoom levels).
Here's the function :
function getIntersections(circleA, circleB) {
/*
* Find the points of intersection of two google maps circles or equal radius
* circleA: a google.maps.Circle object
* circleB: a google.maps.Circle object
* returns: null if
* the two radii are not equal
* the two circles are coincident
* the two circles don't intersect
* otherwise returns: array containing the two points of intersection of circleA and circleB
*/
var R, centerA, centerB, D, h, h_;
try {
R = circleA.getRadius();
centerA = circleA.getCenter();
centerB = circleB.getCenter();
if(R !== circleB.getRadius()) {
throw( new Error("Radii are not equal.") );
}
if(centerA.equals(centerB)) {
throw( new Error("Circle centres are coincident.") );
}
D = google.maps.geometry.spherical.computeDistanceBetween(centerA, centerB); //Distance between the two centres (in meters)
// Check that the two circles intersect
if(D > (2 * R)) {
throw( new Error("Circles do not intersect.") );
}
h = google.maps.geometry.spherical.computeHeading(centerA, centerB); //Heading from centre of circle A to centre of circle B. (in degrees)
h_ = Math.acos(D / 2 / R) * 180 / Math.PI; //Included angle between the intersections (for either of the two circles) (in degrees). This is trivial only because the two radii are equal.
//Return an array containing the two points of intersection as google.maps.latLng objects
return [
google.maps.geometry.spherical.computeOffset(centerA, R, h + h_),
google.maps.geometry.spherical.computeOffset(centerA, R, h - h_)
];
}
catch(e) {
console.error("getIntersections() :: " + e.message);
return null;
}
}
No disrespect to MBo by the way - it's an excellent answer.
I'm trying to find a point that is equal distance away from the middle of a perpendicular line. I want to use this point to create a Bézier curve using the start and end points, and this other point I'm trying to find.
I've calculated the perpendicular line, and I can plot points on that line, but the problem is that depending on the angle of the line, the points get further away or closer to the original line, and I want to be able to calculate it so it's always X units away.
Take a look at this JSFiddle which shows the original line, with some points plotted along the perpendicular line:
http://jsfiddle.net/eLxcB/1/.
If you change the start and end points, you can see these plotted points getting closer together or further away.
How do I get them to be uniformly the same distance apart from each other no matter what the angle is?
Code snippit below:
// Start and end points
var startX = 120
var startY = 150
var endX = 180
var endY = 130
// Calculate how far above or below the control point should be
var centrePointX = ((startX + endX) / 2);
var centrePointY = ((startY + endY) / 2);
// Calculate slopes and Y intersects
var lineSlope = (endY - startY) / (endX - startX);
var perpendicularSlope = -1 / lineSlope;
var yIntersect = centrePointY - (centrePointX * perpendicularSlope);
// Draw a line between the two original points
R.path('M '+startX+' '+startY+', L '+endX+' '+endY);
Generally you can get the coordinates of a normal of a line like this:
P1 = {r * cos(a) + Cx, -r * sin(a) + Cy},
P2 = {-r * cos(a) + Cx, r * sin(a) + Cy}.
A demo applying this to your case at jsFiddle.