I am computing a simple average of latitude/longitude pairs to get the centerpoint (as projected on a flat plane) and I am struggling to develop an algorithm to correctly account for the 180th Meridian. I am developing this in PHP.
I'm computing a simple average, adding up all of the longitudes and dividing by the count. However, in a situation where there is, for example, a -175 and 175 point, it returns an average of 0, whereas I need a value of 180.
Once approach I tried was to add 360 to all negative values, take the average, and then subtract 360. This fixes the problem over the 180th Meridian, but it creates the same issue over the Prime Meridian (0 Meridian).
$neg_lng_flag = false;
//calculate centerpoint
$i = 0;
while($i < $vertice_count) {
$lat_sum += $vertice_obj[$i]->lat;
//if lng is negative, add 360
if($vertice_obj[$i]->lng < 0) {
$lng_sum += $vertice_obj[$i]->lng + 360;
$neg_lng_flag = true;
} else {
$lng_sum += $vertice_obj[$i]->lng;
}
$i++;
}
$avg_lat = round($lat_sum / $vertice_count,2);
$avg_lng = round($lng_sum / $vertice_count,2);
if($neg_lng_flag) {
$avg_lng = $avg_lng - 360;
if($avg_lng < -180) {
$avg_lng = $avg_lng + 180;
}
}
I am not sure what I can do to come up with a consistent algorithm to return a true center longitudal point for all scenarios. I have not been able to find a robust solution either here or through Google more generally. I would appreciate any outside consideration/ideas. Thank you.
I was able to come up with a robust solution for my purposes. In my case, I just needed to calculate the minimum and maximum Longitude coordinates, and if the difference exceeded 180, I knew to shift the negative values by +360, then calculate the average. If the average was greater than 180, I needed to subtract 360, and if it was less than -180 I needed to add 360.
//COMPUTE LONGITUDAL MIDPOINT
function getLngMidpoint($vertice_obj) {
//get min and max vals
$i = 0;
while($i < count($vertice_obj)) {
$min = $vertice_obj[$i]->lng;
$max = $vertice_obj[$i]->lng;
if($vertice_obj[i]->lng > $max) {
$max = $vertice_obj[$i]->lng;
}
if($vertice_obj[$i]->lng < $min) {
$min = $vertice_obj[$i]->lng;
}
$i += 1;
}
$shift = 0;
//check if distance between min and max > 180. If so, need to shift
if(($max - $min) > 180) {
//shift all lng by 180
$shift = 360;
}
$i = 0;
$sum_lng = 0;
while($i < count($vertice_obj)) {
if($vertice_obj[$i] < 0) {
$sum_lng += $vertice_obj[$i]->lng + $shift;
} else {
$sum_lng += $vertice_obj[$i]->lng;
}
$i += 1;
}
$avg_lng = $sum_lng / count($vertice_obj);
if($avg_lng > 180) {
$avg_lng = $avg_lng - 360;
}
if($avg_lng < -180) {
$avg_lng = $avg_lng + 360;
}
return $avg_lng;
}
Related
I need to validate that an inputted number is a valid number based on my stepping rules and round up to the nearest valid number if not. These numbers will change but one example would be:
$min = 0.25;
$step = 0.1
$qty = 0.75 // user input
so these would be valid inputs:
0.75
0.85
0.95
But these should round:
0.76 (to 0.85)
0.80 (to 0.85)
I thought I could use modulus somehow but not getting the calculation correct.
if (($qty % min) / $step == 0)) {
echo "good";
}
I've tried some variations of math that are likely very wrong
$step = 0.1;
$min = 0.25;
$qty = .85;
$h = ($qty / $min) / $step;
echo $h;
$j = mround($qty, $min-$step);
echo $j;
function mround($num, $parts) {
if ($parts <= 0) { $parts = 1; }
$res = $num * (1/$parts);
$res = round($res);
return $res /(1/$parts);
}
I think you can use fmod to do this.
$new = $original + ($step - fmod($original - $minimum, $step));
Example on 3v4l.org
Need to make custom function to check amount with available denominations.
code i make :
$amount = 100;
$notes_aval = array(20,50,100,500,2000);//available currency notes
$is_allowed = 0; //not allowed
foreach($notes_aval as $note){
if (fmod($amount,$note) == 0) {
$is_allowed = 1;//allowed
}
}
echo $is_allowed;
But this is not working out for all cases.
For exam : i have denominations = array (20,50);
with amount 90 is not allowed, but it should be allowed by 20*2 + 50*1 = 90
in the example of denominations = array (20,50) ,if amount 1110 should be acceptable with 1110 = 20*53 + 50*1
Try both modular divisions
function validateCurrency($amount)
{
$requestdAmount = $amount;
$valueUnder = 0;
$notes = array(20, 50,100,500,2000);
$is_allowed = 0;
if(in_array($amount, $notes)){
return $is_allowed = 1;
}
$numOccurance = ceil($amount/$notes[0]);
$arraySums = [];
foreach ($notes as $key => $value) {
for ($i=1; $i <= $numOccurance; $i++) {
if($value * $i == $amount) {
return $is_allowed = 1;
}
$arraySums[$key][] = $value * $i;
}
}
for ($i=0; $i < count($arraySums); $i++) {
for ($j=$i+1; $j < count($arraySums); $j++) {
foreach ($arraySums[$i] as $key => $value) {
foreach ($arraySums[$j] as $key2 => $toBeMul) {
if($value+$toBeMul == $amount) {
return $is_allowed = 1;
}
}
}
}
}
return $is_allowed;
}
// Driver Code
$amount = 40;
$is_allowed = validateCurrency($amount);
echo $is_allowed;
die();
It will work
You need to start exchange from largest value until your amount is smaller than largest note (eg 2000). Then you go this same with lower note (eg 500), and again with lower. When amount is smaller than lowest value (eg. 20) then you cannot exchange this amount.
So:
We start with 2270
We check for largest note - it's 2000.
Now we know we have 2000 and 270 (2270 - 2000) rest
Now we check again for largest value - it's 200
So we have 2000, 200 and 70 (270 - 200) rest
Now largest not possible is 50
So we have 2000, 200, 50 and 20 (70 - 50) rest
Now largest is 20 and we have 2000, 200, 50, 20 and rest is 0
As rest is smaller than lowest note then we can stop checking.
If rest is 0 we know we can exchange, if rest is larger than 0 then we cannot. Additionally we also have list of notes we can use for exchange (2000, 200, 50, 20).
function checkDenomination($amount){
$notes = array(2000,500,100,50,20); //it's easier if they are reversed
$smallestNote = 20;
$result = [];
while($amount >= $smallestNote) { //we will repeat until we can exchange
foreach($notes as $note) {
if ($amount >= $note) { //we check for largest value we can exchange
$result[] = $note;
$amount -= $note; //as we have hit, we can deduct it from amount;
break;
}
}
}
return ($amount > 0) ? false : $result; //return false if we cannot exchange this amount or array with notes we can exchange for full amount
}
var_dump(checkDenomination(100));
var_dump(checkDenomination(23424));
var_dump(checkDenomination(25000));
var_dump(checkDenomination(222));
I'm using MKMapView and I send my php program the visible region (center lat, center lon, span lat, span lon). I need to determine if a coordinate is inside that region using php. I'm hoping there's a standard formula somewhere, but I haven't found one. I'll keep trying to come up with a formula, but it's surprisingly complicated (hopefully not as much as the haversine, which I don't believe I could have figured out myself).
lets try this logic
$topRightLongitude = $centerLongitude + $spanLongitude/2;
if($topRightLongitude > 180 and ($pointLongitude < 0))
$topRightLongitude = $topRightLongitude - 360; // (180*2) - positive becomes negative
$bottomLeftLongitude = $centerLongitude - $spanLongitude/2;
if($bottomLeftLongitude< -180 and ($pointLongitude > 0))
$bottomLeftLongitude= 360 + $bottomLeftLongitude; // now is negative and will become positive
$topRightLatitude = $centerLatitude + $spanLatitude/2;
if($topRightLatitude > 90 and ($pointLatitude < 0))
$topRightLatitude = $topRightLatitude - 180; // (90*2) - positive becomes negative
$bottomLeftLatitude = $centerLatitude - $spanLatitude/2;
if($bottomLeftLatitude< -90 and ($pointLatitude > 0))
$bottomLeftLatitude= 180 + $bottomLeftLongitude; // now is negative and will become positive
if you have
$centerLongitude = 179;
$spanLongitude = 20;
$pointLongitude = -179;
results
$topRightLongitude = -171;
$bottomLeftLongitude = 169;
so your point is in if you test like this:
if($pointLongitude < $topRightLongitude &&
$pointLongitude > $bottomLeftLongitude &&
$pointLatitude < $topRightLatitude &&
$pointLatitude > $bottomLeftLatitude){
echo 'in';
}else{
echo 'out';
}
My Solution
$top = $c_lat + ($d_lat / 2.0);
$bottom = $c_lat - ($d_lat / 2.0);
$left = $c_lon - ($d_lon / 2.0);
$right = $c_lon + ($d_lon / 2.0);
if($left < -180)
{
$second_left = $left + 360.0;
$second_right = 180.0;
$left = -180;
}
elseif($right > 180)
{
$second_right = $right - 360.0;
$second_left = -180.0;
$right = 180.0;
}
$inside = false;
if($t_lat > $bottom && $t_lat < $top && $t_lon > $left && $t_lon < $right)
$inside = true;
else if($second_right && $second_left)
{
if($t_lat > $bottom && $t_lat < $top && $t_lon > $second_left && $t_lon < $second_right)
$inside = true;
}
if($inside)
{
}
This seems to work with MKMapView since the region latitudes are always between -90 and 90.
This logic should work:
if ( ($X > $center_lat - $span_lat/2) &&
($X < $center_lat + $span_lat/2) &&
($Y > $center_lon - $span_lon/2) &&
($Y < $center_lon + $span_lon/2) ) {
echo "It's inside!";
} else {
echo "It's outside ...";
}
I had worked a solution for my own problem before, but for decimal values of coordinates and it works. May be if you can convert deg to decimal it might work.
I have renamed the variable according to your problem.
Here's the logic.
if
(
(
($lat - $spanLat) < $centerLat &&
$centerLat < ($lat+ $spanLat)
) &&
(
($long - $spanLong) < $centerLong &&
$centerLong < ($long + $spanLong)
)
)
I put together a function to create "nice numbers" for displaying labels. The function makes use of log10 to normalize odd numbers. Unfortunately, log10 doesn't deal with numbers < 0 but returns NAN.
Now, I have an "ugly" number like -2.36, how to get the nearest nice number, like -2.3or -2.0?
If I convert it to 2.36 in order to put it thru the function and multiply it by -1 afterwards, I'd get -2.4 ---> no go, because it is required that nice-number > ugly-number.
Any ideas?
Links to define nice-numbers etc:
http://wiki.tcl.tk/16640
Algorithm for "nice" grid line intervals on a graph
round to nearest nice number
Here's my code:
function niceNum($range, $round) {
// $exponent: exponent of range
// $fraction: fractional part of range
// $niceFraction: nice, rounded fraction
if ($range==0) return 0;
$exponent = floor(log10($range));
$fraction = $range / pow(10, $exponent);
if ($round) {
if ($fraction < 1.5)
$niceFraction = 1;
elseif ($fraction < 3)
$niceFraction = 2;
elseif ($fraction < 7)
$niceFraction = 5;
else
$niceFraction = 10;
} else {
if ($fraction <= 1)
$niceFraction = 1;
elseif ($fraction <= 2)
$niceFraction = 2;
elseif ($fraction <= 5)
$niceFraction = 5;
else
$niceFraction = 10;
}
return $niceFraction * pow(10, $exponent);
}
Holt-Winters is introduced here:
http://en.wikipedia.org/wiki/Holt-Winters
The Seasonal Dampened version of it is discussed here (scroll down the page):
http://otexts.com/fpp/7/5/
In a nutshell, it basically looks at 3 things:
long-term trend
short-term trend
seasonal trend
It also doesn't average those together, because really what you need is weighted averaging, where seasonal and short-term are more significant than long-term trend, naturally, with financial data trends.
Given $anYear1 and $anYear2, how do I apply the Holt-Winters Seasonal Dampened Method to forecast 2 more months past the end of $anYear2? Assume $anYear1 is an array of 12 numbers. Assume $anYear2 is an array of a range of 0 to 12 numbers.
So, I can fill it with random data like so:
<?php
$anYear1 = array();
$anYear2 = array();
$nStop = 10; // so we need 11 and 12 of the year
for ($i = 1; $i <= 12; $i++) {
$anYear1[$i] = rand(200,500);
if ($i <= $nStop) {
// give it a natural lift like real financial data
$anYear2[$i] = rand(400,700);
}
}
$nSeasonRange = 4; // 4 months in a business quarter
Therefore, I want to create a function like so:
function forecastHoltWinters($anYear1, $anYear2, $nSeasonRange = 4) {
///////////////////
// DO MAGIC HERE //
///////////////////
// an array with 2 numbers, indicating 2 months forward from end of $anYear2
return $anForecast;
}
$anForecast = forecastHoltWinters($anYear1, $anYear2, $nSeasonRange);
echo "YEAR 1\n";
print_r($anYear1);
echo "\n\nYEAR 2\n"
print_r($anYear2);
echo "\n\nTWO MONTHS FORECAST\n";
print_r($anForecast);
Note: I have found a Github example here, but it doesn't show how to do a projection. It is also discussed here.
I found a way to adapt Ian Barber's function to do what I needed.
<?php
error_reporting(E_ALL);
ini_set('display_errors','On');
$anYear1 = array();
$anYear2 = array();
$nStop = 10;
for($i = 1; $i <= 12; $i++) {
$anYear1[$i] = rand(100,400);
if ($i <= $nStop) {
$anYear2[$i+12] = rand(200,600);
}
}
print_r($anYear1);
print_r($anYear2);
$anData = array_merge($anYear1,$anYear2);
print_r(forecastHoltWinters($anData));
function forecastHoltWinters($anData, $nForecast = 2, $nSeasonLength = 4, $nAlpha = 0.2, $nBeta = 0.01, $nGamma = 0.01, $nDevGamma = 0.1) {
// Calculate an initial trend level
$nTrend1 = 0;
for($i = 0; $i < $nSeasonLength; $i++) {
$nTrend1 += $anData[$i];
}
$nTrend1 /= $nSeasonLength;
$nTrend2 = 0;
for($i = $nSeasonLength; $i < 2*$nSeasonLength; $i++) {
$nTrend2 += $anData[$i];
}
$nTrend2 /= $nSeasonLength;
$nInitialTrend = ($nTrend2 - $nTrend1) / $nSeasonLength;
// Take the first value as the initial level
$nInitialLevel = $anData[0];
// Build index
$anIndex = array();
foreach($anData as $nKey => $nVal) {
$anIndex[$nKey] = $nVal / ($nInitialLevel + ($nKey + 1) * $nInitialTrend);
}
// Build season buffer
$anSeason = array_fill(0, count($anData), 0);
for($i = 0; $i < $nSeasonLength; $i++) {
$anSeason[$i] = ($anIndex[$i] + $anIndex[$i+$nSeasonLength]) / 2;
}
// Normalise season
$nSeasonFactor = $nSeasonLength / array_sum($anSeason);
foreach($anSeason as $nKey => $nVal) {
$anSeason[$nKey] *= $nSeasonFactor;
}
$anHoltWinters = array();
$anDeviations = array();
$nAlphaLevel = $nInitialLevel;
$nBetaTrend = $nInitialTrend;
foreach($anData as $nKey => $nVal) {
$nTempLevel = $nAlphaLevel;
$nTempTrend = $nBetaTrend;
$nAlphaLevel = $nAlpha * $nVal / $anSeason[$nKey] + (1.0 - $nAlpha) * ($nTempLevel + $nTempTrend);
$nBetaTrend = $nBeta * ($nAlphaLevel - $nTempLevel) + ( 1.0 - $nBeta ) * $nTempTrend;
$anSeason[$nKey + $nSeasonLength] = $nGamma * $nVal / $nAlphaLevel + (1.0 - $nGamma) * $anSeason[$nKey];
$anHoltWinters[$nKey] = ($nAlphaLevel + $nBetaTrend * ($nKey + 1)) * $anSeason[$nKey];
$anDeviations[$nKey] = $nDevGamma * abs($nVal - $anHoltWinters[$nKey]) + (1-$nDevGamma)
* (isset($anDeviations[$nKey - $nSeasonLength]) ? $anDeviations[$nKey - $nSeasonLength] : 0);
}
$anForecast = array();
$nLast = end($anData);
for($i = 1; $i <= $nForecast; $i++) {
$nComputed = round($nAlphaLevel + $nBetaTrend * $anSeason[$nKey + $i]);
if ($nComputed < 0) { // wildly off due to outliers
$nComputed = $nLast;
}
$anForecast[] = $nComputed;
}
return $anForecast;
}