Closed mzur closed 6 years ago
There are three options to draw ellipses with the OpenLayers canvas renderer.
The experimental ellipse function. This draws a nice regular ellipse but is only supported in the newest browsers. The ellipse doesn't seem to quite fit into the diamond shape defined by the points (the second axis seems too short). Also, I noticed that ellipses are drawn differently in Firefox and Chrome so I dropped this approach. I think the browsers interpret the angle differently. The function is still experimental after all. Code:
// Center point of the ellipse.
var cx = (x1 + x3) / 2;
var cy = (y1 + y3) / 2;
// Radii of the ellipse.
var r1 = Math.sqrt((x3 - x4) * (x2 - x4) + (y2 - y4) * (y2 - y4)) / 2;
var r2 = Math.sqrt((x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3)) / 2;
// Rotation of the ellipse.
var dx = x1 - x3;
var dy = y1 - y3;
var len = Math.sqrt(dx * dx + dy * dy);
// Angle between vector d and vector (0, 1).
var angle = Math.acos(dy / len);
context.ellipse(cx, cy, r1, r2, angle, 0, 2 * Math.PI);
(Note that the ellipse at the bottom middle has a different orientation than with the other methods)
The quadraticCurveTo function. This draws a quadratic Bezier curve. The control point can be the "corner" between the two points if one imagines the ellipse as a rectangle. I believe this is the way ellipses are drawn in DIAS. Code:
// Vector from p1 to center.
var c1x = (x3 - x1) / 2
var c1y = (y3 - y1) / 2
// Vector from p2 to center.
var c2x = (x4 - x2) / 2
var c2y = (y4 - y2) / 2
context.moveTo(x1, y1);
context.quadraticCurveTo(x1 - c2x, y1 - c2y, x2, y2);
context.quadraticCurveTo(x2 + c1x, y2 + c1y, x3, y3);
context.quadraticCurveTo(x3 + c2x, y3 + c2y, x4, y4);
context.quadraticCurveTo(x4 - c1x, y4 - c1y, x1, y1);
This doesn't look as nice as the result of the
ellipse
function.
The bezierCurveTo function. This draws a cubic Bezier curve. The control points can be "half way" to the "corner" between the two points if one imagines the ellipse as a rectangle. Code:
// Half vector from p1 to center.
var c1x = (x3 - x1) / 4
var c1y = (y3 - y1) / 4
// Half vector from p2 to center.
var c2x = (x4 - x2) / 4
var c2y = (y4 - y2) / 4
context.moveTo(x1, y1);
context.bezierCurveTo(x1 - c2x, y1 - c2y, x2 - c1x, y2 - c1y, x2, y2);
context.bezierCurveTo(x2 + c1x, y2 + c1y, x3 - c2x, y3 - c2y, x3, y3);
context.bezierCurveTo(x3 + c2x, y3 + c2y, x4 + c1x, y4 + c1y, x4, y4);
context.bezierCurveTo(x4 - c1x, y4 - c1y, x1 + c2x, y1 + c2y, x1, y1);
This looks a little better than
quadraticCurveTo
but not as nice as ellipse
.
All the code belongs in ol.renderer.canvas.Replay.replay_
.
Looking at this SO answer I found the value of kappa .5522848
that makes an ellipse drawn with a cubic Bezier curve look nice. Code:
// Offset of the control points in direction of p1 to p3.
// x * .2761424 = x / 2 * .5522848
// where x / 2 is the vector from p1 to the center of the ellipse
// and .5522848 is kappa (https://stackoverflow.com/a/2173084/1796523).
var c1x = (x3 - x1) * .2761424;
var c1y = (y3 - y1) * .2761424;
// Offset of the control points in direction of p2 to p4.
var c2x = (x4 - x2) * .2761424;
var c2y = (y4 - y2) * .2761424;
context.moveTo(x1, y1);
context.bezierCurveTo(x1 - c2x, y1 - c2y, x2 - c1x, y2 - c1y, x2, y2);
context.bezierCurveTo(x2 + c1x, y2 + c1y, x3 - c2x, y3 - c2y, x3, y3);
context.bezierCurveTo(x3 + c2x, y3 + c2y, x4 + c1x, y4 + c1y, x4, y4);
context.bezierCurveTo(x4 - c1x, y4 - c1y, x1 + c2x, y1 + c2y, x1, y1);
I chose this solution.
Ellipses are fully implemented now. The changes in OpenLayers are quite hacked together but should work for our use case.
In order to support all data of the old DIAS system we want to add a new ellipse shape.
[x] Add a new ellipse shape to the DB using a migration.
[x] Look for all places in the backend where annotation shapes play some role and update them (annotations, export,
largomodules). Comment: Largo patch generation should work out of the box. Although an ellipse will be handled like a diamod, the patch padding should take care of the missed area of the arcs between the points.[x] Add a new ellipse drawing tool to the annotation tool. Ellipses are defined by the four points of their principal axes (point index 0 and 2 = first axis, point index 1 and 3 = second axis). Implement a preliminary ellipse shape in OpenLayers that displays ellipses as diamond with the four points. They can be drawn similar to rectangles.
[x] Update the DIAS import script to import ellipses. Change the ordering of the points from
0, 1, 2, 3
to0, 2, 1, 3
.[x] Update the ellipse implementation in OpenLayers to draw arcs instead of lines for the diamond shape. Be sure the draw/edit/translate interactions work. Maybe use this to draw ellipses based on four coordinates? This may be a starting point in OL.
[x] Update the "Creating Annotations" manual with images of a drawn ellipse.
[x]
Migrate the annotation points of all ellipses (stored as rectangles) to whatever is required for the new OpenLayers ellipse implementation.The coordinates are stored correctly from the beginning.