calculate tile size of google map at zoom level n - php

Hey. I have a maps application that uses google maps. I get the bounds off the map, and then I do some clustering markers on that grounds, but to be able to have the clusters stay at the same place, I'd like to know how to make the boundaries that I pass snap into the tilegrid that google uses. The quadtree algorithm they use for their map essentially what I'm asking for is
how do I get the bounds for the tiles that the viewport is in. I've tried to illustrate it :)
If I do the cluster calc on the tile bounds and not the viewport, when I split it up in a grid, the clusters will stay in the same place, because the grid will be absolute at each zoom level.
Also this allows for better query caching, as the queries will be a lot similar when the bounds have to "snap in" to the tiles, AND the user will be able to pan with markers in proximity being displayed already.
UPDATE
I'm starting over...
I've got this
function TileMyBounds($sx, $sy, $nx, $ny, $zoom)
{
function TileMyBounds($sx, $sy, $nx, $ny, $zoom)
{
list($nmx,$nmy) = $this->LatLonToMeters($ny/1000000, $nx/1000000);
list($ntx, $nty) = $this->MetersToTile($nmx, $nmy, $zoom);
$nbounds = $this->TileLatLonBounds($ntx, $nty, $zoom);
list($smx,$smy) = $this->LatLonToMeters($sy/1000000, $sx/1000000);
list($stx, $sty) = $this->MetersToTile($smx, $smy, $zoom);
$sbounds = $this->TileLatLonBounds($stx, $sty, $zoom);
$step = ($sbounds[3]-$sbounds[1])*1000000;
return array($sbounds[0]*1000000, $sbounds[1]*1000000, $nbounds[2]*1000000, $nbounds[3]*1000000, $step);
}
and the function where I use it looks like this:
function clusterGrid($zoom,$nelt,$nelg,$swlt,$swlg)
{
$singlemarkers = array();
$clusters = array();
list($swlg, $swlt, $nelg, $nelt, $step) = $this->TileMyBounds($swlg, $swlt, $nelg, $nelt, $zoom);
$calcbounds = $this->TileMyBounds($swlg, $swlt, $nelg, $nelt, $zoom);
$queryconcat = "";
$length_lng = ceil(($nelg-$swlg)/$step);
$length_lat = ceil(($nelt-$swlt)/$step);
$orgnelg = $nelg;
$orgswlt = $swlt;
for($i=0;$i < $length_lng + 1; $i++) {
$nelg -= $step;
$temp_swlt = $swlt;
for($j=0; $j < $length_lat + 1; $j++) {
$temp_swlt += $step;
if($nelg > $orgnelg) continue;
if($temp_swlt > $nelt) continue;
if($nelg < $swlg) continue;
if($temp_swlt < $orgswlt) continue;
$q = $this->db->select('
COUNT(*) AS CO,
(MAX(lat)+MIN(lat))/2 AS lat,
(MAX(lng)+MIN(lng))/2 AS lng')
->where('`lat` BETWEEN '.$temp_swlt.' AND '.($temp_swlt+$step).' AND
`lng` BETWEEN '.($nelg-$step).' AND '.$nelg)
->get('markers');
$queryconcat += $this->db->last_query();
$result = $q->row_array();
if($result['CO'] == 0) {
continue;
}
$clusters[] = array('lat' => ($result['lat']), 'lng' => ($result['lng']), 'size' => $result['CO']);
}
}
return array('singlemarkers' => '', 'clustermarkers' => $clusters, 'bounds' => $calcbounds, 'lengths' => array($length_lng, $length_lat));
}
UPDATE!!!! 12/03/2011 - Almost there
The tiles are somewhat precise, yet not entirely, so when panning around, the clusters can "move around" a little. Due to the fact that the $step = ($sbounds[3]-$sbounds[1])*1000000; calculation is not always the same at each zoom level, as I would've expected because I would think that a tile would have the same width and length in lat and lon as any other tile at the same zoom level.

But something's not quite right. Can anyone tell why I'm not getting the viewport coordinates when I pass in swlg,swlat,nelg,nelat and the zoom level
You want to solve the space-filling-curve equation first with all 4 bounds coordinates.
list($lng, $lat) = array ($row['lng'], $row['lat']);
list($mx, $my) = $mercator->LatLonToMeters($lat, $lng);
list($tx, $ty) = $mercator->MetersToTile($mx, $my, MAXZOOM);
list($tx, $ty) = array ($tx, ((1 << MAXZOOM) - 1) - $ty );
list($minx, $miny) = $this->PixelsToMeters( $tx*$this->tileSize, $ty*$this->tileSize, $zoom );
list($maxx, $maxy) = $this->PixelsToMeters( ($tx+1)*$this->tileSize, ($ty+1)*$this->tileSize, $zoom );
return array($minx, $miny, $maxx, $maxy);
or
list($lng, $lat) = array ($row['lng'], $row['lat']);
list($mx, $my) = $mercator->LatLonToMeters($lat, $lng);
list($tx, $ty) = $mercator->MetersToTile($mx, $my, MAXZOOM);
list($tx, $ty) = array ($tx, ((1 << MAXZOOM) - 1) - $ty );
$bounds = $this->TileBounds($tx, $ty, $zoom);
list($minLat, $minLon) = $this->MetersToLatLon($bounds[0], $bounds[1]);
list($maxLat, $maxLon) = $this->MetersToLatLon($bounds[2], $bounds[3]);
return array($minLat, $minLon, $maxLat, $maxLon);
EDIT: Problem is solved. OP answered in private Mail:

Related

polygon matrix calculation for viscosity matrix

I am writing a program which needs different value of gas viscosity at different temperature for respective gases. Data which i have is given below. I am new in programming in php . can anyone please give me the logic behind it. so that i can get viscosity at any required tempertaure through the program. thanks
Data for viscosity calculation (dyn. visc. [Ns/m² = kg/ms]; Source: VDI Wärmeatlas
Temp. [°C] 0 100 200 300 400 500
CO2 1,37E-05 1,82E-05 2,22E-05 2,59E-05 2,93E-05 3,24E-05
O2 1,92E-05 2,43E-05 2,88E-05 3,29E-05 3,67E-05 4,03E-05
H2O 9,00E-06 1,25E-05 1,61E-05 1,97E-05 2,33E-05 2,69E-05
N2 1,66E-05 2,09E-05 2,47E-05 2,82E-05 3,14E-05 3,42E-05
You can easily fit your data in Excel. Here's a plot that shows the result, along with the 2nd order polynomial:
Finally I got the right answer. In case if anyone need it, I am posting it out here:
class baseViscosityCalc {
public $arViscosity = array(
array(0, 1.37e-05, 1.92e-05, 9.00e-06, 1.66e-05),
array(100, 1.82e-05, 2.43e-05, 1.25e-05, 2.09e-05),
array(200, 2.22e-05, 2.88e-05, 1.61e-05, 2.47e-05),
array(300, 2.59e-05, 3.29e-05, 1.97e-05, 2.82e-05),
array(400, 2.93e-05, 3.67e-05, 2.33e-05, 3.14e-05),
array(500, 3.24e-05, 4.03e-05, 2.69e-05, 3.42e-05)
);
public function getViscosityData($DCat) {
$arValue = array(
'Temperature' => 0.0,
'CO2' => 0.0,
'O2' => 0.0,
'H2O' => 0.0,
'N2' => 0.0
);
for ($i = 0; $i < count($this->arViscosity); $i++) {
$arValue['Temperature'] = $DCat;
if ($DCat < $this->arViscosity[0][0]) {
$arValue['CO2'] = $this->arViscosity[0][1];
$arValue['O2'] = $this->arViscosity[0][2];
$arValue['H2O'] = $this->arViscosity[0][3];
$arValue['N2'] = $this->arViscosity[0][4];
break;
}
if ($DCat > $this->arViscosity[count($this->arViscosity) - 1][0]) {
$arValue['CO2'] = $this->arViscosity[count($this->arViscosity) - 1][1];
$arValue['O2'] = $this->arViscosity[count($this->arViscosity) - 1][2];
$arValue['H2O'] = $this->arViscosity[count($this->arViscosity) - 1][3];
$arValue['N2'] = $this->arViscosity[count($this->arViscosity) - 1][4];
breaK;
}
if ($DCat > $this->arViscosity[$i][0] && $DCat < $this->arViscosity[$i + 1][0]) {
$factor = ($DCat - $this->arViscosity[$i][0]) / ($this->arViscosity[$i + 1][0] - $this->arViscosity[$i][0]);
$arValue['CO2'] = $factor * ($this->arViscosity[$i + 1][1] - $this->arViscosity[$i][1]) + $this->arViscosity[$i][1];
$arValue['O2'] = $factor * ($this->arViscosity[$i + 1][2] - $this->arViscosity[$i][2]) + $this->arViscosity[$i][2];
$arValue['H2O'] = $factor * ($this->arViscosity[$i + 1][3] - $this->arViscosity[$i][3]) + $this->arViscosity[$i][3];
$arValue['N2'] = $factor * ($this->arViscosity[$i + 1][4] - $this->arViscosity[$i][4]) + $this->arViscosity[$i][4];
break;
}
}
return $arValue;
}
}

Weighted Load Balancing Algorithm into PHP Application

I want to resolve weighted an Adapter from an factory which could be configured by user (enable/disable and weight %).
Example:
AdapterW ≃ 20% of transaction
AdapterX ≃ 30% of transaction
AdapterY ≃ 40% of transaction
AdapterZ ≃ 10% of transaction
I can grant that all items will never sum more than one hundred (100%), but sometimes any adapter could be deactivated.
I have the following parameters:
public function handleAdapter()
{
$isWActive = (boolean)$this->_config[self::W];
$isXActive = (boolean)$this->_config[self::X];
$isYActive = (boolean)$this->_config[self::Y];
$isZActive = (boolean)$this->_config[self::Z];
$WPercentage = (int)$this->_config[self::LOAD_BALANCE_W];
$XPercentage = (int)$this->_config[self::LOAD_BALANCE_X];
$YPercentage = (int)$this->_config[self::LOAD_BALANCE_Y];
$ZPercentage = (int)$this->_config[self::LOAD_BALANCE_Z];
.
.
.
return (self::W | self::X | self::Y | self::Z);
}
How can i balance weighted between this adapters dynamically?
Edit
created a gist to a executable code: https://gist.github.com/markomafs/5d892d06d6670909f9b4
This may not be the best approach, but you can try something like this:
public function handleAdapter()
{
//an array to return the balanced entries
$balancedEntries[] = false;
//verifies which of the options are active
$isWActive = (boolean)$this->_config[self::W];
$isXActive = (boolean)$this->_config[self::X];
$isYActive = (boolean)$this->_config[self::Y];
$isZActive = (boolean)$this->_config[self::Z];
//get configured percentage of each
$WPercentage = (int)$this->_config[self::LOAD_BALANCE_W];
$XPercentage = (int)$this->_config[self::LOAD_BALANCE_X];
$YPercentage = (int)$this->_config[self::LOAD_BALANCE_Y];
$ZPercentage = (int)$this->_config[self::LOAD_BALANCE_Z];
//here you fill the array according to the proportion defined by the percentages
if ($isWActive) {
for ($i = 0; $i < $WPercentage; $i++) {
$balancedEntries[] = self::W;
}
}
if ($isXActive) {
for ($i = 0; $i < $XPercentage; $i++) {
$balancedEntries[] = self::X;
}
}
if ($isYActive) {
for ($i = 0; $i < $YPercentage; $i++) {
$balancedEntries[] = self::Y;
}
}
if ($isZActive) {
for ($i = 0; $i < $ZPercentage; $i++) {
$balancedEntries[] = self::Z;
}
}
return $balancedEntries;
}
And then, in case you want a proportion of 1 to 100 (as in percentages):
$balancedResult = $balancedEntries[array_rand($balancedEntries, 1)];
Since array_rand will return 1 key from the original array, you use it to get it's value.
Another try, this should work for your case - But it only work if you have an adapter as a single char string, this is not visible by your question.
public function handleAdapter()
{
# a map with all adapters
$map = array(
self::W => self::LOAD_BALANCE_W,
self::X => self::LOAD_BALANCE_X,
self::Y => self::LOAD_BALANCE_Y,
self::Z => self::LOAD_BALANCE_Z
);
# generate a string map with one char per percentage point
$stringMap = "";
foreach($map as $key => $value){
# skip if disabled
if(!$this->_config[$key]) continue;
# repeat the key for each percentage point
$stringMap .= str_repeat($key, (int)$this->_config[$value]);
}
# return a random string char from the map
return $stringMap[rand(0, strlen($stringMap) - 1)];
}
Edit: I've misunderstood the question, the answer is wrong.
I understand your question so that you always want to return the adapter with the lowest load to force traffic to this adapter.
public function handleAdapter()
{
$isWActive = (boolean)$this->_config[self::W];
$isXActive = (boolean)$this->_config[self::X];
$isYActive = (boolean)$this->_config[self::Y];
$isZActive = (boolean)$this->_config[self::Z];
$WPercentage = (int)$this->_config[self::LOAD_BALANCE_W];
$XPercentage = (int)$this->_config[self::LOAD_BALANCE_X];
$YPercentage = (int)$this->_config[self::LOAD_BALANCE_Y];
$ZPercentage = (int)$this->_config[self::LOAD_BALANCE_Z];
$map = array();
if($isWActive) $map[self::W] = $WPercentage;
if($isXActive) $map[self::X] = $XPercentage;
if($isYActive) $map[self::Y] = $YPercentage;
if($isZActive) $map[self::Z] = $ZPercentage;
asort($map);
return key($map);
}
Edit: Fixed wrong sort(), you need asort() to maintain the index.

PHP Script quite CPU hungry. Ideas on an workaround

Because Server costs are the greatest spending, we want to get more out of everyone.
How can we achieve, that more scripts can run on this server?
What the Scrips are doing:
We run 80 PHP Scripts on one Server and feed them with Jobs through Gearman.
The Scripts are looking up Websites with cURL, extract the needed Informations with Zend_Dom_Query and store the Data in an DB.
Each Script get feeded with ca. 1000 URLs which they have to look up.
Script example is down below.
What's the Server made of:
lshws outpu:
description: Computer
width: 64 bits
capabilities: vsyscall64 vsyscall32
*-core
description: Motherboard
physical id: 0
*-memory
description: System memory
physical id: 0
size: 8191GiB
*-cpu
product: Intel(R) Xeon(R) CPU E31230 # 3.20GHz
vendor: Intel Corp.
physical id: 1
bus info: cpu#0
width: 64 bits
capabilities: fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp x86-64 constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer xsave avx lahf_lm ida arat epb xsaveopt pln pts tpr_shadow vnmi flexpriority ept vpid
Nevertheless this is an V-Server it's the only V-Server running on that server. It has also not 8191GB Memory more like 16GB.
To show you how exhausted the server is, here's tops output:
top - 14:45:04 up 8 days, 3:10, 1 user, load average: 72.96, 72.51, 71.82
Tasks: 100 total, 72 running, 28 sleeping, 0 stopped, 0 zombie
Cpu(s): 87.5%us, 12.2%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.3%st
Mem: 8589934588k total, 4349016k used, 8585585572k free, 0k buffers
Swap: 0k total, 0k used, 0k free, 282516k cached
Not to forget here's the scripts main structure:
// Get the Infos on which to crawl on
$asin = explode(',', $job->workload());
try {
$userproducts = new App_Dbservices_U...();
$konkurrenz = new App_Dbservices_K...();
$repricingstream = new App_Dbservices_R...();
$err = 0;
for ($i = 0; $i < count($asin) - 3; $i = $i + 50) {
$mh = curl_multi_init();
$handles = array();
for ($j = $i; $j < $i + 50; $j++) {
if ((count($asin) - 3) > $j) {
if (isset($asin[$j])) {
// create a new single curl handle
$ch = curl_init();
// setting several options like url, timeout, returntransfer
// simulate multithreading by calling the wait.php scipt and sleeping for $rand seconds
$url = // URL
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 80);
// add this handle to the multi handle
$erroro[$j] = curl_errno($ch);
$errmsg[$j] = curl_error($ch);
curl_multi_add_handle($mh, $ch);
// put the handles in an array to loop this later on
$handles[] = $ch;
}
}
}
}
// execute the multi handle
$running = null;
do {
curl_multi_exec($mh, $running);
} while ($running > 0);
// get the content (if there is any)
$output = '';
for ($k = 0; $k < count($handles); $k++) {
// get the content of the handle
$output[$k] = curl_multi_getcontent($handles[$k]);
$_asin[$k]['asin'] = $asin[$j - 50 + $k];
$_asin[$k]['condition'] = $condition[$j - 50 + $k];
$_asin[$k]['pId'] = $pId[$j - 50 + $k];
if ($output[$k] != '')
{
// get the dom of each page
$dom = new Zend_Dom_Query($output[$k]);
// get the sellerInfos of each page
$seller = $dom->query('div.Offer');
if (count($seller) > 0) {
// get the price out of the string
$seller_i = 0;
$selfCameOver = false;
foreach ($seller as $d2) {
if ($seller_i <= 6 OR $selfCameOver === false) {
$itemHtml = '';
foreach($d2->childNodes as $node) {
$itemHtml .= $node->ownerDocument->saveHTML($node);
}
$dom = new Zend_Dom_Query($itemHtml);
$itemPrice = $dom->query('span.Price');
foreach($itemPrice as $ItemPrice)
{
$_asin[$k]['price_end'][$seller_i] = 0.00;
$_asin[$k]['shipping_end'][$seller_i] = 0.00;
if (preg_match('/[0-9]++(?>[,.][0-9]+)?+/', $ItemPrice->textContent, $rueckgabe)) {
$priceEnd = str_replace(',', '', str_replace('.', '', $rueckgabe[0][0]));
$priceLength = strlen($priceEnd);
$priceEnd = substr($priceEnd, 0, ($priceLength - 2)) . '.' . substr($priceEnd, ($priceLength - 2), 2);
$_asin[$k]['price_end'][$seller_i] = (float)$priceEnd;
}
}
}
$shippingPrice = $dom->query('span.ShippingPrice');
foreach($shippingPrice as $ShippingPrice)
{
preg_match_all('/[0-9]{1,}([\,\. ]?[0-9])*/', $ShippingPrice->textContent, $rueckgabe);
if (isset($rueckgabe[0][0])) {
// ...
}
}
$_asin[$k]['price_total_end'][$seller_i] = $_asin[$k]['price_end'][$seller_i] + $_asin[$k]['shipping_end'][$seller_i];
$conditionTag = $dom->query('.Condition');
foreach($conditionTag as $ConditionTag)
{
$_asin[$k]['main_con'][$seller_i]= 0;
$_asin[$k]['sub_con'][$seller_i] = 0;
$conditionValue = explode(' - ', $ConditionTag->textContent);
if(isset($conditionValue[0])){
// ...
}
if(isset($conditionValue[1])) {
// ...
}
}
$ratingItem = $dom->query('.Rating');
$_asin[$k]['bewertung_end'][$seller_i] = -1;
$_asin[$k]['stars_end'][$seller_i] = -1;
foreach($ratingItem as $RatingItem)
{
echo $RatingItem->textContent; // 99% positiv ... 12 Monaten ... 11.719 Bewertungen ...
// I want to get 99 (which is stars ) and 11719 (which is bewertungen )
preg_match_all('/[0-9]{1,}([\,\. ]?[0-9])*/', preg_replace('/,/', '.', $RatingItem->textContent), $rueckgabe);
if (isset($rueckgabe[0]) AND count($rueckgabe[0]) > 0) {
$_asin[$k]['bewertung_end'][$seller_i] = (int)str_replace('.', '', $rueckgabe[0][count($rueckgabe[0]) - 1]);
$_asin[$k]['stars_end'][$seller_i] = $rueckgabe[0][0];
}
}
$sellerType = $dom->query('.Name img');
$_asin[$k]['merchant_end'][$seller_i] = "N/A";
$_asin[$k]['name_end'][$seller_i] = "N/A";
$_asin[$k]['img_end'][$seller_i] = "N/A";
$_asin[$k]['konk_type'][$seller_i] = 'ERROR';
if(count($sellerType) == 1)
{
foreach($sellerType as $SellerType)
{
$imgAltText = $SellerType->getAttribute('alt');
$a = explode('.', $imgAltText);
// ...
}
}
elseif(count($sellerType) == 0)
{
$_asin[$k]['img_end'][$seller_i] = 'NO_IMG';
$_asin[$k]['konk_type'][$seller_i] = 'WO_IMG';
$sellerName = $dom->query('.Name b');
foreach($sellerName as $SellerName)
{
$_asin[$k]['name_end'][$seller_i] = $SellerName->textContent;
}
$sellerMerchant = $dom->query('.Name a');
foreach($sellerMerchant as $SellerMerchant)
{
$_asin[$k]['merchant_end'][$seller_i] = str_replace('=', '', substr($SellerMerchant->getAttribute('href'), -14));
}
}
unset($rueckgabe);
}
$seller_i++;
}
}
}
// remove the handle from the multi handle
curl_multi_remove_handle($mh, $handles[$k]);
}
// Update Price ...
// Update Shipping ...
// Update Conc ...
unset($_asin);
// close the multi curl handle to free system resources
curl_multi_close($mh);
}
} catch (Exception $e) {
$error = new Repricing_Dbservices_Error();
$error->setError($id, $e->getMessage(), $e->getLine(), $e->getFile());
}
And also the script for the price update ( the other update-statements looks similiar)
$this->db->beginTransaction();
try {
for ($i = 0; $i < count($asin); $i++) {
if (isset($asin[$i]['price_total_end'])) {
if (count($asin[$i]['price_total_end']) > 1) {
if ($asin[$i]['price_total_end'][0] > 0) {
$this->db->query("UPDATE u... SET lowest_price = ? , last_lowest_price_update = ? WHERE id = ?", array(
$asin[$i]['price_total_end'][1],
date("Y-m-d H:i:s", time()),
$asin[$i]['pId']
));
}
} elseif (count($asin[$i]['price_total_end']) == 1) {
if ($asin[$i]['price_total_end'][0] >= 0) {
$this->db->query("UPDATE u... SET lowest_price = ? , last_lowest_price_update = ? WHERE id = ?", array(
-1,
date("Y-m-d H:i:s", time()),
$asin[$i]['pId']
));
}
}
}
}
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
echo $e->getMessage();
}
$this->db->closeConnection();
Do we have an big performance leak in our script, should we go along with an other language, or any other techniques? Every suggestion is highly appreciated.
You can replace all these kind of lines:
preg_match_all('/[0-9]{1,}([\,\. ]?[0-9])*/', $ItemPrice->textContent, $rueckgabe);
if (isset($rueckgabe[0])) {
// ...
}
by:
if (preg_match('/([0-9]++)(?>[.,]([0-9]++))?+/', $ItemPrice->textContent, $rueckgabe)) {
unset($rueckgabe[0]);
$priceEnd = sprintf("%01.2f", implode('.', $rueckgabe));
$_asin[$k]['price_end'][$seller_i] = $priceEnd;
}
You should replace all your for loops with foreach (then you avoid the count on each loop as RaymondN notices it). Example:
Instead of:
for ($k = 0; $k < count($handles); $k++) {
you write:
foreach($handles as $k=>$handle) {
// you can replace $handles[$k] by $handle
It is not very useful to convert the current datetime and format it in "Y-m-d H:i:s" since you can do directly the same with the mySQL statement NOW().
Don´t use count functions in the for loops that would save some CPU cycles ..
but use.
$m = count($array) - 1;
for ($i = 0 ; $i < $m ; $i++) {}
Whats the PHP version a new PHP version could possible perform much better.
The most notable thing here is that you are sharding the data too much - when your load average is higher than the number of CPUs, then the OS will start pre-emppting jobs instead of waiting for them to yeild the CPU. As a result overall throughput drops significantly. You seem to have a single CPU - for a cpu bound system like this running on a single core, I'd try 2, 4, 8 and 16 processes to see which gives the optimal behaviour (assuming you need to make the code fit the hardware rather than the other way around).
Your next problem is that the zend framework is very cpu and memory hungry: Zend is 4 times slower than unadorned PHP.
You've got a lot of inlined code in the loops here - while inlining does help performance it makes it a lot more difficult to get useful data out of a profiler - hence my next step after making the Zend-free and reducing the concurrency would be to structure the code into functions and profile it.

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];
}

PHP mtChart (new pChart): how do i control the angle of x-axis labels?

I am trying to graph the results of a survey, where the question is multiple choice.
eg. How would you describe this website?
format:
option | number of times selected | percentage of users who selected that option
Informative 1 50%
All of the above 1 50%
Interesting 0 0%
Intelligent 0 0%
Cool 0 0%
Incredible 0 0%
Sleek 0 0%
Amazing
The graph is a bar graph, where each bar represents one of those options, and the height of the bar depends on the number of times selected.
However, the labels are slanted at a 45 degree angle and are barely readable! here is my code:
<?php
require_once ("includes/common.php");
require_once ("graph/mtChart.min.php");
// type must be specified
$type = $_GET['type'];
if($type == "surveys_report_MC_or_CB") {
// PARAMS
$surveyID = $_GET['surveyID'];
$questionID = $_GET['questionID'];
// END PARAMS
$question = SurveyQuestions::getSingle($questionID);
$answers = SurveyAnswers::getAll($questionID);
$options = SurveyQuestionOptions::getAll($question[SurveyQuestions::surveyQuestionID]);
$others = SurveyAnswers::setOptionCounts($options, $answers);
$printedOthers = false;
// set graph
$values = array();
$axisLabels = array();
foreach($options as $option) {
$values[$option[SurveyQuestionOptions::optionText]] = $option['count'];
$axisLabels[] = $option[SurveyQuestionOptions::optionText];
}
$graphs = array();
$graphs[0] = $values;
$xName = "Option";
$yName = "Number of Times Selected";
$graphTitle = $question[SurveyQuestions::question];
$series = array("Total");
$showLegend = false;
$tall = false;
}
drawGraph($graphs, $axisLabels, $xName, $yName, $graphTitle, $series, $showLegend, $tall);
function drawGraph($graphs, $axisLabels, $xName, $yName, $graphTitle, $series, $showLegend, $tall) {
$Graph = ($tall) ? new mtChart(575,375) : new mtChart(575,275);
// Dataset definition
$avg = 0;
$i = 0;
foreach ($graphs as $key => $value) {
$Graph->AddPoint($value,"series" . $key);
$Graph->SetSerieName($series[$key],"series" . $key);
// Get average
$avg += array_sum($value);
$size = sizeof($value);
$i += $size;
// Calculate x-axis tick interval
$step = ceil($size / 25);
}
$Graph->AddPoint($axisLabels,"XLabel");
$Graph->AddAllSeries();
$Graph->RemoveSerie("XLabel");
$Graph->SetAbsciseLabelSerie("XLabel");
$Graph->SetXAxisName($xName);
$Graph->SetYAxisName($yName);
// Get from cache if it exists
$Graph->enableCaching(NULL, 'graph/cache/');
$Graph->GetFromCache();
// Initialize the graph
$Graph->setInterval($step);
$Graph->setFontProperties("graph/tahoma.ttf",8);
($showLegend) ? $Graph->setGraphArea(45,30,475,200) : $Graph->setGraphArea(75,30,505,200);
$Graph->drawGraphArea(255,255,255,TRUE);
$Graph->drawScale(SCALE_START0,100,100,100,TRUE,55,1,TRUE);
$Graph->drawGrid(4,TRUE,230,230,230,50);
// Draw the 0 line
$Graph->setFontProperties("graph/tahoma.ttf",6);
$Graph->drawTreshold(0,143,55,72,TRUE,TRUE);
// Draw the bar graph
$Graph->drawBarGraph();
// Draw average line
$Graph->drawTreshold($avg/$i, 0, 0, 0, FALSE, FALSE, 5);
// Finish the graph
$Graph->setFontProperties("graph/tahoma.ttf",8);
if ($showLegend) {
$Graph->drawLegend(482,30,255,255,255,255,255,255,100,100,100);
}
$Graph->setFontProperties("graph/tahoma.ttf",10);
$Graph->drawTitle(0,22,$graphTitle,100,100,100,555);
// Draw Graph
$Graph->Stroke();
}
and here is where i use it on the page:
<div class="graph_container">
<img src="drawGraph.php?type=surveys_report_MC_or_CB&surveyID=<?php
echo $survey[Surveys::surveyID] ?>&questionID=<?php
echo $question[SurveyQuestions::surveyQuestionID] ?>" />
Is there a setting i can apply to the graph which will make the text look nicer, or at least let me set the angle to 90 degrees so people can read it if they cock their head to the left?
btw, mtchart is located here: http://code.google.com/p/mtchart/
and pchart (the original, which has mainly the same code) is here: http://pchart.sourceforge.net/documentation.php
Edit the following line, which draws the horizontal labels:
$Graph->drawScale(SCALE_START0,100,100,100,TRUE,55,1,TRUE);
// ^^--- Edit this value
The sixth argument (55) is the angle at which to write the text; 0 is horizontal, 90 is vertical, 120 is leaning back on itself, etc.. So, if you want some head cocking, set the value to 90.
The whole prototype for that method is:
void drawScale(int $ScaleMode = SCALE_NORMAL,
int $R = 150, int $G = 150, int $B = 150,
bool $DrawTicks = TRUE, int $Angle = 0, int $Decimals = 1,
bool $WithMargin = FALSE, bool $RightScale = FALSE)

Categories