biigle / core

:large_blue_circle: Application core of BIIGLE
https://biigle.de
GNU General Public License v3.0
12 stars 16 forks source link

Add the ellipse shape #106

Closed mzur closed 6 years ago

mzur commented 6 years ago

In order to support all data of the old DIAS system we want to add a new ellipse shape.

mzur commented 6 years ago

There are three options to draw ellipses with the OpenLayers canvas renderer.

  1. 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);

    biigle_screenshot_msm29_0440 07_49_31 0099 3 (Note that the ellipse at the bottom middle has a different orientation than with the other methods)

  2. 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);

    biigle_screenshot_msm29_0440 07_49_31 0099 This doesn't look as nice as the result of the ellipse function.

  3. 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);

    biigle_screenshot_msm29_0440 07_49_31 0099 1 This looks a little better than quadraticCurveTo but not as nice as ellipse.

All the code belongs in ol.renderer.canvas.Replay.replay_.

mzur commented 6 years ago

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);

biigle_screenshot_msm29_0440 07_49_31 0099 4 I chose this solution.

mzur commented 6 years ago

Ellipses are fully implemented now. The changes in OpenLayers are quite hacked together but should work for our use case.