Weedpacket;10943087 wrote:It does (its the distance of the focus as a fraction of the semimajor axis), but it's not enough: for a given eccentricity and given start/end points there are an infinite number of matching elliptical arcs.
That's what I suspected and meant with "unique".
And speaking of Imagick, it also has an arc function which takes coordinates for a bounding box and two angles as parameters. Though I'm not sure it's that easy to use unless you have n*45 degree angles for L(p1, p2).
Anyway, it's been a long time since I did any trig, so I thought I'd give this a shot. The code could certainly be approved upon, especially moving everything that deals with the different quadrants into one central place, and moreover handle it in a consistent manner.
I just coded on and implemented the quad fixes as needed all over the place and in somewhat different manners as well. You'd have to test and see.
Also, it wouldn't surprise me if the arc sometimes ends up a pixel or two off due to rounding. And if you have an image of a square map, you will need an image bigger than the map to fit the arc when the points are close to the edges (or draw the arc on the other side).
I also ditched the process of dealing with geo coordinates. I do however use the same origo (center of the image), assuming your map has geo(0, 0) at the center of the image, which should be the intersection the greenwich meridian and the equator unless I'm misstaken. So in the end, coordinate translation takes place to fit the image coordinate system where upper left corner is 0, 0, positive x is to the right and positive y is DOWN.
But geo coordinates go from -180 to 180 on the x-axis and -85 to 85 on the y-axis iirc. As such, simply dividing coordinate components x, y by 360 and 170 respecitively, followed by multiplication of image width and image height respectively should solve this.
class Slice {
public $r;
public $cx;
public $cy;
public $a1;
public $a2;
/* Only works for values >= 0.5
* 0.5 means the center of the circle for the arc is placed in the middle
of line L($p1, $p2)
* Lower value => more curved arc. */
private static $radiusToLineLengthRatio = 1;
public function __construct($r, $cx, $cy, $a1, $a2) {
$this->r = $r;
$this->cx = $cx;
$this->cy = $cy;
$this->a1 = $a1;
$this->a2 = $a2;
}
public static function getQuad(&$left, &$right) {
if ($left['x'] > $right['x']) {
$tmp = $right;
$right = $left;
$left = $tmp;
}
if ($left['y'] <= $right['y'])
return 1;
else
return 4;
}
# factory function. eventually calls new self(...) with appropriate values
public static function sliceFromLine($p1, $p2, $img, $side = 'left') {
$r = self::radiusFromLineLength($p1, $p2);
# sort points from left to right and return quad 1 or 4
$quad = self::getQuad($p2, $p1);
if ($p1['x'] == $p2['x']) {
$angle = -90;
}
else {
$diffY = $p2['y'] - $p1['y'];
$diffX = $p2['x'] - $p1['x'];
# Will give negative angle when line is in fourth quad.
$angle = radToDeg(atan( ($diffY / $diffX )));
}
# Center point of Line($p1, $p2), CPL
$middle_x = ($p1['x'] + $p2['x']) / 2;
$middle_y = ($p1['y'] + $p2['y']) / 2;
# calculate center point of circle
$half_line_length = lineLength($p1, $p2) / 2;
$bisector_length = sqrt(pow($r, 2) - pow($half_line_length, 2));
# To draw arc on the other side of L($p1, $p2)
if ($quad == 1) {
if ($side == 'left')
$bisector_angle = 90 + $angle;
else
$bisector_angle = $angle + 90;
}
else if ($quad == 4) {
$bisector_angle = $angle + 90;
}
# x and y components of bisector
$bs_x = $bisector_length * cos(degToRad($bisector_angle));
$bs_y = $bisector_length * sin(degToRad($bisector_angle));
# center position of circle
# To draw arc on the other side of L($p1, $p2), deal with subtraction as needed.
if ($quad == 1) {
if ($side == 'right') {
$cx = $middle_x - $bs_x;
$cy = $middle_y - $bs_y;
}
else {
$cx = $middle_x + $bs_x;
$cy = $middle_y + $bs_y;
}
}
if ($quad == 4) {
if ($side == 'right') {
$cx = $middle_x + $bs_x;
$cy = $middle_y + $bs_y;
}
else {
$cx = $middle_x - $bs_x;
$cy = $middle_y - $bs_y;
}
}
# debug output
drawLine($img, array($cx, $cy), array($middle_x, $middle_y));
drawLine($img, array($cx, $cy), $p1);
#drawLine($img, array($cx, $cy), $p2);
# angles from circle center to arc points and the x-axis
$a1 = radToDeg(asin( ($p1['y'] - $cy) / $r ));
$a2 = radToDeg(asin( ($p2['y'] - $cy) / $r));
// deal with angles greater than 90 degrees.
if ($cx > $p2['x'])
$a2 = 180 - $a2;
if ($quad == 1 && $side == 'right') {
$tmp = $a1;
$a1 = $a2;
$a2 = $tmp;
if ($angle > 45)
$a2 = 180 - $a2;
}
if ($quad == 4 && $side == 'left') {
$tmp = $a1;
$a1 = $a2;
$a2 = $tmp;
}
else if ($quad == 4 && $side == 'right' && abs($bisector_angle) < 45) {
$a1 = 180 - $a1;
}
return new self($r, $cx, $cy, $a1, $a2);
}
private static function radiusFromLineLength($p1, $p2) {
$len = lineLength($p1, $p2);
return self::$radiusToLineLengthRatio * $len;
}
}
function radToDeg($rad) {
return 180 * $rad / pi();
}
function degToRad($deg) {
return $deg * pi() / 180;
}
# Length of line describe by two points
function lineLength($p1, $p2) {
return sqrt(pow($p1['x'] - $p2['x'], 2) + pow($p1['y'] - $p2['y'], 2));
}
# draws the arc of Slice $slice
function drawSlice($img, $slice) {
// allocate some colors
$white = imagecolorallocate($img, 255, 255, 255);
$red = imagecolorallocate($img, 255, 0, 0);
$green = imagecolorallocate($img, 0, 255, 0);
$blue = imagecolorallocate($img, 0, 0, 255);
# slice values
$r = $slice->r;
$p = imageCoordinates(array($slice->cx, $slice->cy), $img);
$a = imageAngles(array($slice->a1, $slice->a2));
$cx = $p[0];
$cy = $p[1];
$a1 = $a[0];
$a2 = $a[1];
imagearc($img, $cx, $cy, 2*$r, 2*$r, $a1, $a2, $white);
header("Content-type: image/png");
imagepng($img);
imagedestroy($img);
}
function drawLine($img, $p1, $p2) {
$white = imagecolorallocate($img, 255, 255, 255);
$p1 = imageCoordinates($p1, $img);
$p2 = imageCoordinates($p2, $img);
#exit;
imageline($img, $p1[0], $p1[1], $p2[0], $p2[1], $white);
}
function imageCoordinates($p, $img) {
if (isset($p['x']))
$p[0] = $p['x'];
if (isset($p['y']))
$p[1] = $p['y'];
$w = imagesx($img);
$h = imagesy($img);
$result = array($w / 2 + $p[0], $h / 2 - $p[1]);
return $result;
}
function imageAngles($angles) {
$result = array();
foreach($angles as $a) {
if ( ($ang = 360 - $a) > 360 )
$ang -= 360;
$result[] = $ang;
}
return $result;
}
$img = imagecreatetruecolor(1000, 1000);
$p1 = array('x' => 0,
'y' => 0);
$p2 = array('x' => 300,
'y' => 0);
$slice = Slice::sliceFromLine($p1, $p2, $img, 'right');
# debug output
drawLine($img, $p1, $p2);
drawSlice($img, $slice);
Further stuff to do
Keep angles as radians all the way until calling the imageangle function. This means you could remove radToDeg function.
Remove debug output (code is drawing the line, one side of the circle slice and the bisector atm)
Make use of a swapargs($arg1, $arg2) function since that's done in a couple of places.
Do note that the $side argument for the factory function sliceFromLine specifies on which side the circle center goes, not the side where the arc is drawn. And I don't remember if I made right or left mean "below" in the special case where you have a horizontal line.