Calculate percentage between two numbers? - php

I have these two numbers original one is 1000 the new one is 440
I need to calculate the percentage between them as follow:
1000
440
The percentage that getting is 44% and left is 56%
my helper function:
function calculatePercentage($original, $given)
{
$percentChange = (($original - $given) / $original) * 100;
return round(abs($percentChange)) . '%';
}
This gives me the reset percentage!

use this
function calculatePercentage($original, $given)
{
$percentageOfGiven = round(abs( ($given * 100) / $original ));
return [
'getting' => $percentageOfGiven . '%',
'left' => (100 - $percentageOfGiven) . '%'
];
}
The output will be like [ 'getting' => "44%", 'left' => "56%"] .
The principle used is that if the original is 100% then what would be the given value.

Wouldn't it be like
function calculatePercentage($original, $given)
{
$percentChange = ($given / $original) * 100;
return round(abs($percentChange)) . '%';
}
If I am wrong, I probably didn't understand ques enough

Related

PHP fake average rating generator

Let's imagine that the material has a rating of 2.7 with 7 votes.
I want to create a script that would generate my desired rating that would look real and not fake.
For example, I want the rating to change from 2.7 to 4.6, but the number of votes should also change proportionally.
I can't figure out how to implement it correctly.
My script will make the rating 6.0 as I want, but as a result the number of votes will be as high as 500+. I want to refine the script so that it is more realistic and changes the rating with minimal votes
My code:
My script will make the rating 6.0 as I want for example, but the number of votes will be as high as 500+. I want to generate the average values of the rating based on the previous values, while the deviation in the votes should not look fake.
<?php
class RatingFaker
{
private $totalRating = 10; // Maximum rating threshold (5 or 10)
private $maxRatingGenerate = 10; // We generate maximum rating values during the passage
private $minRatingGenerarate = 5; // Minimally generates a rating value during a pass
private $minRatingCheck = 3.5; // The minimum acceptable threshold from which we start to act
public function __construct($minRatingCheck)
{
$this->minRatingCheck = $minRatingCheck;
}
private function calcAverageRating($ratings = [])
{
$totalWeight = 0;
$totalReviews = 0;
foreach ($ratings as $weight => $numberofReviews) {
$WeightMultipliedByNumber = $weight * $numberofReviews;
$totalWeight += $WeightMultipliedByNumber;
$totalReviews += $numberofReviews;
}
//divide the total weight by total number of reviews
$averageRating = $totalWeight / $totalReviews;
return [
'rating' => $averageRating,
'vote_num' => $totalReviews
];
}
private function getRandVoters()
{
$randRating = rand($this->minRatingGenerarate, $this->maxRatingGenerate);
$randVoters = rand(1, 99);
return [
$randRating => $randVoters
];
}
private function ratingLoop($valueList)
{
$valueList = array_merge($valueList, $this->getRandVoters());
$newRating = $this->calcAverageRating($valueList);
if($newRating['rating'] < $this->minRatingCheck) {
$valueList = array_merge($valueList, $this->getRandVoters());
return $this->ratingLoop($valueList);
}
if($newRating['rating'] > 10) {
$newRating['rating'] = 10;
}
return [
'rating' => round($newRating['rating'], 1),
'sum' => round($newRating['vote_num'] * round($newRating['rating'], 1)),
'voters' => $newRating['vote_num']
];
}
public function check($currentRate, $currentVoteNum)
{
if($currentRate < $this->minRatingCheck) {
$rating = $this->ratingLoop([
$currentRate => $currentVoteNum,
]);
}
return $rating ?? [];
}
}
$currentRate = 2.4;
$voteNum = 88;
$oldRating = [
'rating' => $currentRate,
'sum' => round($voteNum * $currentRate),
'voters' => $voteNum
];
$rating = [];
$ratingFaker = new RatingFaker(6.0);
$rating = $ratingFaker->check($currentRate, $voteNum);
echo '<pre>';
echo 'Was:';
print_r($oldRating);
echo "<br>";
echo "<br>";
echo "<br>";
echo 'Now:';
print_r($rating);
echo '</pre>';
When the current rating is 2.5 based on 9 votes, and you want to get a rating of 6.0 with a minimal number of votes, you need to make sure the total value of 10 votes is 60 ( 60/10 = 6.0 ).
The current rating is : ( 2.5*9 / 9 ) = 2.5
With you extra vote it is: ( (2.5*9+x) / 10 ) = 6.0
Now you only have to find the correct value for x, which is 37.5

PHP Convert any kg amount to a readable metric unit

I am trying to create a mathematical function (in PHP) that will take a given number of Kg, and convert them to a readable form or better yet, return the unit best suited for that amount. The input will always be kg. Preferably log.
For example:
5 kg = (5) kg
0.5 kg = (500) gm
1000 kg = (1) tonne
0.001 kg = (1) gm
0.0001 kg = (100) mg
I know there is a way to do it using log or log10 functions but I cannot figure it out.
How could it be done?
I think something like this should work, but I'm sure there are people who can make a much better solution.
function outputWeight($kg)
{
$power = floor(log($kg, 10));
switch($power) {
case 5 :
case 4 :
case 3 : $unit = 'ton';
$power = 3;
break;
case 2 :
case 1 :
case 0 : $unit = 'kilogram';
$power = 0;
break;
case -1 :
case -2 :
case -3 : $unit = 'gram';
$power = -3;
break;
case -4 :
case -5 :
case -6 : $unit = 'milligram';
$power = -6;
break;
default : return 'out of range';
}
return ($kg / pow(10, $power)) . ' ' . $unit;
}
echo outputWeight(0.015) . '<br>';
echo outputWeight(0.15) . '<br>';
echo outputWeight(1.5) . '<br>';
echo outputWeight(15) . '<br>';
echo outputWeight(150) . '<br>';
The idea is that you can easily extend the range. This will output
15 gram
150 gram
1.5 kilogram
15 kilogram
150 kilogram
I did not thoroughly test this code!
After playing with it for a while, here is what I came up with
function readableMetric($kg)
{
$amt = $kg * pow(1000, 3);
$s = array('mcg', 'mg', 'gm', 'kg','tonne');
$e = floor(log10($amt)/log10(1000));
return [
"amount" => $amt/pow(1000, $e),
"unit" => $s[$e]
];
}
The following function internally uses an array with the assignments unit => conversion-factor. This array can easily be expanded or modified to meet your own requirements. A fixed limit of 1000 is used in the function. This means that the output value is always less than 1000, with the exception of tons. With an additional argument which is preset to 2, the number of maximum decimal places can be changed.
function scaleKg(float $kg, int $decimalPlaces = 2) : string {
$scale = [
'micrograms' => 1.E9,
'milligram' => 1.E6,
'gram' => 1.E3,
'kilogram' => 1,
'ton' => 1.E-3,
];
foreach($scale as $unit => $factor){
$mass = $kg * $factor;
if($mass < 1000) {
return round($mass,$decimalPlaces).' '.$unit;
}
}
return round($mass,$decimalPlaces).' '.$unit;
}
Some examples:
$kg = 1.212345;
echo scaleKg($kg); //1.21 kilogram
$kg = 455;
echo scaleKg($kg); //455 kilogram
$kg = 0.0456;
echo scaleKg($kg); //45.6 gram
$kg = 23456;
echo scaleKg($kg); //23.46 ton
$kg = 23489000;
echo scaleKg($kg); //23489 ton
$kg = 167E-6;
echo scaleKg($kg); //167 milligram
I find myself agreeing with Kiko that the shortest code isn't always the best or most readable.
Bearing that in mind bringing log into it seems like unnecessary complication. So I suggest a condensed version of my original code:
function readableMetric($mass)
{
$units = [
-3 => "tonne",
0 => "kg",
3 => "g",
6 => "mg",
];
foreach ($units as $x => $unit) {
if ( ($newMass = $mass * 10 ** $x) >= 1 ) {
return "{$newMass} {$unit}";
}
}
}
This is more extensible (e.g. you could easily add centigram)
The math is intuitive to most (powers of 10)
If you really want to golf it you can shrink it down to a one liner:
function readableMetric($m)
{
foreach([-3=>"tonne",0=>"kg",3=>"g",6 =>"mg"] as $x=>$g)if(($n=$m*10**$x)>=1)return"$n $g";
}
Original Answer
You could just do it with a series of if / else statements?
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
if ($mass >= 1000) {
return ($mass / 1000) . " ton";
}
elseif ($mass < 0.001) {
return ($mass * 1000000) . " mg";
}
elseif ($mass < 1) {
return ($mass * 1000 ) . " g";
}
else {
return $mass . " kg";
}
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}
Output:
5 kg
500 g
1 ton
1 g
100 mg
If you want slightly easier/more readable/more user friendly updating to add new measurements in then you can do something like this instead:
$values = [
5, 0.5, 1000, 0.001, 0.0001
];
function convertmass($mass) : string
{
$massLookup = [
[ "unit" => "kg", "min" => 1, "max" => 1000, "multiple" => 1],
[ "unit" => "tonne", "min" => 1000, "max" => 1000000, "multiple" => 0.001],
[ "unit" => "g", "min" => 0.001, "max" => 1, "multiple" => 1000],
[ "unit" => "mg", "min" => 0.000001, "max" => 0.001, "multiple" => 1000000],
];
foreach ($massLookup as $unit) {
if ($mass >= $unit["min"] && $mass < $unit["max"]) {
return ($mass * $unit["multiple"]) . " {$unit["unit"]}";
}
}
return "Measurement {$mass} kg is out of range";
}
foreach ($values as $mass) {
echo convertmass($mass), PHP_EOL;
}

Youtube playlist all videos duration show in php

i want to equal youtube playlist all videos time from this link http://gdata.youtube.com/feeds/api/playlists/PLCK7NnIZXn7gGU5wDy9iKOK6T2fwtGL6l. here have time code like this time='00:05:11.500' .. i want to get all videos time from php then its show like this from php
show it like this : 2:10:50 (2=hours,10=minutes,50=seconds)
i want to variable from php for like this one. plzz help for this post thanks. i tried to do that.. but i can do this.. if someone can plz help me.. if have 4 videos, want to equal all videos time and then want to show all duration from php only
Ok, here's an answer that solves the problem assuming you have no code whatsoever, and no intention of
trying do experiment yourself.
You will probably not be able to use this for anything else than the exact problem described:
adding all the durations of this feed together and displaying it as hours:minutes:seconds
<?php
$total_seconds = 0;
$dom = new DOMDocument();
$dom->loadXML(file_get_contents('http://gdata.youtube.com/feeds/api/playlists/PLCK7NnIZXn7gGU5wDy9iKOK6T2fwtGL6l'));
$xpath = new DOMXPath($dom);
foreach ($xpath->query('//yt:duration/#seconds') as $duration) {
$total_seconds += (int) $duration->value;
}
Then you display $total_seconds in your format. Here's two options:
assuming that hours will never be larger than 24
echo gmdate("H:i:s", $total_seconds);
allowing total time to be larger than 24 hours
echo (int) ($total_seconds / 3600) . ':' . (int) ($total_seconds / 60) % 60 . ':' . $total_seconds % 60;
Keep in mind: This code does exactly ZERO error checking. Things that can go wrong:
The PHP configuration may not allow http stream wrapper
The PHP build might not have Dom enabled
The XML feed may be unavailable
The XML feed might not contain any entries
EDIT:
I took a closer look at the feed, and it seems the "time" entries are just pointers for the thumbnails. The actual duration for a video is set in seconds <yt:duration seconds='667'/> so you could just add them together as integers and then use the DateTime class to convert to whatever your format is. Example here.
END EDIT
First of all, to get all the times, you could need an atom feed reader in PHP. There are plenty out there. Do not try to parse the XML, ATOM is a well known standard that should be easily used (if you really only want the times, you could go with an xpath query).
Now that you have all the times at your disposal, you need a way to add them up easily, preferably without messing with nested loops and if-statements.
Here's a class that represents a single time entry for a single video:
final class Duration
{
private $hours;
private $minutes;
private $seconds;
private $centis;
/* we don't want any Durations not created with a create function */
private function __construct() {}
public static function fromString($input = '00:00:00.000') {
$values = self::valuesFromString($input);
return self::fromValues($values['hours'], $values['minutes'], $values['seconds'], $values['centis']);
}
public function addString($string) {
$duration = self::fromString($string);
return $this->addDuration($duration);
}
public function addDuration(Duration $duration) {
// add the durations, and return a new duration;
$values = self::valuesFromString((string) $duration);
// adding logic here
$centis = $values['centis'] + $this->centis;
$this->fixValue($centis, 1000, $values['seconds']);
$seconds = $values['seconds'] + $this->seconds;
$this->fixValue($seconds, 60, $values['minutes']);
$minutes = $values['minutes'] + $this->minutes;
$this->fixValue($minutes, 60, $values['hours']);
$hours = $values['hours'] + $this->hours;
return self::fromValues($hours, $minutes, $seconds, $centis);
}
public function __toString() {
return str_pad($this->hours,2,'0',STR_PAD_LEFT) . ':'
. str_pad($this->minutes,2,'0',STR_PAD_LEFT) . ':'
. str_pad($this->seconds,2,'0',STR_PAD_LEFT) . '.'
. str_pad($this->centis,3,'0',STR_PAD_LEFT);
}
public function toValues() {
return self::valuesFromString($this);
}
private static function valuesFromString($input) {
if (1 !== preg_match('/(?<hours>[0-9]{2}):(?<minutes>([0-5]{1}[0-9]{1})):(?<seconds>[0-5]{1}[0-9]{1}).(?<centis>[0-9]{3})/', $input, $matches)) {
throw new InvalidArgumentException('Invalid input string (should be 01:00:00.000): ' . $input);
}
return array(
'hours' => (int) $matches['hours'],
'minutes' => (int) $matches['minutes'],
'seconds' => (int) $matches['seconds'],
'centis' => (int) $matches['centis']
);
}
private static function fromValues($hours = 0, $minutes = 0, $seconds = 0, $centis = 0) {
$duration = new Duration();
$duration->hours = $hours;
$duration->minutes = $minutes;
$duration->seconds = $seconds;
$duration->centis = $centis;
return $duration;
}
private function fixValue(&$input, $max, &$nextUp) {
if ($input >= $max) {
$input -= $max;
$nextUp += 1;
}
}
}
You can create a new Duration only by calling the static factory fromString(), and that accepts only strings in the form "00:00:00.000" (hours:minutes:seconds.milliseconds):
$duration = Duration::fromString('00:04:16.250');
Next, you can add another string or an actual duration object, to create a new Duration:
$newDuration = $duration->addString('00:04:16.250');
$newDuration = $duration->addDuration($duration);
The Duration object will output it's own duration string in the format '00:00:00.000':
echo $duration;
// Gives
00:04:16.250
Or, if you're interested in the separate values, you can get them like so:
print_r($duration->toValues());
// Gives
Array
(
[hours] => 0
[minutes] => 4
[seconds] => 16
[milliseconds] => 250
)
Final example for using this in a loop to get the total video time:
$allTimes = array(
'00:30:05:250',
'01:24:38:250',
'00:07:01:750'
);
$d = Duration::fromString();
foreach ($allTimes as $time) {
$d = $d->addString($time);
}
echo $d . "\n";
print_r($d->toValues());
// Gives
02:01:45.250
Array
(
[hours] => 2
[minutes] => 1
[seconds] => 45
[milliseconds] => 250
)
For questions on why I used a final class with private constructor:
I wrote this as an exercise for myself, following Mathias Veraes's blog post on "named constructors".
Also, I couldn't resist adding his "TestFrameworkInATweet" as well:
function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n"; if(!$p){$GLOBALS['f']=1;}}function done(){if(#$GLOBALS['f'])die(1);}
function throws($exp,Closure $cb){try{$cb();}catch(Exception $e){return $e instanceof $exp;}return false;}
it('should be an empty duration from string', Duration::fromString() == '00:00:00.000');
it('should throw an exception with invalid input string', throws("InvalidArgumentException", function () { Duration::fromString('invalid'); }));
it('should throw an exception with invalid seconds input string', throws("InvalidArgumentException", function () { Duration::fromString('00:00:61:000'); }));
it('should throw an exception with invalid minutes input string', throws("InvalidArgumentException", function () { Duration::fromString('00:61:00:000'); }));
it('should add milliseconds to seconds', Duration::fromString('00:00:00.999')->addString('00:00:00.002') == Duration::fromString('00:00:01.001'));
it('should add seconds to minutes', Duration::fromString('00:00:59.000')->addString('00:00:02.000') == Duration::fromString('00:01:01.000'));
it('should add minutes to hours', Duration::fromString('00:59:00.000')->addString('00:02:00.000') == Duration::fromString('01:01:00.000'));
it('should add all levels up', Duration::fromString('00:59:59.999')->addString('00:01:01.002') == Duration::fromString('01:01:01.001'));
$duration = Duration::fromString('00:00:01.500');
it('should add a Duration', $duration->addDuration($duration) == '00:00:03.000');

Finding closest larger resolution with nearest aspect ratio in an array of resolutions

I have an array:
$resolutions = array(
'480x640',
'480x800',
'640x480',
'640x960',
'800x1280',
'2048x1536'
);
I want to retrieve closest larger value with the nearest aspect ratio (same orientation).
So, in case of $needle = '768x1280' - 800x1280.
And, in case of $needle = '320x240' - 640x480. While the closest here is 480x640 it shouldn't be matched, because its aspect ratio differs too much.
So on, and so forth.
Purpose:
I have a set of images with resolutions as specified in $resolutions. Those images are going to be used for smartphone wallpapers.
With JavaScript, I am sending over a request with screen.width and screen.height to determine $needle.
On the server side, I am going to fetch the closest larger value of the given resolution, scale it down to fit the whole screen while preserving aspect ratio, and if something overlaps the dimensions, crop it to perfectly fit the screen.
Problem:
While everything is pretty simple with scaling and cropping, I cannot think of a way to find out the closest larger value, to load the reference image.
Hints:
In case it helps, $resolutions and $needle can be in a different format, ie.: array('width' => x, 'height' => y).
Tries:
I tried to experiment with levenshtein distance: http://codepad.viper-7.com/e8JGOw
Apparently, it worked only for 768x1280 and resulted 800x1280. For 320x240 it resulted in 480x640 but that does not fit this time.
Try this
echo getClosestRes('500x960');
echo '<br /> try too large to match: '.getClosestRes('50000x960');
function getClosestRes($res){
$screens = array(
'landscape'=>array(
'640x480',
'1200x800'
),
'portrait'=>array(
'480x640',
'480x800',
'640x960',
'800x1280',
'1536x2048'
)
);
list($x,$y)=explode('x',$res);
$use=($x>$y?'landscape':'portrait');
// if exact match exists return original
if (array_search($res, $screens[$use])) return $res;
foreach ($screens[$use] as $screen){
$s=explode('x',$screen);
if ($s[0]>=$x && $s[1]>=$y) return $screen;
}
// just return largest if it gets this far.
return $screen; // last one set to $screen is largest
}
Made a quick class. Should competently find the minimum resolution for any two numbers that you specify. I have preloaded it with the resolutions you specified but the $_resolutions array could be set to whichever standards you like, and can also be changed on-the-fly.
class Resolution {
/**
* Standard resolutions
*
* Ordered by smallest to largest width, followed by height.
*
* #var array
*/
private $_resolutions = array(
array('480', '640'),
array('480', '800'),
array('640', '480'),
array('640', '960'),
array('800', '1280'),
array('2048', '1536')
);
/**
* Width
*
* #var int
*/
private $_width;
/**
* Height
*
* #var int
*/
private $_height;
/**
* Constructor
*
* #param int $width
* #param int $height
* #return void
*/
public function __construct($width, $height) {
$this->setSize($width, $height);
}
/**
* Find the minimum matched standard resolution
*
* #param bool $revertToLargest (OPTIONAL) If no large enough resolution is found, use the largest available.
* #param bool $matchAspectRatio (OPTIONAL) Attempt to get the closest resolution with the same aspect ratio. If no resolutions have the same aspect ratio, it will simply use the minimum available size.
* #return array The matched resolution width/height as an array. If no large enough resolution is found, FALSE is returned, unless $revertToLargest is set.
*/
public function getMinimumMatch($revertToLargest = false, $matchAspectRatio = true) {
if ($matchAspectRatio) {
$aspect = $this->_width/$this->_height;
foreach ($this->_resolutions as $res) {
if ($res[0]/$res[1] == $aspect) {
if ($this->_width > $res[0] || $this->_height > $res[1]) {
return ($revertToLargest ? $res : false);
}
return $res;
}
}
}
foreach ($this->_resolutions as $i => $res) {
if ($this->_width <= $res[0]) {
$total = count($this->_resolutions);
for ($j = $i; $j < $total; $j++) {
if ($this->_height <= $this->_resolutions[$j][1]) {
return $this->_resolutions[$j];
}
}
}
}
return ($revertToLargest ? end($this->_resolutions) : false);
}
/**
* Get the resolution
*
* #return array The resolution width/height as an array
*/
public function getSize() {
return array($this->_width, $this->_height);
}
/**
* Set the resolution
*
* #param int $width
* #param int $height
* #return array The new resolution width/height as an array
*/
public function setSize($width, $height) {
$this->_width = abs(intval($width));
$this->_height = abs(intval($height));
return $this->getSize();
}
/**
* Get the standard resolutions
*
* #return array
*/
public function getStandardResolutions() {
return $this->_resolutions;
}
/**
* Set the standard resolution values
*
* #param array An array of resolution width/heights as sub-arrays
* #return array
*/
public function setStandardResolutions(array $resolutions) {
$this->_resolutions = $resolutions;
return $this->_resolutions;
}
}
Example Usage
$screen = new Resolution(320, 240);
$screen->getMinimumMatch();
// Returns 640 x 480 (aspect ratio matched)
$screen = new Resolution(1280, 960);
$screen->getMinimumMatch();
// Returns 640 x 480 (aspect ratio matched)
$screen = new Resolution(400, 960);
$screen->getMinimumMatch();
// Returns 640 x 960 (aspect ratio not matched, so uses closest fit)
$screen = new Resolution(5000, 5000);
$screen->getMinimumMatch();
// Returns FALSE (aspect ratio not matched and resolution too large)
$screen = new Resolution(5000, 5000);
$screen->getMinimumMatch(true);
// Returns 2048 x 1536 (aspect ratio not matched and resolution too large, so uses largest available)
You can first extract the arrays like:
$resolutions = array(
'480x640',
'480x800',
'640x480',
'640x960',
'800x1280',
'2048x1536'
);
foreach ($resolutions as $resolution):
$width[]=(int)$resolution;
$height[]=(int)substr(strrchr($resolution, 'x'), 1);
echo $width,' x ',$height,'<br>';
endforeach;
Then you can match the given needle with the array with in_array and array_search like:
$key = array_search('480', $items);
echo $key;
When you have the key just increment it for the closest greater value. I'll let you do that by yourself.
Okay, I have it. I've written a function that returns the lowest suitable resolution, and accounts for nonstandard resolutions as well.
<?php
//some obscure resolution, for illustrative purposes
$theirResolution = '530x700';
$resolutions = array(
'480x640',
'480x800',
'640x480',
'640x960',
'800x1280',
'2048x1536'
);
function findSmallestResolution($theirResolution,$resolutions){
$temp = explode('x',$theirResolution);
//Isolate their display's X dimension
$theirResolutionX = intval($temp[1]);
foreach($resolutions as $key => $value){
$temp = explode('x',$value);
//if the current resolution is bigger than or equal to theirs in the X dimension, then it's a possibility.
if($theirResolutionX <= intval($temp[1])){
$possibleResolutionsX[] = $value;
}
}
//Now we'll filter our $possibleResolutions in the Y dimension.
$temp = explode('x',$theirResolution);
//Isolate their display's Y dimension
$theirResolutionY = intval($temp[0]);
foreach($possibleResolutionsX as $key => $value){
$temp = explode('x',$value);
//if the current resolution is bigger than or equal to theirs in the X dimension, then it's a possibility.
if($theirResolutionY <= intval($temp[0])){
$possibleResolutionsXY[] = $value;
}
}
//at this point, $possibleResolutionsXY has all of our entries that are big enough. Now to find the smallest among them.
foreach($possibleResolutionsXY as $key => $value){
$temp = explode('x', $value);
//since we didn't specify how standard our app's possible resolutions are, I'll have to measure the smallest in terms of total dots and not simply X and Y.
$dotCount[] = intval($temp[0]) * intval($temp[1]);
}
//find our resolution with the least dots from the ones that still fit the user's.
foreach($dotCount as $key => $value){
if($value == min($dotCount)){
$minkey = $key;
}
}
//use the key from dotCount to find its corresponding resolution from possibleResolutionsXY.
return $possibleResolutionsXY[$minkey];
}
findSmallestResolution($theirResolution,$resolutions);
// returns '640x960'.
?>
Would it be easier if you had a single number to compare against?
It's a ratio, so just do, for example: 640 / 480 = 1.33*
Then you at least have something nice and simple to compare against the dimensions you are sending and presumably come up with a tolerance?
A simple example, which assume that the ratio array is ordered from lowest to highest. If this was a problem then we would create a search that ordered by the area (x by y).
function getNearestRatio($myx, $myy)
{
$ratios = array(
array('x'=>480, 'y'=>640),
array('x'=>480, 'y'=>800),
array('x'=>640, 'y'=>480),
array('x'=>640, 'y'=>960),
array('x'=>800, 'y'=>1280),
array('x'=>2048, 'y'=>1536)
);
$tolerance = 0.1;
foreach ($ratios as $ratio) {
$aspect = $ratio['x'] / $ratio['y'];
$myaspect = $myx / $myy;
if ( ! ($aspect - $tolerance < $myaspect && $myaspect < $aspect + $tolerance )) {
continue;
}
if ($ratio['x'] < $myx || $ratio['y'] < $myy) {
continue;
}
break;
}
return $ratio;
}
I've built in a tolerance, so that it will match 'nearby' aspect ratios, as you allude to in your question.
This function should pass both test cases you have given.
Well, this turned out larger than I anticipated, but I think this meets the criteria.
It works by breaking the available resolutions down to their ratio. Then sorting by the delta between the target ratio and the available ratios ascending, then by size (pixels) descending. Returning the top match - which should be the closest, smallest match.
class ResolutionMatcher
{
private $resolutions;
public function __construct(array $resolutions)
{
foreach ($resolutions as $resolution) {
$this->resolutions[$resolution] = $this->examineResolution($resolution);
}
}
public function findClosest($target)
{
$targetDetails = $this->examineResolution($target);
$deltas = array();
foreach ($this->resolutions as $resolution => $details) {
if ($details['long'] < $targetDetails['long'] || $details['short'] < $targetDetails['short']) continue;
$deltas[$resolution] = array(
'resolution' => $resolution,
'delta' => abs($details['ratio'] - $targetDetails['ratio']),
);
}
$resolutions = $this->resolutions;
uasort($deltas, function ($a, $b) use ($resolutions) {
$deltaA = $a['delta'];
$deltaB = $b['delta'];
if ($deltaA === $deltaB) {
$pixelsA = $resolutions[$a['resolution']]['pixels'];
$pixelsB = $resolutions[$b['resolution']]['pixels'];
if ($pixelsA === $pixelsB) {
return 0;
}
return $pixelsA > $pixelsB ? 1 : -1;
}
return $deltaA > $deltaB ? 1 : -1;
});
$resolutions = array_keys($deltas);
return array_pop($resolutions);
}
private function examineResolution($resolution)
{
list($width, $height) = explode('x', $resolution);
$long = ($width > $height) ? $width : $height;
$short = ($width < $height) ? $width : $height;
$ratio = $long / $short;
$pixels = $long * $short;
return array(
'resolutions' => $resolution,
'pixels' => $pixels,
'long' => $long,
'short' => $short,
'ratio' => $ratio,
);
}
}
Usage:
$resolutions = array(
'480x640',
'480x800',
'640x480',
'640x960',
'800x1280',
'2048x1536'
);
$target = $_GET['target'];
$matcher = new ResolutionMatcher($resolutions);
$closest = $matcher->findClosest($target);
First of all, I would store the haystack using width first, height second:
$resolutions = array(
array('w' => 640, 'h' => 480),
array('w' => 800, 'h' => 480),
array('w' => 960, 'h' => 640),
array('w' => 1280, 'h' => 800),
array('w' => 2048, 'h' => 1536),
);
Then, calculate dimension differences between needle and each item, followed by the area size:
array_walk($resolutions, function(&$item) use ($needle) {
$item['aspect'] = abs($item['w'] - $needle['w']) / abs($item['h'] - $needle['h']);
$item['area'] = $item['w'] * item['h'];
});
usort($resolutions, function($a, $b) {
if ($a['aspect'] != $b['aspect']) {
return ($a['aspect'] < $b['aspect']) ? -1 : 1;
}
return 0;
});
Then you filter the list based on which resolutions are bigger; the first match is the one closest to the needle aspect ratio:
$needle_area = $needle['w'] * $needle['h'];
foreach ($resolutions as $item) {
if ($needle_area < $item['area']) {
return $item;
}
}
return null;
if you are just looking for the nearest ratio, try this:
echo getClosestRatio(1.20); //gets 1.19
echo getClosestRatio(1.09); //gets 1
echo getClosestRatio(1.30); //gets 1.3333333
function getClosestRatio($fx){
$ratio = array(
1,
1.19,
1.25,
(4/3),
1.3375,
1.43,
1.5,
1.56,
1.6180,
5/3,
16/9,
1.85,
1.9,
2/1
);
$min=[];
foreach ($ratio as $screen){
# if($fx==$screen){return $screen;}
$diff=abs($screen-$fx);
$min["".$diff]=$screen;
}
ksort($min);
return array_values($min)[0];
}

Reading Geotag data from image in php

Does anyone know if there is a way to read geotag data from photos in PHP?
Thanks
Like everyone else has said, exif_read_data(); will do it.
To go ahead and get all of the data, use these args:
exif_read_data($img, 0, true); // where $img is the path to your image
This function can only read headers from tiffs and jpegs and I'm pretty sure only jpegs may contain geotags. I've written a simple php script for use in the command line and posted it as a gist on github.
Run the script like this: php exif.php
It will echo out an array. Look for the coordinates here:
[GPS] => Array
[GPSLatitudeRef] => N
[GPSLatitude] => Array
[0] => 30/1
[1] => 1589/100
[2] => 0/1
[GPSLongitudeRef] => W
[GPSLongitude] => Array
[0] => 87/1
[1] => 3609/100
[2] => 0/1
[GPSAltitudeRef] =>
[GPSAltitude] => 18289/454
[GPSTimeStamp] => Array
[0] => 20/1
[1] => 22/1
[2] => 2065/1
[GPSImgDirectionRef] => T
[GPSImgDirection] => 34765/689
The Latitude and Longitude arrays contain three values: 0 is for degrees, 1 is for minutes and 2 is for seconds. If you see something like "1589/100" this is equal to 15.89. So for the GPSLongitude array, 3609/100 is equal to 36.09.
Convert the coordinates from degrees-minutes-second form to decimal form here http://www.satsig.net/degrees-minutes-seconds-calculator.htm
If the latitude is South, dont forget to make it negative. If the longitude is west, make that negative. The coodinates from the above data are: 30.26483, -87.6015
Will Coughlin's answer is correct though I formulated a function for quick reference in case someone stumbles upon the same problem.
/**
* Returns an array of latitude and longitude from the Image file
* #param image $file
* #return multitype:number |boolean
*/
function read_gps_location($file){
if (is_file($file)) {
$info = exif_read_data($file);
if (isset($info['GPSLatitude']) && isset($info['GPSLongitude']) &&
isset($info['GPSLatitudeRef']) && isset($info['GPSLongitudeRef']) &&
in_array($info['GPSLatitudeRef'], array('E','W','N','S')) && in_array($info['GPSLongitudeRef'], array('E','W','N','S'))) {
$GPSLatitudeRef = strtolower(trim($info['GPSLatitudeRef']));
$GPSLongitudeRef = strtolower(trim($info['GPSLongitudeRef']));
$lat_degrees_a = explode('/',$info['GPSLatitude'][0]);
$lat_minutes_a = explode('/',$info['GPSLatitude'][1]);
$lat_seconds_a = explode('/',$info['GPSLatitude'][2]);
$lng_degrees_a = explode('/',$info['GPSLongitude'][0]);
$lng_minutes_a = explode('/',$info['GPSLongitude'][1]);
$lng_seconds_a = explode('/',$info['GPSLongitude'][2]);
$lat_degrees = $lat_degrees_a[0] / $lat_degrees_a[1];
$lat_minutes = $lat_minutes_a[0] / $lat_minutes_a[1];
$lat_seconds = $lat_seconds_a[0] / $lat_seconds_a[1];
$lng_degrees = $lng_degrees_a[0] / $lng_degrees_a[1];
$lng_minutes = $lng_minutes_a[0] / $lng_minutes_a[1];
$lng_seconds = $lng_seconds_a[0] / $lng_seconds_a[1];
$lat = (float) $lat_degrees+((($lat_minutes*60)+($lat_seconds))/3600);
$lng = (float) $lng_degrees+((($lng_minutes*60)+($lng_seconds))/3600);
//If the latitude is South, make it negative.
//If the longitude is west, make it negative
$GPSLatitudeRef == 's' ? $lat *= -1 : '';
$GPSLongitudeRef == 'w' ? $lng *= -1 : '';
return array(
'lat' => $lat,
'lng' => $lng
);
}
}
return false;
}
Hope it helps someone.
Call this function with filename. I testet it and it works prefectly.
Call example:
$fileName='xxxxxx'; //or $fileName='xxxxxxxxx';
echo $returned_data = triphoto_getGPS($fileName);
Function:
function triphoto_getGPS($fileName)
{
//get the EXIF all metadata from Images
$exif = exif_read_data($fileName);
if(isset($exif["GPSLatitudeRef"])) {
$LatM = 1; $LongM = 1;
if($exif["GPSLatitudeRef"] == 'S') {
$LatM = -1;
}
if($exif["GPSLongitudeRef"] == 'W') {
$LongM = -1;
}
//get the GPS data
$gps['LatDegree']=$exif["GPSLatitude"][0];
$gps['LatMinute']=$exif["GPSLatitude"][1];
$gps['LatgSeconds']=$exif["GPSLatitude"][2];
$gps['LongDegree']=$exif["GPSLongitude"][0];
$gps['LongMinute']=$exif["GPSLongitude"][1];
$gps['LongSeconds']=$exif["GPSLongitude"][2];
//convert strings to numbers
foreach($gps as $key => $value){
$pos = strpos($value, '/');
if($pos !== false){
$temp = explode('/',$value);
$gps[$key] = $temp[0] / $temp[1];
}
}
//calculate the decimal degree
$result['latitude'] = $LatM * ($gps['LatDegree'] + ($gps['LatMinute'] / 60) + ($gps['LatgSeconds'] / 3600));
$result['longitude'] = $LongM * ($gps['LongDegree'] + ($gps['LongMinute'] / 60) + ($gps['LongSeconds'] / 3600));
$result['datetime'] = $exif["DateTime"];
return $result;
}
}
You can use the EXIF functions of PHP:
exif_read_data($file);
You can use the exif_read_data() function if the geotag data is embedded in the EXIF data.
Install Intervention\Image by following command.
Reference: http://image.intervention.io/getting_started/installation
composer require intervention/image
Update config/app.php
'providers' => [
Intervention\Image\ImageServiceProvider::class
],
'aliases' => [
'Image' => Intervention\Image\Facades\Image::class
]
Use Library:
$data = Image::make(public_path('IMG.jpg'))->exif();
if(isset($data['GPSLatitude'])) {
$lat = eval('return ' . $data['GPSLatitude'][0] . ';')
+ (eval('return ' . $data['GPSLatitude'][1] . ';') / 60)
+ (eval('return ' . $data['GPSLatitude'][2] . ';') / 3600);
$lng = eval('return ' . $data['GPSLongitude'][0] . ';')
+ (eval('return ' . $data['GPSLongitude'][1] . ';') / 60)
+ (eval('return ' . $data['GPSLongitude'][2] . ';') / 3600);
echo "$lat, $lng";
} else {
echo "No GPS Info";
}

Categories