RGB to closest predefined color - php

Edit:
With the answer given I made this function
function grabclosestcolor($r, $g, $b){
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
$differencearray = array();
foreach ($colors as $value) {
$difference = sqrt(pow($r-$value[0],2)+pow($g-$value[1],2)+pow($b-$value[2],2));
array_push($differencearray, $difference);
$smallest = min($differencearray);
$key = array_search($smallest, $differencearray);
return $colors[$key];
}
}
My goal is this. I grab a picture and loop through each pixel and grab its x,y, and rgb.
Instead of just grabbing the rgb, I have a predefined array and I'm looking for the closest match from the color I grabbed to the predefined array.
The goal here is to only use colors from the predefined array.
Here is my array of colors.
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
and here is my existing code that loops through it all.
$int = imagesx($im) - 1;
$int2 = imagesy($im) - 1;
$start2 = 0;
do{
$start = 0;
do{
$rgb = imagecolorat($im, $start, $start2);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$value = rgb2hex($r,$g,$b).":$start:$start2";
array_push($colorsofimage, $value);
} while($int > $start++);
} while($int2 > $start2++);
rgb2hex is a User Defined Function, but what I want to accomplish is to change that function with the function to grab the closest color.
$colorsofimage contains an array of each pixels info with hex:x:y
what i want it to be is rgb2hex(NEWFUNCTION($r,$g,$b));
So that the new hex is the 1 out of the predefined array.
I hope you understood, because I have no clue how to do it because I don't know the algorithm of a color.

You have to calculate the distance to each color, and pick the smallest.
There are a few ways to do this. A simple method would be to calculate the distance would be:
sqrt((r-r1)^2+(g-g1)^2+(b-b1)^2)
A better method might be to incorporate the weighted values to calculate a distance, for instance the values used when converting RGB->YUV:
Y = 0.299 * R + 0.587 * G + 0.114 * B
in that case you would use
sqrt(((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2)
Of course, since you don't need the exact distances, just a comparison, you can and probably should just skip the square root, making the last calculation:
((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2

The RGB colour-space is simply a cube. In 24-bit colour each side has a length of 256, allowing values from 0 to 255. In order to find the closest colour in within this cube, you need a distance function. The simplest and most intuitive is the Euclidean distance: if you have colour (r1, g1, b1) and another colour (r2, g2, b2) the distance would be sqrt((r2-r1)^2 + (g2-g1)^2 + (b2-b1)^2).
The challenge for you is then to find the best match across all the values in your predefined array. I suggest that you start simply by iterating over all your values and check the distance for each in turn. Note that for this purpose you do not need to perform the sqrt, simply comparing on the sum of the squares would be sufficient, and would have the benefit of being all based in integer maths. My PHP isn't great, but roughly you would do:
function dist($col1,$col2) {
$delta_r = $col1[0] - $col2[0];
$delta_g = $col1[1] - $col2[1];
$delta_b = $col1[2] - $col2[2];
return $delta_r * $delta_r + $delta_g * $delta_g + $delta_b * $delta_b;
}
$closest=$colors[0];
$mindist=dist($rgb,$colors[0]);
$ncolors=sizeof($colors);
for($i = 1; $i < $ncolors; ++$i)
{
$currdist = dist($rgb,$colors[$i]);
if($currdist<$mindist) {
$mindist=$currdist;
$closest=$colors[$i];
}
}
There are more complicated distance functions (for instance, taking better account of psychovisual interpretation of colour differences (look into Delta E) but I suspect this is more than you need.

Since this question is displayed in the top ten of goolge search results, here is a more complex function I wrote some years ago, which produced better results than the existing PHP functions.
/*
* Die Funktion gibt den Array-Schlüssel der Farbe ($palette),
* die am ehesten der Farbe $givenColor entspricht.
*
* Returns the index of the palette-color which is most similar
* to $givenColor.
*
* $givenColor und die Einträge in $palette können entweder
* Strings im Format (#)rrggbb
* (z. B. "ff0000", "4da4f3" oder auch "#b5d7f3")
* oder Arrays mit je einem Wert für Rot, Grün und Blau
* (z. B. $givenColor = array( 0xff, 0x00, 0x00 ) )
* sein.
*
* $givenColor and the colors in $palette should be either
* formatted as (#)rrggbb
* (e. g. "ff0000", "4da4f3" or "#b5d7f3")
* or arrays with values for red, green and blue
* (e. g. $givenColor = array( 0xff, 0x00, 0x00 ) )
*
* Referenzen/References:
* function rgb2lab
* - http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHilfe/farbraumJava.htm
* - http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
* - http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
*
* function deltaE
* - http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
*/
function getNearestColor( $givenColor,
$palette = array('blue' => '0000ff','red' => 'ff0000','green' => '00ff00','yellow' => 'ffff00','black' => '000000','white' => 'ffffff','orange' => 'ff8800','purple' => 'ff00ff', 'teal' => '00ffff')
)
{
if(!function_exists('rgb2lab'))
{
function rgb2lab($rgb) {
$eps = 216/24389; $k = 24389/27;
// reference white D50
$xr = 0.964221; $yr = 1.0; $zr = 0.825211;
// reference white D65
#$xr = 0.95047; $yr = 1.0; $zr = 1.08883;
// RGB to XYZ
$rgb[0] = $rgb[0]/255; //R 0..1
$rgb[1] = $rgb[1]/255; //G 0..1
$rgb[2] = $rgb[2]/255; //B 0..1
// assuming sRGB (D65)
$rgb[0] = ($rgb[0] <= 0.04045)?($rgb[0]/12.92):pow(($rgb[0]+0.055)/1.055,2.4);
$rgb[1] = ($rgb[1] <= 0.04045)?($rgb[1]/12.92):pow(($rgb[1]+0.055)/1.055,2.4);
$rgb[2] = ($rgb[2] <= 0.04045)?($rgb[2]/12.92):pow(($rgb[2]+0.055)/1.055,2.4);
// sRGB D50
$x = 0.4360747*$rgb[0] + 0.3850649*$rgb[1] + 0.1430804*$rgb[2];
$y = 0.2225045*$rgb[0] + 0.7168786*$rgb[1] + 0.0606169*$rgb[2];
$z = 0.0139322*$rgb[0] + 0.0971045*$rgb[1] + 0.7141733*$rgb[2];
// sRGB D65
/*$x = 0.412453*$rgb[0] + 0.357580*$rgb[1] + 0.180423*$rgb[2];
$y = 0.212671*$rgb[0] + 0.715160*$rgb[1] + 0.072169*$rgb[2];
$z = 0.019334*$rgb[0] + 0.119193*$rgb[1] + 0.950227*$rgb[2];*/
// XYZ to Lab
$xr = $x/$xr; $yr = $y/$yr; $zr = $z/$zr;
$fx = ($xr > $eps)?pow($xr, 1/3):($fx = ($k * $xr + 16) / 116); $fy = ($yr > $eps)?pow($yr, 1/3):($fy = ($k * $yr + 16) / 116); $fz = ($zr > $eps)?pow($zr, 1/3):($fz = ($k * $zr + 16) / 116);
$lab = array();
$lab[] = round(( 116 * $fy ) - 16); $lab[] = round(500*($fx-$fy)); $lab[] = round(200*($fy-$fz));
return $lab;
} // function rgb2lab
}
if(!function_exists('deltaE'))
{
function deltaE($lab1, $lab2)
{
// CMC 1:1
$l = 1; $c = 1;
$c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]); $c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
$h1 = (((180000000/M_PI) * atan2($lab1[1],$lab1[2]) + 360000000) % 360000000)/1000000;
$t = (164 <= $h1 AND $h1 <= 345)?(0.56 + abs(0.2 * cos($h1+168))):(0.36 + abs(0.4 * cos($h1+35)));
$f = sqrt(pow($c1,4)/(pow($c1,4) + 1900));
$sl = ($lab1[0] < 16)?(0.511):((0.040975*$lab1[0])/(1 + 0.01765*$lab1[0]));
$sc = (0.0638 * $c1)/(1 + 0.0131 * $c1) + 0.638;
$sh = $sc * ($f * $t + 1 -$f);
return sqrt( pow(($lab1[0]-$lab2[0])/($l * $sl),2) + pow(($c1-$c2)/($c * $sc),2) + pow(sqrt(($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1]) + ($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]) + ($c1-$c2)*($c1-$c2))/$sh,2) );
} // function deltaE
}
if(!function_exists('colorDistance'))
{
function colorDistance($lab1,$lab2)
{
return sqrt(($lab1[0]-$lab2[0])*($lab1[0]-$lab2[0])+($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1])+($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]));
}
}
if(!function_exists('str2rgb'))
{
function str2rgb($str)
{
$str = preg_replace('~[^0-9a-f]~','',$str);
$rgb = str_split($str,2);
for($i=0;$i<3;$i++)
$rgb[$i] = intval($rgb[$i],16);
return $rgb;
} // function str2rgb
}
// split into RGB, if not already done
$givenColorRGB = is_array($givenColor)?$givenColor:str2rgb($givenColor);
$min = 0xffff;
$return = NULL;
foreach($palette as $key => $color)
{
// split into RGB
$color = is_array($color)?$color:str2rgb($color);
// deltaE
#if($min >= ($deltaE = deltaE(rgb2lab($color),rgb2lab($givenColorRGB))))
// euclidean distance
if($min >= ($deltaE = colorDistance(rgb2lab($color),rgb2lab($givenColorRGB))))
{
$min = $deltaE;
$return = $key;
}
}
return $return;
}

Calculate the distance from the input color to all possible candidates of your palette, and then pick the one with the smallest distance as the one to replace it with.
Distance can be defined in any way you like; Euclidean distance seems workable for RGB cubes, cylinders or HSL/HSV cones.

There is no point in taking the square root. Finding the shortest distance is the same as finding the shortest squared distance. sqrt is an expensive operation, so just skip it.
Whether it really matters, of course, depends of how often your program will make this calculation, but it's still pointless to have it.

Related

How to match a string in an array list and if not found then find the closest string to match that using PHP? [duplicate]

Edit:
With the answer given I made this function
function grabclosestcolor($r, $g, $b){
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
$differencearray = array();
foreach ($colors as $value) {
$difference = sqrt(pow($r-$value[0],2)+pow($g-$value[1],2)+pow($b-$value[2],2));
array_push($differencearray, $difference);
$smallest = min($differencearray);
$key = array_search($smallest, $differencearray);
return $colors[$key];
}
}
My goal is this. I grab a picture and loop through each pixel and grab its x,y, and rgb.
Instead of just grabbing the rgb, I have a predefined array and I'm looking for the closest match from the color I grabbed to the predefined array.
The goal here is to only use colors from the predefined array.
Here is my array of colors.
$colors = array(array(124,12,12),array(7,7,11),array(110,224,219),array(123,123,123),array(124,177,74),array(130,86,53),array(77,77,77),array(164,124,68),array(204,196,132),array(164,148,147),array(163,123,67),array(26,122,26), array(195,195,50),array(193,193,193),array(255,248,73),array(243,243,243));
and here is my existing code that loops through it all.
$int = imagesx($im) - 1;
$int2 = imagesy($im) - 1;
$start2 = 0;
do{
$start = 0;
do{
$rgb = imagecolorat($im, $start, $start2);
$r = ($rgb >> 16) & 0xFF;
$g = ($rgb >> 8) & 0xFF;
$b = $rgb & 0xFF;
$value = rgb2hex($r,$g,$b).":$start:$start2";
array_push($colorsofimage, $value);
} while($int > $start++);
} while($int2 > $start2++);
rgb2hex is a User Defined Function, but what I want to accomplish is to change that function with the function to grab the closest color.
$colorsofimage contains an array of each pixels info with hex:x:y
what i want it to be is rgb2hex(NEWFUNCTION($r,$g,$b));
So that the new hex is the 1 out of the predefined array.
I hope you understood, because I have no clue how to do it because I don't know the algorithm of a color.
You have to calculate the distance to each color, and pick the smallest.
There are a few ways to do this. A simple method would be to calculate the distance would be:
sqrt((r-r1)^2+(g-g1)^2+(b-b1)^2)
A better method might be to incorporate the weighted values to calculate a distance, for instance the values used when converting RGB->YUV:
Y = 0.299 * R + 0.587 * G + 0.114 * B
in that case you would use
sqrt(((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2)
Of course, since you don't need the exact distances, just a comparison, you can and probably should just skip the square root, making the last calculation:
((r - r1) * .299)^2 + ((g - g1) * .587)^2 + ((b - b1) * .114)^2
The RGB colour-space is simply a cube. In 24-bit colour each side has a length of 256, allowing values from 0 to 255. In order to find the closest colour in within this cube, you need a distance function. The simplest and most intuitive is the Euclidean distance: if you have colour (r1, g1, b1) and another colour (r2, g2, b2) the distance would be sqrt((r2-r1)^2 + (g2-g1)^2 + (b2-b1)^2).
The challenge for you is then to find the best match across all the values in your predefined array. I suggest that you start simply by iterating over all your values and check the distance for each in turn. Note that for this purpose you do not need to perform the sqrt, simply comparing on the sum of the squares would be sufficient, and would have the benefit of being all based in integer maths. My PHP isn't great, but roughly you would do:
function dist($col1,$col2) {
$delta_r = $col1[0] - $col2[0];
$delta_g = $col1[1] - $col2[1];
$delta_b = $col1[2] - $col2[2];
return $delta_r * $delta_r + $delta_g * $delta_g + $delta_b * $delta_b;
}
$closest=$colors[0];
$mindist=dist($rgb,$colors[0]);
$ncolors=sizeof($colors);
for($i = 1; $i < $ncolors; ++$i)
{
$currdist = dist($rgb,$colors[$i]);
if($currdist<$mindist) {
$mindist=$currdist;
$closest=$colors[$i];
}
}
There are more complicated distance functions (for instance, taking better account of psychovisual interpretation of colour differences (look into Delta E) but I suspect this is more than you need.
Since this question is displayed in the top ten of goolge search results, here is a more complex function I wrote some years ago, which produced better results than the existing PHP functions.
/*
* Die Funktion gibt den Array-Schlüssel der Farbe ($palette),
* die am ehesten der Farbe $givenColor entspricht.
*
* Returns the index of the palette-color which is most similar
* to $givenColor.
*
* $givenColor und die Einträge in $palette können entweder
* Strings im Format (#)rrggbb
* (z. B. "ff0000", "4da4f3" oder auch "#b5d7f3")
* oder Arrays mit je einem Wert für Rot, Grün und Blau
* (z. B. $givenColor = array( 0xff, 0x00, 0x00 ) )
* sein.
*
* $givenColor and the colors in $palette should be either
* formatted as (#)rrggbb
* (e. g. "ff0000", "4da4f3" or "#b5d7f3")
* or arrays with values for red, green and blue
* (e. g. $givenColor = array( 0xff, 0x00, 0x00 ) )
*
* Referenzen/References:
* function rgb2lab
* - http://www.f4.fhtw-berlin.de/~barthel/ImageJ/ColorInspector//HTMLHilfe/farbraumJava.htm
* - http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
* - http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
*
* function deltaE
* - http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CMC.html
*/
function getNearestColor( $givenColor,
$palette = array('blue' => '0000ff','red' => 'ff0000','green' => '00ff00','yellow' => 'ffff00','black' => '000000','white' => 'ffffff','orange' => 'ff8800','purple' => 'ff00ff', 'teal' => '00ffff')
)
{
if(!function_exists('rgb2lab'))
{
function rgb2lab($rgb) {
$eps = 216/24389; $k = 24389/27;
// reference white D50
$xr = 0.964221; $yr = 1.0; $zr = 0.825211;
// reference white D65
#$xr = 0.95047; $yr = 1.0; $zr = 1.08883;
// RGB to XYZ
$rgb[0] = $rgb[0]/255; //R 0..1
$rgb[1] = $rgb[1]/255; //G 0..1
$rgb[2] = $rgb[2]/255; //B 0..1
// assuming sRGB (D65)
$rgb[0] = ($rgb[0] <= 0.04045)?($rgb[0]/12.92):pow(($rgb[0]+0.055)/1.055,2.4);
$rgb[1] = ($rgb[1] <= 0.04045)?($rgb[1]/12.92):pow(($rgb[1]+0.055)/1.055,2.4);
$rgb[2] = ($rgb[2] <= 0.04045)?($rgb[2]/12.92):pow(($rgb[2]+0.055)/1.055,2.4);
// sRGB D50
$x = 0.4360747*$rgb[0] + 0.3850649*$rgb[1] + 0.1430804*$rgb[2];
$y = 0.2225045*$rgb[0] + 0.7168786*$rgb[1] + 0.0606169*$rgb[2];
$z = 0.0139322*$rgb[0] + 0.0971045*$rgb[1] + 0.7141733*$rgb[2];
// sRGB D65
/*$x = 0.412453*$rgb[0] + 0.357580*$rgb[1] + 0.180423*$rgb[2];
$y = 0.212671*$rgb[0] + 0.715160*$rgb[1] + 0.072169*$rgb[2];
$z = 0.019334*$rgb[0] + 0.119193*$rgb[1] + 0.950227*$rgb[2];*/
// XYZ to Lab
$xr = $x/$xr; $yr = $y/$yr; $zr = $z/$zr;
$fx = ($xr > $eps)?pow($xr, 1/3):($fx = ($k * $xr + 16) / 116); $fy = ($yr > $eps)?pow($yr, 1/3):($fy = ($k * $yr + 16) / 116); $fz = ($zr > $eps)?pow($zr, 1/3):($fz = ($k * $zr + 16) / 116);
$lab = array();
$lab[] = round(( 116 * $fy ) - 16); $lab[] = round(500*($fx-$fy)); $lab[] = round(200*($fy-$fz));
return $lab;
} // function rgb2lab
}
if(!function_exists('deltaE'))
{
function deltaE($lab1, $lab2)
{
// CMC 1:1
$l = 1; $c = 1;
$c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]); $c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
$h1 = (((180000000/M_PI) * atan2($lab1[1],$lab1[2]) + 360000000) % 360000000)/1000000;
$t = (164 <= $h1 AND $h1 <= 345)?(0.56 + abs(0.2 * cos($h1+168))):(0.36 + abs(0.4 * cos($h1+35)));
$f = sqrt(pow($c1,4)/(pow($c1,4) + 1900));
$sl = ($lab1[0] < 16)?(0.511):((0.040975*$lab1[0])/(1 + 0.01765*$lab1[0]));
$sc = (0.0638 * $c1)/(1 + 0.0131 * $c1) + 0.638;
$sh = $sc * ($f * $t + 1 -$f);
return sqrt( pow(($lab1[0]-$lab2[0])/($l * $sl),2) + pow(($c1-$c2)/($c * $sc),2) + pow(sqrt(($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1]) + ($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]) + ($c1-$c2)*($c1-$c2))/$sh,2) );
} // function deltaE
}
if(!function_exists('colorDistance'))
{
function colorDistance($lab1,$lab2)
{
return sqrt(($lab1[0]-$lab2[0])*($lab1[0]-$lab2[0])+($lab1[1]-$lab2[1])*($lab1[1]-$lab2[1])+($lab1[2]-$lab2[2])*($lab1[2]-$lab2[2]));
}
}
if(!function_exists('str2rgb'))
{
function str2rgb($str)
{
$str = preg_replace('~[^0-9a-f]~','',$str);
$rgb = str_split($str,2);
for($i=0;$i<3;$i++)
$rgb[$i] = intval($rgb[$i],16);
return $rgb;
} // function str2rgb
}
// split into RGB, if not already done
$givenColorRGB = is_array($givenColor)?$givenColor:str2rgb($givenColor);
$min = 0xffff;
$return = NULL;
foreach($palette as $key => $color)
{
// split into RGB
$color = is_array($color)?$color:str2rgb($color);
// deltaE
#if($min >= ($deltaE = deltaE(rgb2lab($color),rgb2lab($givenColorRGB))))
// euclidean distance
if($min >= ($deltaE = colorDistance(rgb2lab($color),rgb2lab($givenColorRGB))))
{
$min = $deltaE;
$return = $key;
}
}
return $return;
}
Calculate the distance from the input color to all possible candidates of your palette, and then pick the one with the smallest distance as the one to replace it with.
Distance can be defined in any way you like; Euclidean distance seems workable for RGB cubes, cylinders or HSL/HSV cones.
There is no point in taking the square root. Finding the shortest distance is the same as finding the shortest squared distance. sqrt is an expensive operation, so just skip it.
Whether it really matters, of course, depends of how often your program will make this calculation, but it's still pointless to have it.

Get in between lat long from two lat long and direction

I am working on one application where I need in between lat long from two lat long.
So for that what I have is
1) Lat long of start point
2) Lat long of end point
3) Direction, like south(180 degree) or north(0 degree)
4) Distance between two points
Now what I will do is I will divide whole path in some number of chunks, like I will divide whole path by 10 KM.
So lets say total distance is 100KM then I will get 10 chunks.
if 200KM then there will be 20 chunks.
So now I want to find out those point's lat long from two lat long.
So all 20 points should be in one line and at equal distance.
How to achieve that ?
You can try taking the difference between the two Lat Lon points, dividing them by the number of chunks. This will give the "increment". This can then be added, or taken away depending on the direction, in a loop to create the points in between.
Consider the following I did as a test:
//Position 1
$lat1 = 53.2226754;
$lon1 = 0.124584;
//Position 2
$lat2 = 52.212445;
$lon2 = -0.12458;
//Differences in Lat / Lon
$latDif = round($lat1 - $lat2, 7);
$lonDif = round($lon1 - $lon2, 7);
//Calculate Step / Increment
//I used 10 as an example for the number of steps
$chunks = 10;
$latStep = round($latDif / $chunks, 7);
$lonStep = round($lonDif / $chunks, 7);
//New Lat Lon starts at start point
$newLat = $lat1;
$newLon = $lon1;
echo $lat1 . ", " . $lon1 . "<br>"; //Start Point
for($i = 1; $i < $chunks; $i++){
//Direction could be substituted here
if($lat1 < $lat2){
$newLat = $newLat + $latStep; //Going North
} else {
$newLat = $newLat - $latStep; //Going South
}
if($lon1 < $lon2){
$newLon = $newLon + $lonStep; //Going East
} else {
$newLon = $newLon - $lonStep; //Going West
}
echo $newLat . ", " . $newLon . "<br>"; //New Point
}
echo $lat2 . ", " . $lon2 . "<br>"; //End Point
This leads to the following Lat Lon output:
53.2226754, 0.124584;
53.1216524, 0.0996676;
53.0206294, 0.0747512;
52.9196064, 0.0498348;
52.8185834, 0.0249184;
52.7175604, 0.0000002;
52.6165374, -0.0249144;
52.5155144, -0.0498308;
52.4144914, -0.0747472;
52.3134684, -0.0996636;
52.212445, -0.12458;
I plotted these on an online geoplanner and got the following:
GPS Results Link
As per #Piskvor's comment, my previous method would not have worked over long distances due to the Mercator Projection of maps.
After some Internet digging, some head-scratching and serious mind boggling, I think I've created a PHP method for calculating the Great Circle path between two Lat Lon points.
For the following example, I'm using the start point as London Heathrow Airport and end point as JFK Intl Airport.
Firstly, we need to calculate the distance between Point A and Point B along the Great Circle that connects them, using the Haversine Formula:
//Must convert to Radians for use with Trig functions
$lat1 = deg2rad(51.4700223); $lon1 = deg2rad(-0.4542955); // LHR
$lat2 = deg2rad(40.6413111); $lon2 = deg2rad(-73.7781391); // JFK
$eRadius = 6367000; // Earth Radius in metres
//Difference between lat and lon
$difLat = $lat2 - $lat1;
$difLon = $lon2 - $lon1;
//Some mathematical magic
$a = sin($difLat / 2) * sin($difLat / 2) +
cos($lat1) * cos($lat2) *
sin($difLon / 2) * sin($difLon / 2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
//Distance from pA to pB along the great circle in metres
$t_distance = $eRadius * $c;
Now we have the total distance we can plan the points of the path:
//Step distance in metres
$sDist = 800000; //800km
//Lat Lon Storage;
$lat = []; //Short hand for array(); PHP > 5.4
$lon = [];
//Number of steps - rounded down!
$steps = floor($t_distance / $sDist);
//Percentage of distance for 1 step
$_f = 100 / $steps;
//CALCULATE POINTS
for($i = 0; $i <= $steps; $i++){
$f = ($_f * $i) / 100; // value between 0-1 corresponding with percentage of step with the step number
$_d = $t_distance / $eRadius; //Angular Distance
$a = sin((1- $f) * $_d) / sin($_d);
$b = sin($f * $_d) / sin($_d);
$x = ($a * cos($lat1) * cos($lon1)) + ($b * cos($lat2) * cos($lon2));
$y = ($a * cos($lat1) * sin($lon1)) + ($b * cos($lat2) * sin($lon2));
$z = ($a * sin($lat1)) + ($b * sin($lat2));
//New Lat Lon point
$nLat = round(atan2($z, sqrt(pow($x, 2) + pow($y, 2))),7);
$nLon = round(atan2($y, $x),7);
//Push to Lat Lon arrays, converting Radians back to Degrees
array_push($lat, rad2deg($nLat));
array_push($lon, rad2deg($nLon));
}
Using a simple foreach loop the Lat Long points are something like:
51.470024866283, -0.45429823575923
53.302510052872, -13.752895032598
53.561704700234, -27.704938735627
52.21669773532, -41.302699715615
49.42997998883, -53.701145438835
45.476181591127, -64.517161945995
40.6413111, -73.7781391
Plotting these points on the online geoplanner will give you this result:
I should also like to point out that these two websites really helped me when creating this method:
http://www.movable-type.co.uk/scripts/latlong.html
http://williams.best.vwh.net/avform.htm

Converting string to integer from form

Need a little help
I have
$_POST["zapremina"]=2000;
$_POST["starost"]="15%";
$_POST["namena"]="50%";
I want simple function to do this
$foo=(2000 - 15%) - 50%;
How to do that?
PHP is loosely typed, so you don't have to cast types explicity or do unnecessary operations (e.g. str_replace)
You can do the following:
$z = $_POST["zapremina"]; //$_POST["zapremina"]=2000;
$s = $_POST["starost"]; //$_POST["starost"]=15%;
$n = $_POST["namena"]; //$_POST["namena"]="50%;
$result = (($z - ($z *($s / 100))) - ($z * ($n / 100)));
Remember to use parentheses to have a readable code and meaningful var names.
Like this:
$starostPercentage = (substr($POST["starost"], 0, -1) / 100);
$namenaPercentage = (substr($POST["namena"], 0, -1) / 100);
$foo = ($_POST["zapremina"] * (100 - $starostPercentage)) * $namenaPercentage;
This is what this does and why:
Convert the percentages (like 15%) from their text form to their decimal form (substr(15%) = 15, 15 / 100 = 0.15).
Calculate $foo with these decimals. 2000 - 15% is what you would write (as a human), but in PHP you need to write that as 2000 * (100 * 0.15), meaning: 85% of 2000).
I'd go with this:
$zap = intval($_POST['zapremina']);
$sta = intval($_POST['starost']);
$nam = intval($_POST['namena']);
$foo = ($zap * ((100-$sta)/100)) * ((100 - $nam)/100)
add this function and then call it
function calculation($a, $b, $c)
{
$b = substr($b, 0, -1) / 100;
$c = substr($c, 0, -1) / 100;
return (($a * $b) * $c);
}
and now you can call
$foo = calculation($_POST["zapremina"], $_POST["starost"], $_POST["namena"]);
go with function most of times, because it will be helpful for reusability.

Implementing Vincenty's Formula in PHP

I've been attempting to implement Vincenty's formulae with the following:
/* Implemented using Vincenty's formulae from http://en.wikipedia.org/wiki/Vincenty%27s_formulae,
* answers "Direct Problem".
* $latlng is a ('lat'=>x1, 'lng'=>y1) array
* $distance is in miles
* $angle is in degrees
*/
function addDistance($latlng, $distance, $bearing) {
//variables
$bearing = deg2rad($bearing);
$iterations = 20; //avoid too-early termination while avoiding the non-convergant case
//knowns
$f = EARTH_SPHEROID_FLATTENING; //1/298.257223563
$a = EARTH_RADIUS_EQUATOR_MILES; //3963.185 mi
$phi1 = deg2rad($latlng['lat']);
$l1 = deg2rad($latlng['lng']);
$b = (1 - $f) * $a;
//first block
$tanU1 = (1-$f)*tan($phi1);
$U1 = atan($tanU1);
$sigma1 = atan($tanU1 / cos($bearing));
$sinalpha = cos($U1)*sin($bearing);
$cos2alpha = (1 - $sinalpha) * (1 + $sinalpha);
$usquared = $cos2alpha * (($a*$a - $b*$b) / 2);
$A = 1 + ($usquared)/16384 * (4096+$usquared*(-768+$usquared*(320 - 175*$usquared)));
$B = ($usquared / 1024)*(256*$usquared*(-128 + $usquared * (74 - 47*$usquared)));
//the loop - determining our value
$sigma = $distance / ($b * $A);
for($i = 0; $i < $iterations; ++$i) {
$twosigmam = 2*$sigma1 + $sigma;
$delta_sigma = $B * sin($sigma) * (cos($twosigmam)+(1/4)*$B*(cos(-1 + 2*cos(cos($twosigmam))) - (1/6)*$B*cos($twosigmam)*(-3+4*sin(sin($sigma)))*(-3+4*cos(cos($twosigmam)))));
$sigma = $distance / ($b * $A) + $delta_sigma;
}
//second block
$phi2 = atan((sin($U1)*cos($sigma)+cos($U1)*sin($sigma)*cos($bearing)) / ((1-$f) * sqrt(sin($sinalpha) + pow(sin($U1)*sin($sigma) - cos($U1)*cos($sigma)*cos($bearing), 2))));
$lambda = atan((sin($sigma) * sin($bearing)) / (cos($U1)*cos($sigma) - sin($U1)*sin($sigma)*cos($bearing)));
$C = ($f / 16)* $cos2alpha * (4+$f*(4-3*$cos2alpha));
$L = $lambda - (1 - $C) * $f * $sinalpha * ($sigma + $C*sin($sigma)*(cos($twosigmam)+$C*cos($sigma)*(-1+2*cos(cos($twosigmam)))));
$alpha2 = atan($sinalpha / (-sin($U1)*sin($sigma) + cos($U1)*cos($sigma)*cos($bearing)));
//and return our results
return array('lat' => rad2deg($phi2), 'lng' => rad2deg($lambda));
}
var_dump(addDistance(array('lat' => 93.129, 'lng' => -43.221), 20, 135);
The issue is that the results are not reasonable - I'm getting variances of up to 20 latitude and longitude keeping the distance at 20. Is it not in units of elliptical distance on the sphere? Am I misunderstanding something, or is my implementation flawed?
There are a number of errors in transcription from the wikipedia page Direct Problem section:
Your u2 expression has 2 in the denominator where it should have b2;
Your A and B expressions are inconsistent about whether the initial fraction factor needs to be parenthesised to correctly express a / b * c as (a/b) * c - what happens without parentheses is a php syntax issue which I don't know the answer to, but you should favour clarity;
You should be iterating "until there is no significant change in sigma", which may or may not happen in your fixed number of iterations;
There are errors in your DELTA_sigma formula:
on the wikipedia page, the first term inside the square bracket [ is cos sigma (-1 etc, whereas you have cos (-1 etc, which is very different;
in the same formula and also later, note that cos2 x means (cos x)(cos x), not cos cos x!
Your phi_2 formula has a sin($sinalpha) where it should have a sin($sinalpha)*sin($sinalpha);
I think that's all.
Have you tried this:
https://github.com/treffynnon/Geographic-Calculations-in-PHP

Algorithm to portion

I have a number X, consider X = 1000
And I want piecemeal this number at three times, then Y = 3, then X = (X / 3)
This will give me equal, just not always accurate, so I need: a percentage value is set, also consider K = 8, K is the percentage, but what I want to do? I want the first portion has a value over 8% in K, suppose that 8% are: 500 and the other two plots are 250, 250
The algorithm is basically what I need it, add a percentage value for the first installment and the other equals
EDIT
I just realized, this is far simpler than I made it. To find the value of $div in my original answer you can just:
$div = (int)($num / ($parcels + $percent / 100));
Then the $final_parcels will be the same as below. Basically, the line above replaces the while loop entirely. Don't know what I was thinking.
/EDIT
I think this will do what you want... unless I am missing something.
<?php
$num = 1000;
$percent = 8;
$parcels = 3;
$total = PHP_INT_MAX;
$div = (int)($num / $parcels);
while ($total > $num) {
$div -= 1;
$total = (int)($div * ($parcels + $percent / 100));
}
$final_parcels = array();
$final_parcels[] = ($num - (($parcels - 1) * $div));
for ($i = 1; $i < $parcels; $i++) {
$final_parcels[] = $div;
}
print_r($final_parcels);
This output will be
Array
(
[0] => 352
[1] => 324
[2] => 324
)
324 * 1.08 = 350.
352 + 324 * 2 = 1000.
Let $T is your total X, $n is a number of parts and $K is percentage mentioned above. Than
$x1 = $T / $n + $T * $K / 100;
$x2 = $x3 = .. = $xn = ($T - $x1) / ($n - 1);
Applied to your example:
$x1 = 1000 / 3 + 1000 * 0.03 = 363.3333333333333333333333333333
// you could round it if you want
// lets round it to ten, as you mentioned
$x1 = round($x1, -1) = 360
$x2 = $x3 = (1000 - 360) / 2 = 320
Extra for the first piece W = X*K/100
Remaining Z = X-W
Each non-first piece = Z/Y = (X-W)/Y = (100-K)*X/(100*Y)
The first piece = W + (100-K)*X/(100*Y) = X*K/100 + (100-K)*X/(100*Y)

Categories