From: Maxious
Date: Wed, 27 Apr 2011 12:11:37 +0000
Subject: Merge branch 'master' of https://github.com/maxious/ACTBus-ui
X-Git-Url: https://maxious.lambdacomplex.org/git/?p=busui.git&a=commitdiff&h=971bb30494b1626dc1cbdc47e5c8a134dac1d5db
---
Merge branch 'master' of https://github.com/maxious/ACTBus-ui
Conflicts:
include/common-template.inc.php
---
--- a/about.php
+++ b/about.php
@@ -25,7 +25,7 @@
All offers are not binding and without obligation. The Author expressly reserves the right, in his discretion, to suspend,
change, modify, add or remove portions of the Site and to restrict or terminate the use and accessibility of the Site
without prior notice.
-
+
--- /dev/null
+++ b/aws/awsStartup.sh
@@ -1,1 +1,39 @@
+#!/bin/bash
+#this script should be run from a fresh git checkout from github
+#ami base must have yum install lighttpd-fastcgi, git, tomcat6
+#php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
+#postgres postgres-server php-pg
+#http://www.how2forge.org/installing-lighttpd-with-php5-and-mysql-support-on-fedora-12
+cp /root/aws.php /tmp/
+mkdir /var/www/lib/staticmaplite/cache
+chcon -h system_u:object_r:httpd_sys_content_t /var/www
+chcon -R -h root:object_r:httpd_sys_content_t /var/www/*
+chcon -R -t httpd_sys_content_rw_t /var/www/lib/staticmaplite/cache
+chmod -R 777 /var/www/lib/staticmaplite/cache
+chcon -R -t httpd_sys_content_rw_t /var/www/labs/tiles
+chmod -R 777 /var/www/labs/tiles
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
+-O /var/www/cbrfeed.zip
+
+createdb transitdata
+createlang -d transitdata plpgsql
+psql -d transitdata -f /var/www/lib/postgis.sql
+# curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz
+#made with pg_dump transitdata | gzip -c > transitdata.cbrfeed.sql.gz
+gunzip /var/www/transitdata.cbrfeed.sql.gz
+psql -d transitdata -f /var/www/transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -d transitdata -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
+php /var/www/updatedb.php
+
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
+-O /tmp/Graph.obj
+rm -rfv /usr/share/tomcat6/webapps/opentripplanner*
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-webapp.war \
+-O /usr/share/tomcat6/webapps/opentripplanner-webapp.war
+wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-api-webapp.war \
+-O /usr/share/tomcat6/webapps/opentripplanner-api-webapp.war
+/etc/init.d/tomcat6 restart
+
--- /dev/null
+++ b/aws/pg_hba.conf
@@ -1,1 +1,75 @@
+# PostgreSQL Client Authentication Configuration File
+# ===================================================
+#
+# Refer to the "Client Authentication" section in the
+# PostgreSQL documentation for a complete description
+# of this file. A short synopsis follows.
+#
+# This file controls: which hosts are allowed to connect, how clients
+# are authenticated, which PostgreSQL user names they can use, which
+# databases they can access. Records take one of these forms:
+#
+# local DATABASE USER METHOD [OPTIONS]
+# host DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
+# hostssl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
+# hostnossl DATABASE USER CIDR-ADDRESS METHOD [OPTIONS]
+#
+# (The uppercase items must be replaced by actual values.)
+#
+# The first field is the connection type: "local" is a Unix-domain socket,
+# "host" is either a plain or SSL-encrypted TCP/IP socket, "hostssl" is an
+# SSL-encrypted TCP/IP socket, and "hostnossl" is a plain TCP/IP socket.
+#
+# DATABASE can be "all", "sameuser", "samerole", a database name, or
+# a comma-separated list thereof.
+#
+# USER can be "all", a user name, a group name prefixed with "+", or
+# a comma-separated list thereof. In both the DATABASE and USER fields
+# you can also write a file name prefixed with "@" to include names from
+# a separate file.
+#
+# CIDR-ADDRESS specifies the set of hosts the record matches.
+# It is made up of an IP address and a CIDR mask that is an integer
+# (between 0 and 32 (IPv4) or 128 (IPv6) inclusive) that specifies
+# the number of significant bits in the mask. Alternatively, you can write
+# an IP address and netmask in separate columns to specify the set of hosts.
+#
+# METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
+# "ident", "pam", "ldap" or "cert". Note that "password" sends passwords
+# in clear text; "md5" is preferred since it sends encrypted passwords.
+#
+# OPTIONS are a set of options for the authentication in the format
+# NAME=VALUE. The available options depend on the different authentication
+# methods - refer to the "Client Authentication" section in the documentation
+# for a list of which options are available for which authentication methods.
+#
+# Database and user names containing spaces, commas, quotes and other special
+# characters must be quoted. Quoting one of the keywords "all", "sameuser" or
+# "samerole" makes the name lose its special character, and just match a
+# database or username with that name.
+#
+# This file is read on server startup and when the postmaster receives
+# a SIGHUP signal. If you edit the file on a running system, you have
+# to SIGHUP the postmaster for the changes to take effect. You can use
+# "pg_ctl reload" to do that.
+# Put your actual configuration here
+# ----------------------------------
+#
+# If you want to allow non-local connections, you need to add more
+# "host" records. In that case you will also need to make PostgreSQL listen
+# on a non-local interface via the listen_addresses configuration parameter,
+# or via the -i or -h command line switches.
+#
+
+
+
+# TYPE DATABASE USER CIDR-ADDRESS METHOD
+
+# "local" is for Unix domain socket connections only
+local all all trust
+# IPv4 local connections:
+host all all 127.0.0.1/32 trust
+# IPv6 local connections:
+host all all ::1/128 trust
+
--- a/feedback.php
+++ b/feedback.php
@@ -26,7 +26,7 @@
}
if (isset($_REQUEST['feedback']) || isset($_REQUEST['newlocation'])){
sendEmail("bus.lambda feedback",print_r($_REQUEST,true));
- echo "Thank you for your feedback! ";
+ echo "Thank you for your feedback! ";
} else {
$stopid = "";
$stopcode = "";
@@ -48,7 +48,7 @@
if you click on feedback from a stop page, these will get filled in automatically. else describe the location/street of the stop in one of these boxes
Suggested Stop Location (lat/long or words):
- if your device supports javascript, you can pick a location from the map above
+
--- /dev/null
+++ b/include/common-db.inc.php
@@ -1,1 +1,21 @@
+
--- a/include/common-geo.inc.php
+++ b/include/common-geo.inc.php
@@ -46,9 +46,9 @@
$center = $totalLat / sizeof($mapPoints) . "," . $totalLon / sizeof($mapPoints);
}
$output = "";
- if ($collapsible) $output.= 'Open Map... ';
- $output.= '
';
+ if ($collapsible) $output.= '
Open Map... ';
+ $output.= '
';
if ($collapsible) $output.= '
';
return $output;
}
@@ -70,6 +70,7 @@
else return round($km,2)."k";
} else return floor($km * 1000);
}
+
function decodePolylineToArray($encoded)
{
// source: http://latlongeeks.com/forum/viewtopic.php?f=4&t=5
--- a/include/common-net.inc.php
+++ b/include/common-net.inc.php
@@ -5,7 +5,7 @@
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_TIMEOUT, 30);
+ curl_setopt($ch, CURLOPT_TIMEOUT, 45);
$page = curl_exec($ch);
if (curl_errno($ch)) {
echo "
Database temporarily unavailable: ";
--- a/include/common-session.inc.php
+++ b/include/common-session.inc.php
@@ -17,7 +17,6 @@
}
else {
$geolocate = filter_var($_REQUEST['geolocate'], FILTER_SANITIZE_URL);
- echo $_REQUEST['geolocate'];
if (startsWith($geolocate, "-")) {
$locateparts = explode(",", $geolocate);
$_SESSION['lat'] = $locateparts[0];
@@ -52,6 +51,9 @@
session_destroy();
session_start();
}
-debug(print_r($_SESSION, true) , "session");
+//debug(print_r($_SESSION, true) , "session");
+function current_time() {
+ return ($_SESSION['time']? $_SESSION['time'] : date("H:i:s"));
+}
?>
--- a/include/common-template.inc.php
+++ b/include/common-template.inc.php
@@ -1,28 +1,27 @@
';
if ($datepicker) echo ' ';
- if (isDebugServer()) echo '
+ if (isDebugServer()) {
+ echo '
+
';
- else echo '
+ }
+ else {
+ echo '
- ';
- if ($datepicker) echo '';
+ }
+ if ($datepicker) {
+ echo '
';
+ }
echo '
+
+
+
+ Status:
+
+
+
+
+
+
--- /dev/null
+++ b/labs/busstopdensity.tile.php
@@ -1,1 +1,121 @@
+
+* @version: 1.0
+* @history:
+* 1.0 creation
+*/
+ set_time_limit(120);//2mn
+ ini_set('memory_limit', '256M');
+error_reporting(E_ALL ^ E_DEPRECATED);
+ require_once ('lib/GoogleMapUtility.php');
+ require_once ('lib/HeatMap.php');
+
+ //Root folder to store generated tiles
+ define('TILE_DIR', 'tiles/');
+ //Covered geographic areas
+ define('MIN_LAT', -35.48);
+ define('MAX_LAT', -35.15);
+ define('MIN_LNG', 148.98);
+ define('MAX_LNG', 149.25);
+ define('TILE_SIZE_FACTOR', 0.5);
+ define('SPOT_RADIUS', 30);
+ define('SPOT_DIMMING_LEVEL', 50);
+
+ //Input parameters
+ if(isset($_GET['x']))
+ $X = (int)$_GET['x'];
+ else
+ exit("x missing");
+ if(isset($_GET['y']))
+ $Y = (int)$_GET['y'];
+ else
+ exit("y missing");
+ if(isset($_GET['zoom']))
+ $zoom = (int)$_GET['zoom'];
+ else
+ exit("zoom missing");
+
+ $dir = TILE_DIR.$zoom;
+ $tilename = $dir.'/'.$X.'_'.$Y.'.png';
+ //HTTP headers (data type and caching rule)
+ header("Cache-Control: must-revalidate");
+ header("Expires: " . gmdate("D, d M Y H:i:s", time() + 86400) . " GMT");
+ if(!file_exists($tilename)){
+ $rect = GoogleMapUtility::getTileRect($X, $Y, $zoom);
+ //A tile can contain part of a spot with center in an adjacent tile (overlaps).
+ //Knowing the spot radius (in pixels) and zoom level, a smart way to process tiles would be to compute the box (in decimal degrees) containing only spots that can be drawn on current tile. We choose a simpler solution by increeasing geo bounds by 2*TILE_SIZE_FACTOR whatever the zoom level and spot radius.
+ $extend_X = $rect->width * TILE_SIZE_FACTOR;//in decimal degrees
+ $extend_Y = $rect->height * TILE_SIZE_FACTOR;//in decimal degrees
+ $swlat = $rect->y - $extend_Y;
+ $swlng = $rect->x - $extend_X;
+ $nelat = $swlat + $rect->height + 2 * $extend_Y;
+ $nelng = $swlng + $rect->width + 2 * $extend_X;
+
+ if( ($nelat <= MIN_LAT) || ($swlat >= MAX_LAT) || ($nelng <= MIN_LNG) || ($swlng >= MAX_LNG)){
+ //No geodata so return generic empty tile
+ echo file_get_contents(TILE_DIR.'empty.png');
+ exit();
+ }
+
+ //Get McDonald's spots
+ $spots = fGetPOI('Select * from stops where
+ (stop_lon > '.$swlng.' AND stop_lon < '.$nelng.')
+ AND (stop_lat < '.$nelat.' AND stop_lat > '.$swlat.')', $im, $X, $Y, $zoom, SPOT_RADIUS);
+
+
+ if(empty($spots)){
+ //No geodata so return generic empty tile
+ header('Content-type: image/png');
+ echo file_get_contents(TILE_DIR.'empty.png');
+ }else{
+ if(!file_exists($dir)){
+ mkdir($dir, 0705);
+ }
+ //All the magics is in HeatMap class :)
+ $im = HeatMap::createImage($spots, GoogleMapUtility::TILE_SIZE, GoogleMapUtility::TILE_SIZE, heatMap::$WITH_ALPHA, SPOT_RADIUS, SPOT_DIMMING_LEVEL, HeatMap::$GRADIENT_FIRE);
+ //Store tile for reuse and output it
+ header('content-type:image/png;');
+ imagepng($im, $tilename);
+ echo file_get_contents($tilename);
+ imagedestroy($im);
+ unset($im);
+ }
+ }else{
+ //Output stored tile
+ header('content-type:image/png;');
+ echo file_get_contents($tilename);
+ }
+ /////////////
+ //Functions//
+ /////////////
+ function fGetPOI($query, &$im, $X, $Y, $zoom, $offset){
+ global $conn;
+ $nbPOIInsideTile = 0;
+
+ $result = pg_query($conn, $query);
+ $spots = Array();
+ if (!$result) {
+ databaseError(pg_result_error($result));
+ return Array();
+ }
+ foreach( pg_fetch_all($result) as $row){
+ $point = GoogleMapUtility::getOffsetPixelCoords($row['stop_lat'], $row['stop_lon'], $zoom, $X, $Y);
+ //Count result only in the tile
+ if( ($point->x > -$offset) && ($point->x < (GoogleMapUtility::TILE_SIZE+$offset)) && ($point->y > -$offset) && ($point->y < (GoogleMapUtility::TILE_SIZE+$offset))){
+ $spots[] = new HeatMapPoint($point->x, $point->y);
+ }
+
+ }//while
+ return $spots;
+ }//fAddPOI
+?>
+
+
--- /dev/null
+++ b/labs/css
@@ -1,1 +1,1 @@
-
+../css/
--- /dev/null
+++ b/labs/feedback.php
@@ -1,1 +1,1 @@
-
+../feedback.php
--- /dev/null
+++ b/labs/index.php
@@ -1,1 +1,20 @@
+
+
+
+
+
--- /dev/null
+++ b/labs/js
@@ -1,1 +1,1 @@
-
+../js
--- /dev/null
+++ b/labs/lib
@@ -1,1 +1,1 @@
-
+../lib
--- a/labs/myway_api.json.php
+++ b/labs/myway_api.json.php
@@ -74,7 +74,7 @@
}
if (!isset($return['error'])) {
- include_once ('simple_html_dom.php');
+ include_once ('lib/simple_html_dom.php');
$page = str_get_html($pageHTML);
$pageAlerts = $page->find(".smartCardAlert");
if (sizeof($pageAlerts) > 0) {
--- a/labs/mywaybalance.php
+++ b/labs/mywaybalance.php
@@ -1,22 +1,40 @@
+
+
+ ';
+
$return = Array();
+function logout() {
+ setcookie("card_number", "", time() - 60 * 60 * 24 * 100, "/");
+ setcookie("date", "", time() - 60 * 60 * 24 * 100, "/");
+ setcookie("secret_answer", "", time() - 60 * 60 * 24 * 100, "/");
+}
function printBalance($cardNumber, $date, $pwrd)
{
global $return;
- $return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd"), true);
-
- if (isset($return['error'])) {
- echo "
" . var_dump($return['error']) . " ";
- } else {
+ $return = json_decode(getPage(curPageURL() . "/myway_api.json.php?card_number=$cardNumber&DOBday={$date[0]}&DOBmonth={$date[1]}&DOByear={$date[2]}&secret_answer=$pwrd") , true);
+ if (isset($return['error'])) {
+ logout();
+ echo '
' . $return['error'][0] . " ";
+ }
+ else {
echo "
Balance: " . $return['myway_carddetails']['Card Balance'] . " ";
echo '
";
}
@@ -25,12 +43,15 @@
$cardNumber = $_REQUEST['card_number'];
$date = explode("/", $_REQUEST['date']);
$pwrd = $_REQUEST['secret_answer'];
- if ($_REQUEST['remember'] == true) {
- $_COOKIE['card_number'] = $cardNumber;
- $_COOKIE['date'] = $date;
- $_COOKIE['secret_answer'] = $pwrd;
+ if ($_REQUEST['remember'] == "on") {
+ setcookie("card_number", $cardNumber, time() + 60 * 60 * 24 * 100, "/");
+ setcookie("date", $_REQUEST['date'], time() + 60 * 60 * 24 * 100, "/");
+ setcookie("secret_answer", $pwrd, time() + 60 * 60 * 24 * 100, "/");
}
printBalance($cardNumber, $date, $pwrd);
+}
+else if (isset($_REQUEST['logout'])) {
+ echo '
Logged out of MyWay balance Back to main menu... ';
}
else if (isset($_COOKIE['card_number']) && isset($_COOKIE['date']) && isset($_COOKIE['secret_answer'])) {
$cardNumber = $_COOKIE['card_number'];
--- /dev/null
+++ b/labs/networkstats.php
@@ -1,1 +1,148 @@
+
+
+
+
+
+
+
+
+
+
+
+{$route['route_short_name']} {$route['route_long_name']}";
+foreach (getRouteTrips($routeid) as $key => $trip) {
+ $dLabel[$key] = $trip['arrival_time'];
+ if ($key == 0) {
+ $time = strtotime($trip['arrival_time']);
+ $adjustFactor = (date("G", $time) * 3600);
+ }
+ $tripStops = viaPoints($trip['trip_id']);
+ foreach ($tripStops as $i => $stop) {
+ if ($key == 0) {
+ $dTicks[$i] = $stop['stop_name'];
+ }
+ $time = strtotime($stop['arrival_time']);
+ $d[$key][$i] = (date("G", $time) * 3600) + (date("i", $time) * 60) + date("s", $time) - $adjustFactor;
+
+ }
+}
+
+?>
+
+
+
+
+
+
+
+
+
+
--- a/labs/tripPlannerTester.kml.php
+++ b/labs/tripPlannerTester.kml.php
@@ -8,6 +8,50 @@
}
else {
return (($pBegin - $pEnd) * (1 - ($pStep / $pMax))) + $pEnd;
+ }
+}
+require ("../lib/rolling-curl/RollingCurl.php");
+function processResult_cb($response, $info, $request)
+{
+ global $testRegions, $regionTimes,$csv,$kml, $latdeltasize,$londeltasize;
+ $md = $request->metadata;
+ $tripplan = json_decode($response);
+ $plans = Array();
+ //var_dump(Array($info, $request));
+ if (is_array($tripplan->plan->itineraries->itinerary)) {
+ foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
+ $plans[floor($itinerary->duration / 60000) ] = $itinerary;
+ }
+ }
+ else {
+ $plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
+ }
+ if ($csv) echo "{$md['i']},{$md['j']}," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,{$md['key']}\n";
+ if ($kml) {
+ $time = min(array_keys($plans));
+ $plan = "";
+ if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
+ foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
+ $plan.= processLeg($legNumber, $leg) . ",";
+ }
+ }
+ else {
+ $plan.= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
+ }
+ if (isset($tripplan->error) && $tripplan->error->id == 404) {
+ $time = 999;
+ $plan = "Trip not possible without excessive walking from nearest bus stop";
+ }
+ $testRegions[] = Array(
+ "lat" => $md['i'],
+ "lon" => $md['j'],
+ "time" => $time,
+ "latdeltasize" => $latdeltasize,
+ "londeltasize" => $londeltasize,
+ "regionname" => $md['key'],
+ "plan" => $plan . "original plan "
+ );
+ $regionTimes[] = $time;
}
}
function Gradient($HexFrom, $HexTo, $ColorSteps)
@@ -49,18 +93,17 @@
//}
//$walkingstep.= floor($step->distance) . "m";
//return $walkingstep;
+
}
}
$csv = false;
$kml = true;
if ($kml) {
- //header('Content-Type: application/vnd.google-earth.kml+xml');
+ header('Content-Type: application/vnd.google-earth.kml+xml');
echo '
';
}
include ('../include/common.inc.php');
-//Test code to grab transit times
-// make sure to sleep(10);
$boundingBoxes = Array(
"belconnen" => Array(
"startlat" => - 35.1928,
@@ -105,72 +148,29 @@
$useragent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
if ($csv) echo "";
if ($csv) echo "lat,lon,time,latdeltasize, londeltasize, region key name\n";
+$rc = new RollingCurl("processResult_cb");
+$rc->window_size = 3;
foreach ($boundingBoxes as $key => $boundingBox) {
for ($i = $boundingBox['startlat']; $i >= $boundingBox['finishlat']; $i-= $latdeltasize) {
for ($j = $boundingBox['startlon']; $j <= $boundingBox['finishlon']; $j+= $londeltasize) {
$url = $otpAPIurl . "ws/plan?date=" . urlencode($startDate) . "&time=" . urlencode($startTime) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=440&wheelchair=false&toPlace=" . $i . "," . $j . "&fromPlace=$fromPlace";
- $ch = curl_init($url);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_HEADER, 0);
- curl_setopt($ch, CURLOPT_HTTPHEADER, array(
+ $request = new RollingCurlRequest($url);
+ $request->headers = Array(
"Accept: application/json"
- ));
- curl_setopt($ch, CURLOPT_TIMEOUT, 5);
- $page = curl_exec($ch);
- if (curl_errno($ch)) {
- if ($csv) echo "Trip planner temporarily unavailable: " . curl_errno($ch) . " " . curl_error($ch);
- }
- else {
- $tripplan = json_decode($page);
- $plans = Array();
- if (is_array($tripplan->plan->itineraries->itinerary)) {
- foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
- $plans[floor($itinerary->duration / 60000) ] = $itinerary;
- }
- }
- else {
- $plans[floor($tripplan->plan->itineraries->itinerary->duration / 60000) ] = $tripplan->plan->itineraries->itinerary;
- }
- if ($csv) echo "$i,$j," . min(array_keys($plans)) . ",$latdeltasize, $londeltasize,$key\n";
- if ($kml) {
- $time = min(array_keys($plans));
- $plan = "";
- if (is_array($plans[min(array_keys($plans)) ]->legs->leg)) {
- foreach ($plans[min(array_keys($plans)) ]->legs->leg as $legNumber => $leg) {
- $plan .= processLeg($legNumber, $leg).",";
- }
- }
- else {
- $plan .= processLeg(0, $plans[min(array_keys($plans)) ]->legs->leg);
- }
- if (isset($tripplan->error) && $tripplan->error->id == 404) {
- $time = 999;
- $plan = "Trip not possible without excessive walking from nearest bus stop";
- }
- $testRegions[] = Array(
- "lat" => $i,
- "lon" => $j,
- "time" => $time,
- "latdeltasize" => $latdeltasize,
- "londeltasize" => $londeltasize,
- "regionname" => $key,
- "plan" => $plan . "original plan "
- );
- $regionTimes[] = $time;
- }
- }
- flush(); @ob_flush();
- curl_close($ch);
- }
- }
-}
+ );
+ $request->metadata = Array( "i" => $i, "j" => $j, "key" => $key);
+ $rc->add($request);
+ }
+ }
+}
+$rc->execute();
if ($kml) {
$colorSteps = 9;
//$minTime = min($regionTimes);
//$maxTime = max($regionTimes);
//$rangeTime = $maxTime - $minTime;
//$deltaTime = $rangeTime / $colorSteps;
- $Gradients = Gradient(strrev("66FF00"), strrev("FF0000"), $colorSteps); // KML is BGR not RGB so strrev
+ $Gradients = Gradient(strrev("66FF00") , strrev("FF0000") , $colorSteps); // KML is BGR not RGB so strrev
foreach ($testRegions as $testRegion) {
//$band = (floor(($testRegion[time] - $minTime) / $deltaTime));
$band = (floor($testRegion[time] / 10));
--- a/layar_api.php
+++ b/layar_api.php
@@ -9,32 +9,30 @@
$page_end = $max_page + filter_var($_REQUEST['pageKey'], FILTER_SANITIZE_NUMBER_INT);
$lat = filter_var($_REQUEST['lat'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
$lon = filter_var($_REQUEST['lon'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION);
-
-$contents = getStopsNearby($lat, $lon, 50);
-
+$max_distance = filter_var($_REQUEST['radius'], FILTER_SANITIZE_NUMBER_INT);
+$contents = getNearbyStops($lat, $lon, 50, $max_distance);
$stopNum = 0;
foreach ($contents as $stop) {
$stopNum++;
if ($stopNum > $page_start && $stopNum <= $page_end) {
$hotspot = Array();
- $hotspot['id'] = $stop[id];
- $hotspot['title'] = $stop[name];
+ $hotspot['id'] = $stop['stop_id'];
+ $hotspot['title'] = $stop['stop_name'];
$hotspot['type'] = 0;
- $hotspot['lat'] = floor($stop[lat] * 1000000);
- $hotspot['lon'] = floor($stop[lon] * 1000000);
- $hotspot['distance'] = distance($stop[lat], $stop[lon], $_REQUEST['lat'], $_REQUEST['lon']);
+ $hotspot['lat'] = floor($stop['stop_lat'] * 1000000);
+ $hotspot['lon'] = floor($stop['stop_lon'] * 1000000);
+ $hotspot['distance'] = floor($stop['distance']);
+ $hotspot['attribution'] = "ACTION Buses";
$hotspot['actions'] = Array(
Array(
"label" => 'View more trips/information',
- 'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $stop[id]
+ 'uri' => 'http://bus.lambdacomplex.org/' . 'stop.php?stopid=' . $stop['stop_id']
)
);
-
- $url = $APIurl . "/json/stoptrips?stop=" . $row[0] . "&time=" . midnight_seconds() . "&service_period=" . service_period() . "&limit=4&time_range=" . strval(90 * 60);
- $trips = getStopTrips($stopID);
+ $trips = getStopTripsWithTimes($stop['stop_id'], "", "", "", 3);
foreach ($trips as $key => $row) {
if ($key < 3) {
- $hotspot['line' . strval($key + 2) ] = $row[1][1] . ' @ ' . midnight_seconds_to_time($row[0]);
+ $hotspot['line' . strval($key + 2) ] = $row['route_short_name'] . ' ' . $row['route_long_name'] . ' @ ' . $row['arrival_time'];
}
}
if (sizeof($trips) == 0) $hotspot['line2'] = 'No trips in the near future.';
@@ -49,7 +47,7 @@
$output['errorString'] = 'no results, try increasing range';
$output['errorCode'] = 21;
}
-if ($page_end >= $max_results || sizeof($hotspot) < $max_page) {
+if ($page_end >= $max_results || sizeof($contents) < $page_start+$max_page) {
$output["morePages"] = false;
$output["nextPageKey"] = null;
}
--- /dev/null
+++ b/lib/GoogleMapUtility.php
@@ -1,1 +1,97 @@
+
+* @version: 1.1
+* @history:
+* 1.0 creation
+ 1.1 disclaimer added
+*/
+class GoogleMapUtility {
+ const TILE_SIZE = 256;
+
+ //(lat, lng, z) -> parent tile (X,Y)
+ public static function getTileXY($lat, $lng, $zoom) {
+ $normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+ $scale = 1 << ($zoom);
+ return new Point(
+ (int)($normalised->x * $scale),
+ (int)($normalised->y * $scale)
+ );
+ }//toTileXY
+
+ //(lat, lng, z) -> (x,y) with (0,0) in the upper left corner of the MAP
+ public static function getPixelCoords($lat, $lng, $zoom) {
+ $normalised = GoogleMapUtility::_toNormalisedMercatorCoords(GoogleMapUtility::_toMercatorCoords($lat, $lng));
+ $scale = (1 << ($zoom)) * GoogleMapUtility::TILE_SIZE;
+ return new Point(
+ (int)($normalised->x * $scale),
+ (int)($normalised->y * $scale)
+ );
+ }//getPixelCoords
+ //(lat, lng, z) -> (x,y) in the upper left corner of the TILE ($X, $Y)
+ public static function getOffsetPixelCoords($lat,$lng,$zoom, $X, $Y) {
+ $pixelCoords = GoogleMapUtility::getPixelCoords($lat, $lng, $zoom);
+ return new Point(
+ $pixelCoords->x - $X * GoogleMapUtility::TILE_SIZE,
+ $pixelCoords->y - $Y * GoogleMapUtility::TILE_SIZE
+ );
+ }//getPixelOffsetInTile
+
+ public static function getTileRect($X,$Y,$zoom) {
+ $tilesAtThisZoom = 1 << $zoom;
+ $lngWidth = 360.0 / $tilesAtThisZoom;
+ $lng = -180 + ($X * $lngWidth);
+ $latHeightMerc = 1.0 / $tilesAtThisZoom;
+ $topLatMerc = $Y * $latHeightMerc;
+ $bottomLatMerc = $topLatMerc + $latHeightMerc;
+ $bottomLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $bottomLatMerc))))) - (M_PI / 2));
+ $topLat = (180 / M_PI) * ((2 * atan(exp(M_PI * (1 - (2 * $topLatMerc))))) - (M_PI / 2));
+ $latHeight = $topLat - $bottomLat;
+ return new Boundary($lng, $bottomLat, $lngWidth, $latHeight);
+ }//getTileRect
+
+ private static function _toMercatorCoords($lat, $lng) {
+ if ($lng > 180) {
+ $lng -= 360;
+ }
+ $lng /= 360;
+ $lat = asinh(tan(deg2rad($lat)))/M_PI/2;
+ return new Point($lng, $lat);
+ }//_toMercatorCoords
+
+ private static function _toNormalisedMercatorCoords($point) {
+ $point->x += 0.5;
+ $point->y = abs($point->y-0.5);
+ return $point;
+ }//_toNormalisedMercatorCoords
+}//GoogleMapUtility
+
+class Point {
+ public $x,$y;
+ function __construct($x,$y) {
+ $this->x = $x;
+ $this->y = $y;
+ }
+ function __toString() {
+ return "({$this->x},{$this->y})";
+ }
+}//Point
+
+class Boundary {
+ public $x,$y,$width,$height;
+ function __construct($x,$y,$width,$height) {
+ $this->x = $x;
+ $this->y = $y;
+ $this->width = $width;
+ $this->height = $height;
+ }
+ function __toString() {
+ return "({$this->x} x {$this->y},{$this->width},{$this->height})";
+ }
+}//Boundary
+?>
--- /dev/null
+++ b/lib/HeatMap.php
@@ -1,1 +1,275 @@
-
+
+* @version: 1.0
+* @history:
+* 1.0 creation
+*/
+ define('PI2', 2*M_PI);
+
+ class HeatMapPoint{
+ public $x,$y;
+ function __construct($x,$y) {
+ $this->x = $x;
+ $this->y = $y;
+ }
+ function __toString() {
+ return "({$this->x},{$this->y})";
+ }
+ }//Point
+
+ class HeatMap{
+ //TRANSPARENCY
+ public static $WITH_ALPHA = 0;
+ public static $WITH_TRANSPARENCY = 1;
+ //GRADIENT STYLE
+ public static $GRADIENT_CLASSIC = 'classic';
+ public static $GRADIENT_FIRE = 'fire';
+ public static $GRADIENT_PGAITCH = 'pgaitch';
+ //GRADIENT MODE (for heatImage)
+ public static $GRADIENT_NO_NEGATE_NO_INTERPOLATE = 0;
+ public static $GRADIENT_NO_NEGATE_INTERPOLATE = 1;
+ public static $GRADIENT_NEGATE_NO_INTERPOLATE = 2;
+ public static $GRADIENT_NEGATE_INTERPOLATE = 3;
+ //NOT PROCESSED PIXEL (for heatImage)
+ public static $KEEP_VALUE = 0;
+ public static $NO_KEEP_VALUE = 1;
+ //CONSTRAINTS
+ private static $MIN_RADIUS = 2;//in px
+ private static $MAX_RADIUS = 50;//in px
+ private static $MAX_IMAGE_SIZE = 10000;//in px
+
+ //generate an $image_width by $image_height pixels heatmap image of $points
+ public static function createImage($data, $image_width, $image_height, $mode=0, $spot_radius = 30, $dimming = 75, $gradient_name = 'classic'){
+ $_gradient_name = $gradient_name;
+ if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+ $_gradient_name = self::$GRADIENT_CLASSIC;
+ }
+ $_image_width = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_width)));
+ $_image_height = min(self::$MAX_IMAGE_SIZE, max(0, intval($image_height)));
+ $_spot_radius = min(self::$MAX_RADIUS, max(self::$MIN_RADIUS, intval($spot_radius)));
+ $_dimming = min(255, max(0, intval($dimming)));
+ if(!is_array($data)){
+ return false;
+ }
+ $im = imagecreatetruecolor($_image_width, $_image_height);
+ $white = imagecolorallocate($im, 255, 255, 255);
+ imagefill($im, 0, 0, $white);
+ if(self::$WITH_ALPHA == $mode){
+ imagealphablending($im, false);
+ imagesavealpha($im,true);
+ }
+ //Step 1: create grayscale image
+ foreach($data as $datum){
+ if( (is_array($datum) && (count($datum)==1)) || (!is_array($datum) && ('HeatMapPoint' == get_class($datum)))){//Plot points
+ if('HeatMapPoint' != get_class($datum)){
+ $datum = $datum[0];
+ }
+ self::_drawCircularGradient($im, $datum->x, $datum->y, $_spot_radius, $_dimming);
+ }else if(is_array($datum)){//Draw lines
+ $length = count($datum)-1;
+ for($i=0; $i < $length; ++$i){//Loop through points
+ //Bresenham's algorithm to plot from from $datum[$i] to $datum[$i+1];
+ self::_drawBilinearGradient($im, $datum[$i], $datum[$i+1], $_spot_radius, $_dimming);
+ }
+ }
+ }
+ //Gaussian filter
+ if($_spot_radius >= 30){
+ imagefilter($im, IMG_FILTER_GAUSSIAN_BLUR);
+ }
+ //Step 2: create colored image
+ if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+ return FALSE;
+ }
+ $grad_size = count($grad_rgba);
+ for($x=0; $x <$_image_width; ++$x){
+ for($y=0; $y <$_image_height; ++$y){
+ $level = imagecolorat($im, $x, $y) & 0xFF;
+ if( ($level >= 0) && ($level < $grad_size) ){
+ imagesetpixel($im, $x, $y, $grad_rgba[imagecolorat($im, $x, $y) & 0xFF]);
+ }
+ }
+ }
+ if(self::$WITH_TRANSPARENCY == $mode){
+ imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+ }
+ return $im;
+ }//createImage
+
+ //Heat an image
+ public static function heatImage($filepath, $gradient_name = 'classic', $mode= 0, $min_level=0, $max_level=255, $gradient_interpolate=0, $keep_value=0){
+ $_gradient_name = $gradient_name;
+ if(($_gradient_name != self::$GRADIENT_CLASSIC) && ($_gradient_name != self::$GRADIENT_FIRE) && ($_gradient_name != self::$GRADIENT_PGAITCH)){
+ $_gradient_name = self::$GRADIENT_CLASSIC;
+ }
+ $_min_level = min(255, max(0, intval($min_level)));
+ $_max_level = min(255, max(0, intval($max_level)));
+
+ //try opening jpg first then png then gif format
+ if(FALSE === ($im = @imagecreatefromjpeg($filepath))){
+ if(FALSE === ($im = @imagecreatefrompng($filepath))){
+ if(FALSE === ($im = @imagecreatefromgif($filepath))){
+ return FALSE;
+ }
+ }
+ }
+ if(self::$WITH_ALPHA == $mode){
+ imagealphablending($im, false);
+ imagesavealpha($im,true);
+ }
+ $width = imagesx($im);
+ $height = imagesy($im);
+ if(FALSE === ($grad_rgba = self::_createGradient($im, $mode, $_gradient_name))){
+ return FALSE;
+ }
+ //Convert to grayscale
+ $grad_size = count($grad_rgba);
+ $level_range = $_max_level - $_min_level;
+ for($x=0; $x <$width; ++$x){
+ for($y=0; $y <$height; ++$y){
+ $rgb = imagecolorat($im, $x, $y);
+ $r = ($rgb >> 16) & 0xFF;
+ $g = ($rgb >> 8) & 0xFF;
+ $b = $rgb & 0xFF;
+ $gray_level = Min(255, Max(0, floor(0.33 * $r + 0.5 * $g + 0.16 * $b)));//between 0 and 255
+ if( ($gray_level >= $_min_level) && ($gray_level <= $_max_level) ){
+ switch($gradient_interpolate){
+ case self::$GRADIENT_NO_NEGATE_NO_INTERPOLATE:
+ //$_max_level takes related lowest gradient color
+ //$_min_level takes related highest gradient color
+ $value = 255 - $gray_level;
+ break;
+ case self::$GRADIENT_NEGATE_NO_INTERPOLATE:
+ //$_max_level takes related highest gradient color
+ //$_min_level takes related lowest gradient color
+ $value = $gray_level;
+ break;
+ case self::$GRADIENT_NO_NEGATE_INTERPOLATE:
+ //$_max_level takes lowest gradient color
+ //$_min_level takes highest gradient color
+ $value = 255- floor(($gray_level - $_min_level) * $grad_size / $level_range);
+ break;
+ case self::$GRADIENT_NEGATE_INTERPOLATE:
+ //$_max_level takes highest gradient color
+ //$_min_level takes lowest gradient color
+ $value = floor(($gray_level - $_min_level) * $grad_size / $level_range);
+ break;
+ default:
+ }
+ imagesetpixel($im, $x, $y, $grad_rgba[$value]);
+ }else{
+ if(self::$KEEP_VALUE == $keep_value){
+ //Do nothing
+ }else{//self::$NO_KEEP_VALUE
+ imagesetpixel($im, $x, $y, imagecolorallocatealpha($im,0,0,0,0));
+ }
+ }
+ }
+ }
+ if(self::$WITH_TRANSPARENCY == $mode){
+ imagecolortransparent($im, $grad_rgba[count($grad_rgba)-1]);
+ }
+ return $im;
+ }//heatImage
+
+ private static function _drawCircularGradient(&$im, $center_x, $center_y, $spot_radius, $dimming){
+ $dirty = array();
+ $ratio = (255 - $dimming) / $spot_radius;
+ for($r=$spot_radius; $r > 0; --$r){
+ $channel = $dimming + $r * $ratio;
+ $angle_step = 0.45/$r; //0.01;
+ //Process pixel by pixel to draw a radial grayscale radient
+ for($angle=0; $angle <= PI2; $angle += $angle_step){
+ $x = floor($center_x + $r*cos($angle));
+ $y = floor($center_y + $r*sin($angle));
+ if(!isset($dirty[$x][$y])){
+ $previous_channel = @imagecolorat($im, $x, $y) & 0xFF;//grayscale so same value
+ $new_channel = Max(0, Min(255,($previous_channel * $channel)/255));
+ imagesetpixel($im, $x, $y, imagecolorallocate($im, $new_channel, $new_channel, $new_channel));
+ $dirty[$x][$y] = 0;
+ }
+ }
+ }
+ }//_drawCircularGradient
+
+ private static function _drawBilinearGradient(&$im, $point0, $point1, $spot_radius, $dimming){
+ if($point0->x < $point1->x){
+ $x0 = $point0->x;
+ $y0 = $point0->y;
+ $x1 = $point1->x;
+ $y1 = $point1->y;
+ }else{
+ $x0 = $point1->x;
+ $y0 = $point1->y;
+ $x1 = $point0->x;
+ $y1 = $point0->y;
+ }
+
+ if( ($x0==$x1) && ($y0==$y1)){//check if same coordinates
+ return false;
+ }
+ $steep = (abs($y1 - $y0) > abs($x1 - $x0))? true: false;
+ if($steep){
+ list($x0, $y0) = array($y0, $x0);//swap
+ list($x1, $y1) = array($y1, $x1);//swap
+ }
+ if($x0>$x1){
+ list($x0, $x1) = array($x1, $x0);//swap
+ list($y0, $y1) = array($y1, $y0);//swap
+ }
+ $deltax = $x1 - $x0;
+ $deltay = abs($y1 - $y0);
+ $error = $deltax / 2;
+ $y = $y0;
+ if( $y0 < $y1){
+ $ystep = 1;
+ }else{
+ $ystep = -1;
+ }
+ $step = max(1, floor($spot_radius/ 3));
+ for($x=$x0; $x<=$x1; ++$x){//Loop through x value
+ if(0==(($x-$x0) % $step)){
+ if($steep){
+ self::_drawCircularGradient(&$im, $y, $x, $spot_radius, $dimming);
+ }else{
+ self::_drawCircularGradient(&$im, $x, $y, $spot_radius, $dimming);
+ }
+ }
+ $error -= $deltay;
+ if($error<0){
+ $y = $y + $ystep;
+ $error = $error + $deltax;
+ }
+ }
+ }//_drawBilinearGradient
+
+ private static function _createGradient($im, $mode, $gradient_name){
+ //create the gradient from an image
+ if(FALSE === ($grad_im = imagecreatefrompng('gradient/'.$gradient_name.'.png'))){
+ return FALSE;
+ }
+ $width_g = imagesx($grad_im);
+ $height_g = imagesy($grad_im);
+ //Get colors along the longest dimension
+ //Max density is for lower channel value
+ for($y=$height_g-1; $y >= 0 ; --$y){
+ $rgb = imagecolorat($grad_im, 1, $y);
+ //Linear function
+ $alpha = Min(127, Max(0, floor(127 - $y/2)));
+ if(self::$WITH_ALPHA == $mode){
+ $grad_rgba[] = imagecolorallocatealpha($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF, $alpha);
+ }else{
+ $grad_rgba[] = imagecolorallocate($im, ($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF);
+ }
+ }
+ imagedestroy($grad_im);
+ unset($grad_im);
+ return($grad_rgba);
+ }//_createGradient
+ }//Heatmap
+?>
--- /dev/null
+++ b/lib/rolling-curl/.svn/all-wcprops
@@ -1,1 +1,42 @@
+K 25
+svn:wc:ra_dav:version-url
+V 22
+/svn/!svn/ver/20/trunk
+END
+RollingCurlGroup.php
+K 25
+svn:wc:ra_dav:version-url
+V 43
+/svn/!svn/ver/20/trunk/RollingCurlGroup.php
+END
+example_groups.php
+K 25
+svn:wc:ra_dav:version-url
+V 41
+/svn/!svn/ver/20/trunk/example_groups.php
+END
+example.php
+K 25
+svn:wc:ra_dav:version-url
+V 34
+/svn/!svn/ver/20/trunk/example.php
+END
+RollingCurl.php
+K 25
+svn:wc:ra_dav:version-url
+V 38
+/svn/!svn/ver/20/trunk/RollingCurl.php
+END
+CHANGELOG.txt
+K 25
+svn:wc:ra_dav:version-url
+V 36
+/svn/!svn/ver/20/trunk/CHANGELOG.txt
+END
+README.txt
+K 25
+svn:wc:ra_dav:version-url
+V 33
+/svn/!svn/ver/20/trunk/README.txt
+END
--- /dev/null
+++ b/lib/rolling-curl/.svn/entries
@@ -1,1 +1,233 @@
-
+10
+
+dir
+20
+http://rolling-curl.googlecode.com/svn/trunk
+http://rolling-curl.googlecode.com/svn
+
+
+
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+74aa2acc-2e27-11de-b2a4-4f96ceaaac44
+
+RollingCurlGroup.php
+file
+
+
+
+
+2011-04-10T08:32:48.081650Z
+73c08d9e9e24b4adc89816624c7aca30
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+5152
+
+example_groups.php
+file
+
+
+
+
+2011-04-10T08:32:48.082650Z
+907ed82a47d346c39acbd5578e1d0230
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1367
+
+example.php
+file
+
+
+
+
+2011-04-10T08:32:48.083650Z
+87aa845abfaffc09ed4eca024f2a8b8a
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1860
+
+RollingCurl.php
+file
+
+
+
+
+2011-04-10T08:32:48.084650Z
+205391c449f3f3ee050004dadc374dc8
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+10444
+
+CHANGELOG.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+d0452f6f9530ed04580159121d0fd5f7
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+has-props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+662
+
+README.txt
+file
+
+
+
+
+2011-04-10T08:32:48.085650Z
+60dd357081431c0f2b82989cdbce8615
+2010-09-12T20:39:22.711474Z
+20
+alexander.makarow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6355
+
+
--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
--- /dev/null
+++ b/lib/rolling-curl/.svn/prop-base/example_groups.php.svn-base
@@ -1,1 +1,6 @@
+K 13
+svn:eol-style
+V 6
+native
+END
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/CHANGELOG.txt.svn-base
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/README.txt.svn-base
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+ // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+ "http://www.facebook.com",
+ "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+ // parse the page title out of the returned HTML
+ if (preg_match("~(.*?) ~i", $response, $out)) {
+ $title = $out[1];
+ }
+ echo "$title ";
+ print_r($info);
+ echo " ";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+ // add each request to the RollingCurl object
+ $request = new RollingCurlRequest($url);
+ $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+ private $rc;
+
+ function __construct(){
+ $this->rc = new RollingCurl(array($this, 'processPage'));
+ }
+
+ function processPage($response, $info, $request){
+ //...
+ }
+
+ function run($urls){
+ foreach ($urls as $url){
+ $request = new RollingCurlRequest($url);
+ $this->rc->add($request);
+ }
+ $this->rc->execute();
+ }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+ 'http://google.com/',
+ 'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+ public $test_verbose = true;
+
+ function process($output, $info) {
+ echo "Processing " . $this->url . "\n";
+ if ($this->test_verbose)
+ print_r($info);
+
+ parent::process($output, $info);
+ }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+ function process($output, $info, $request) {
+ echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+ parent::process($output, $info, $request);
+ }
+
+ function finished() {
+ echo "Group CB: Finished" . $this->name . "\n";
+ parent::finished();
+ }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurl.php.svn-base
@@ -1,1 +1,375 @@
-
+url = $url;
+ $this->method = $method;
+ $this->post_data = $post_data;
+ $this->headers = $headers;
+ $this->options = $options;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+ }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+ /**
+ * @var int
+ *
+ * Window size is the max number of simultaneous connections allowed.
+ *
+ * REMEMBER TO RESPECT THE SERVERS:
+ * Sending too many requests at one time can easily be perceived
+ * as a DOS attack. Increase this window_size if you are making requests
+ * to multiple servers or have permission from the receving server admins.
+ */
+ private $window_size = 5;
+
+ /**
+ * @var float
+ *
+ * Timeout is the timeout used for curl_multi_select.
+ */
+ private $timeout = 10;
+
+ /**
+ * @var string|array
+ *
+ * Callback function to be applied to each result.
+ */
+ private $callback;
+
+ /**
+ * @var array
+ *
+ * Set your base options that you want to be used with EVERY request.
+ */
+ protected $options = array(
+ CURLOPT_SSL_VERIFYPEER => 0,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_CONNECTTIMEOUT => 30,
+ CURLOPT_TIMEOUT => 30
+ );
+
+ /**
+ * @var array
+ */
+ private $headers = array();
+
+ /**
+ * @var Request[]
+ *
+ * The request queue
+ */
+ private $requests = array();
+
+ /**
+ * @var RequestMap[]
+ *
+ * Maps handles to request indexes
+ */
+ private $requestMap = array();
+
+ /**
+ * @param $callback
+ * Callback function to be applied to each result.
+ *
+ * Can be specified as 'my_callback_function'
+ * or array($object, 'my_callback_method').
+ *
+ * Function should take three parameters: $response, $info, $request.
+ * $response is response body, $info is additional curl info.
+ * $request is the original request
+ *
+ * @return void
+ */
+ function __construct($callback = null) {
+ $this->callback = $callback;
+ }
+
+ /**
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return (isset($this->{$name})) ? $this->{$name} : null;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @return bool
+ */
+ public function __set($name, $value) {
+ // append the base options & headers
+ if ($name == "options" || $name == "headers") {
+ $this->{$name} = $value + $this->{$name};
+ } else {
+ $this->{$name} = $value;
+ }
+ return true;
+ }
+
+ /**
+ * Add a request to the request queue
+ *
+ * @param Request $request
+ * @return bool
+ */
+ public function add($request) {
+ $this->requests[] = $request;
+ return true;
+ }
+
+ /**
+ * Create new Request and add it to the request queue
+ *
+ * @param string $url
+ * @param string $method
+ * @param $post_data
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+ $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+ return true;
+ }
+
+ /**
+ * Perform GET request
+ *
+ * @param string $url
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function get($url, $headers = null, $options = null) {
+ return $this->request($url, "GET", null, $headers, $options);
+ }
+
+ /**
+ * Perform POST request
+ *
+ * @param string $url
+ * @param $post_data
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function post($url, $post_data = null, $headers = null, $options = null) {
+ return $this->request($url, "POST", $post_data, $headers, $options);
+ }
+
+ /**
+ * Execute processing
+ *
+ * @param int $window_size Max number of simultaneous connections
+ * @return string|bool
+ */
+ public function execute($window_size = null) {
+ // rolling curl window must always be greater than 1
+ if (sizeof($this->requests) == 1) {
+ return $this->single_curl();
+ } else {
+ // start the rolling curl. window_size is the max number of simultaneous connections
+ return $this->rolling_curl($window_size);
+ }
+ }
+
+ /**
+ * Performs a single curl request
+ *
+ * @access private
+ * @return string
+ */
+ private function single_curl() {
+ $ch = curl_init();
+ $request = array_shift($this->requests);
+ $options = $this->get_options($request);
+ curl_setopt_array($ch, $options);
+ $output = curl_exec($ch);
+ $info = curl_getinfo($ch);
+
+ // it's not neccesary to set a callback for one-off requests
+ if ($this->callback) {
+ $callback = $this->callback;
+ if (is_callable($this->callback)) {
+ call_user_func($callback, $output, $info, $request);
+ }
+ }
+ else
+ return $output;
+ return true;
+ }
+
+ /**
+ * Performs multiple curl requests
+ *
+ * @access private
+ * @throws RollingCurlException
+ * @param int $window_size Max number of simultaneous connections
+ * @return bool
+ */
+ private function rolling_curl($window_size = null) {
+ if ($window_size)
+ $this->window_size = $window_size;
+
+ // make sure the rolling window isn't greater than the # of urls
+ if (sizeof($this->requests) < $this->window_size)
+ $this->window_size = sizeof($this->requests);
+
+ if ($this->window_size < 2) {
+ throw new RollingCurlException("Window size must be greater than 1");
+ }
+
+ $master = curl_multi_init();
+
+ // start the first batch of requests
+ for ($i = 0; $i < $this->window_size; $i++) {
+ $ch = curl_init();
+
+ $options = $this->get_options($this->requests[$i]);
+
+ curl_setopt_array($ch, $options);
+ curl_multi_add_handle($master, $ch);
+
+ // Add to our request Maps
+ $key = (string) $ch;
+ $this->requestMap[$key] = $i;
+ }
+
+ do {
+ while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+ if ($execrun != CURLM_OK)
+ break;
+ // a request was just completed -- find out which one
+ while ($done = curl_multi_info_read($master)) {
+
+ // get the info and content returned on the request
+ $info = curl_getinfo($done['handle']);
+ $output = curl_multi_getcontent($done['handle']);
+
+ // send the return values to the callback function.
+ $callback = $this->callback;
+ if (is_callable($callback)) {
+ $key = (string) $done['handle'];
+ $request = $this->requests[$this->requestMap[$key]];
+ unset($this->requestMap[$key]);
+ call_user_func($callback, $output, $info, $request);
+ }
+
+ // start a new request (it's important to do this before removing the old one)
+ if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+ $ch = curl_init();
+ $options = $this->get_options($this->requests[$i]);
+ curl_setopt_array($ch, $options);
+ curl_multi_add_handle($master, $ch);
+
+ // Add to our request Maps
+ $key = (string) $ch;
+ $this->requestMap[$key] = $i;
+ $i++;
+ }
+
+ // remove the curl handle that just completed
+ curl_multi_remove_handle($master, $done['handle']);
+
+ }
+
+ // Block for data in / output; error handling is done by curl_multi_exec
+ if ($running)
+ curl_multi_select($master, $this->timeout);
+
+ } while ($running);
+ curl_multi_close($master);
+ return true;
+ }
+
+
+ /**
+ * Helper function to set up a new request by setting the appropriate options
+ *
+ * @access private
+ * @param Request $request
+ * @return array
+ */
+ private function get_options($request) {
+ // options for this entire curl object
+ $options = $this->__get('options');
+ if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+ $options[CURLOPT_FOLLOWLOCATION] = 1;
+ $options[CURLOPT_MAXREDIRS] = 5;
+ }
+ $headers = $this->__get('headers');
+
+ // append custom options for this specific request
+ if ($request->options) {
+ $options = $request->options + $options;
+ }
+
+ // set the request URL
+ $options[CURLOPT_URL] = $request->url;
+
+ // posting data w/ this request?
+ if ($request->post_data) {
+ $options[CURLOPT_POST] = 1;
+ $options[CURLOPT_POSTFIELDS] = $request->post_data;
+ }
+ if ($headers) {
+ $options[CURLOPT_HEADER] = 0;
+ $options[CURLOPT_HTTPHEADER] = $headers;
+ }
+
+ return $options;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+ }
+}
+
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/RollingCurlGroup.php.svn-base
@@ -1,1 +1,218 @@
-
+group = $group;
+ }
+
+ /**
+ * Process the request
+ *
+ *
+ */
+ function process($output, $info) {
+ if ($this->group)
+ $this->group->process($output, $info, $this);
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->group);
+ parent::__destruct();
+ }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+ /**
+ * @var string group name
+ */
+ protected $name;
+
+ /**
+ * @var int total number of requests in a group
+ */
+ protected $num_requests = 0;
+
+ /**
+ * @var int total number of finished requests in a group
+ */
+ protected $finished_requests = 0;
+
+ /**
+ * @var array requests array
+ */
+ private $requests = array();
+
+ /**
+ * @param string $name group name
+ * @return void
+ */
+ function __construct($name) {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+ }
+
+ /**
+ * Adds request to a group
+ *
+ * @throws RollingCurlGroupException
+ * @param RollingCurlGroupRequest|array $request
+ * @return bool
+ */
+ function add($request) {
+ if ($request instanceof RollingCurlGroupRequest) {
+ $request->setGroup($this);
+ $this->num_requests++;
+ $this->requests[] = $request;
+ }
+ else if (is_array($request)) {
+ foreach ($request as $req)
+ $this->add($req);
+ }
+ else
+ throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+ return true;
+ }
+
+ /**
+ * @throws RollingCurlGroupException
+ * @param RollingCurl $rc
+ * @return bool
+ */
+ function addToRC(RollingCurl $rc){
+ $ret = true;
+
+ while (count($this->requests) > 0){
+ $ret1 = $rc->add(array_shift($this->requests));
+ if (!$ret1)
+ $ret = false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Override to implement custom response processing.
+ *
+ * Don't forget to call parent::process().
+ *
+ * @param string $output received page body
+ * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+ * @param RollingCurlRequest $request request used
+ * @return void
+ */
+ function process($output, $info, $request) {
+ $this->finished_requests++;
+
+ if ($this->finished_requests >= $this->num_requests)
+ $this->finished();
+ }
+
+ /**
+ * Override to execute code after all requests in a group are processed.
+ *
+ * @return void
+ */
+ function finished() {
+ }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+ /**
+ * @var mixed common callback for all groups
+ */
+ private $group_callback = null;
+
+ /**
+ * @param string $output received page body
+ * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+ * @param RollingCurlRequest $request request used
+ * @return void
+ */
+ protected function process($output, $info, $request) {
+ if ($request instanceof RollingCurlGroupRequest)
+ $request->process($output, $info);
+
+ if (is_callable($this->group_callback))
+ call_user_func($this->group_callback, $output, $info, $request);
+ }
+
+ /**
+ * @param mixed $callback common callback for all groups
+ * @return void
+ */
+ function __construct($callback = null) {
+ $this->group_callback = $callback;
+
+ parent::__construct(array(&$this, "process"));
+ }
+
+ /**
+ * Adds a group to processing queue
+ *
+ * @param RollingCurlGroup|Request $request
+ * @return bool
+ */
+ public function add($request) {
+ if ($request instanceof RollingCurlGroup)
+ return $request->addToRC($this);
+ else
+ return parent::add($request);
+ }
+
+ /**
+ * Execute processing
+ *
+ * @param int $window_size Max number of simultaneous connections
+ * @return bool|string
+ */
+ public function execute($window_size = null) {
+ if (count($this->requests) == 0)
+ return false;
+
+ return parent::execute($window_size);
+ }
+}
+
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example.php.svn-base
@@ -1,1 +1,66 @@
+(.*?)~i", $response, $out)) {
+ $title = $out[1];
+ }
+ echo "$title ";
+ print_r($info);
+ print_r($request);
+ echo " ";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo " ";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+ "http://www.facebook.com",
+ "http://www.yahoo.com",
+ "http://www.youtube.com",
+ "http://www.live.com",
+ "http://www.wikipedia.com",
+ "http://www.blogger.com",
+ "http://www.msn.com",
+ "http://www.baidu.com",
+ "http://www.yahoo.co.jp",
+ "http://www.myspace.com",
+ "http://www.qq.com",
+ "http://www.google.co.in",
+ "http://www.twitter.com",
+ "http://www.google.de",
+ "http://www.microsoft.com",
+ "http://www.google.cn",
+ "http://www.sina.com.cn",
+ "http://www.wordpress.com",
+ "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+ $request = new RollingCurlRequest($url);
+ $rc->add($request);
+}
+$rc->execute();
+
--- /dev/null
+++ b/lib/rolling-curl/.svn/text-base/example_groups.php.svn-base
@@ -1,1 +1,49 @@
+url . "\n";
+ if ($this->test_verbose)
+ print_r($info);
+
+ parent::process($output, $info);
+ }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+ function process($output, $info, $request) {
+ echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+ parent::process($output, $info, $request);
+ }
+
+ function finished() {
+ echo "Group CB: Finished" . $this->name . "\n";
+ parent::finished();
+ }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+ $rc->add($req);
+
+$rc->execute();
--- /dev/null
+++ b/lib/rolling-curl/CHANGELOG.txt
@@ -1,1 +1,15 @@
+Rolling Curl changelog
+======================
+September 13, 2010
+------------------
+- Bug #12, #14: Fixed default options overriding (LionsAd)
+- Bug #10: Added use of curl_multi_select to avoid burning CPU (LionsAd)
+- Enh #6, #9: Added $request as parameter to callback function (LionsAd)
+- Chg: Request renamed to RollingCurlRequest (LionsAd)
+- Added RollingCurlGroup class that allows processing groups of requests (LionsAd)
+- More cleanup at unsetting a class (LionsAd)
+- Timeout parameter for curl_multi_select is now configurable (LionsAd)
+- single_curl now returns true (LionsAd)
+- Readme corrections (Alexander Makarov)
+- Code cleanup (Alexander Makarov)
--- /dev/null
+++ b/lib/rolling-curl/README.txt
@@ -1,1 +1,210 @@
-
+Rolling Curl
+============
+
+RollingCurl allows you to process multiple HTTP requests in parallel using CURL PHP library.
+
+Released under the Apache License 2.0.
+
+Authors
+-------
+- Was originally written by [Josh Fraser](joshfraser.com).
+- Currently maintained by [Alexander Makarov](http://rmcreative.ru/).
+- Received significant updates and patched from [LionsAd](http://github.com/LionsAd/rolling-curl).
+
+Overview
+--------
+RollingCurl is a more efficient implementation of curl_multi() curl_multi is a great way to process multiple HTTP requests in parallel in PHP.
+curl_multi is particularly handy when working with large data sets (like fetching thousands of RSS feeds at one time). Unfortunately there is
+very little documentation on the best way to implement curl_multi. As a result, most of the examples around the web are either inefficient or
+fail entirely when asked to handle more than a few hundred requests.
+
+The problem is that most implementations of curl_multi wait for each set of requests to complete before processing them. If there are too many requests
+to process at once, they usually get broken into groups that are then processed one at a time. The problem with this is that each group has to wait for
+the slowest request to download. In a group of 100 requests, all it takes is one slow one to delay the processing of 99 others. The larger the number of
+requests you are dealing with, the more noticeable this latency becomes.
+
+The solution is to process each request as soon as it completes. This eliminates the wasted CPU cycles from busy waiting. Also there is a queue of
+cURL requests to allow for maximum throughput. Each time a request is completed, a new one is added from the queue. By dynamically adding and removing
+links, we keep a constant number of links downloading at all times. This gives us a way to throttle the amount of simultaneous requests we are sending.
+The result is a faster and more efficient way of processing large quantities of cURL requests in parallel.
+
+Callbacks
+---------
+
+Each of requests usually do have a callback to process results that is being executed when request is done
+(both successfully or not).
+
+Callback accepts three parameters and can look like the following one:
+~~~
+[php]
+function request_callback($response, $info, $request){
+ // doing something with the data received
+}
+~~~
+
+- $response contains received page body.
+- $info is an associative array that holds various information about response such as HTTP response code, content type,
+time taken to make request etc.
+- $request contains RollingCurlRequest that was used to make request.
+
+Examples
+--------
+### Hello world
+
+~~~
+[php]
+// an array of URL's to fetch
+$urls = array("http://www.google.com",
+ "http://www.facebook.com",
+ "http://www.yahoo.com");
+
+// a function that will process the returned responses
+function request_callback($response, $info, $request) {
+ // parse the page title out of the returned HTML
+ if (preg_match("~(.*?) ~i", $response, $out)) {
+ $title = $out[1];
+ }
+ echo "$title ";
+ print_r($info);
+ echo " ";
+}
+
+// create a new RollingCurl object and pass it the name of your custom callback function
+$rc = new RollingCurl("request_callback");
+// the window size determines how many simultaneous requests to allow.
+$rc->window_size = 20;
+foreach ($urls as $url) {
+ // add each request to the RollingCurl object
+ $request = new RollingCurlRequest($url);
+ $rc->add($request);
+}
+$rc->execute();
+~~~
+
+
+### Setting custom options
+
+Set custom options for EVERY request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->execute();
+~~~
+
+Set custom options for A SINGLE request:
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$request = new RollingCurlRequest($url);
+$request->options = array(CURLOPT_HEADER => true, CURLOPT_NOBODY => true);
+$rc->add($request);
+$rc->execute();
+~~~
+
+### Shortcuts
+
+~~~
+[php]
+$rc = new RollingCurl("request_callback");
+$rc->get("http://www.google.com");
+$rc->get("http://www.yahoo.com");
+$rc->execute();
+~~~
+
+### Class callbacks
+
+~~~
+[php]
+class MyInfoCollector {
+ private $rc;
+
+ function __construct(){
+ $this->rc = new RollingCurl(array($this, 'processPage'));
+ }
+
+ function processPage($response, $info, $request){
+ //...
+ }
+
+ function run($urls){
+ foreach ($urls as $url){
+ $request = new RollingCurlRequest($url);
+ $this->rc->add($request);
+ }
+ $this->rc->execute();
+ }
+}
+
+$collector = new MyInfoCollector();
+$collector->run(array(
+ 'http://google.com/',
+ 'http://yahoo.com/'
+));
+~~~
+
+### Using RollingCurlGroup
+
+~~~
+[php]
+class TestCurlRequest extends RollingCurlGroupRequest {
+ public $test_verbose = true;
+
+ function process($output, $info) {
+ echo "Processing " . $this->url . "\n";
+ if ($this->test_verbose)
+ print_r($info);
+
+ parent::process($output, $info);
+ }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+ function process($output, $info, $request) {
+ echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+ parent::process($output, $info, $request);
+ }
+
+ function finished() {
+ echo "Group CB: Finished" . $this->name . "\n";
+ parent::finished();
+ }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+$rc->add($req);
+
+$rc->execute();
+~~~
+
+The same function (add) can be used both for adding requests and groups of requests.
+The "callback" in request and groups is:
+
+process($output, $info)
+
+and
+
+process($output, $info, $request)
+
+Also you can override RollingCurlGroup::finished() that will be executed right after finishing group processing.
+
+$Id$
--- /dev/null
+++ b/lib/rolling-curl/RollingCurl.php
@@ -1,1 +1,376 @@
-
+url = $url;
+ $this->method = $method;
+ $this->post_data = $post_data;
+ $this->headers = $headers;
+ $this->options = $options;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->url, $this->method, $this->post_data, $this->headers, $this->options);
+ }
+}
+
+/**
+ * RollingCurl custom exception
+ */
+class RollingCurlException extends Exception {
+}
+
+/**
+ * Class that holds a rolling queue of curl requests.
+ *
+ * @throws RollingCurlException
+ */
+class RollingCurl {
+ /**
+ * @var int
+ *
+ * Window size is the max number of simultaneous connections allowed.
+ *
+ * REMEMBER TO RESPECT THE SERVERS:
+ * Sending too many requests at one time can easily be perceived
+ * as a DOS attack. Increase this window_size if you are making requests
+ * to multiple servers or have permission from the receving server admins.
+ */
+ private $window_size = 5;
+
+ /**
+ * @var float
+ *
+ * Timeout is the timeout used for curl_multi_select.
+ */
+ private $timeout = 10;
+
+ /**
+ * @var string|array
+ *
+ * Callback function to be applied to each result.
+ */
+ private $callback;
+
+ /**
+ * @var array
+ *
+ * Set your base options that you want to be used with EVERY request.
+ */
+ protected $options = array(
+ CURLOPT_SSL_VERIFYPEER => 0,
+ CURLOPT_RETURNTRANSFER => 1,
+ CURLOPT_CONNECTTIMEOUT => 60,
+ CURLOPT_TIMEOUT => 60
+ );
+
+ /**
+ * @var array
+ */
+ private $headers = array();
+
+ /**
+ * @var Request[]
+ *
+ * The request queue
+ */
+ private $requests = array();
+
+ /**
+ * @var RequestMap[]
+ *
+ * Maps handles to request indexes
+ */
+ private $requestMap = array();
+
+ /**
+ * @param $callback
+ * Callback function to be applied to each result.
+ *
+ * Can be specified as 'my_callback_function'
+ * or array($object, 'my_callback_method').
+ *
+ * Function should take three parameters: $response, $info, $request.
+ * $response is response body, $info is additional curl info.
+ * $request is the original request
+ *
+ * @return void
+ */
+ function __construct($callback = null) {
+ $this->callback = $callback;
+ }
+
+ /**
+ * @param string $name
+ * @return mixed
+ */
+ public function __get($name) {
+ return (isset($this->{$name})) ? $this->{$name} : null;
+ }
+
+ /**
+ * @param string $name
+ * @param mixed $value
+ * @return bool
+ */
+ public function __set($name, $value) {
+ // append the base options & headers
+ if ($name == "options" || $name == "headers") {
+ $this->{$name} = $value + $this->{$name};
+ } else {
+ $this->{$name} = $value;
+ }
+ return true;
+ }
+
+ /**
+ * Add a request to the request queue
+ *
+ * @param Request $request
+ * @return bool
+ */
+ public function add($request) {
+ $this->requests[] = $request;
+ return true;
+ }
+
+ /**
+ * Create new Request and add it to the request queue
+ *
+ * @param string $url
+ * @param string $method
+ * @param $post_data
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function request($url, $method = "GET", $post_data = null, $headers = null, $options = null) {
+ $this->requests[] = new RollingCurlRequest($url, $method, $post_data, $headers, $options);
+ return true;
+ }
+
+ /**
+ * Perform GET request
+ *
+ * @param string $url
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function get($url, $headers = null, $options = null) {
+ return $this->request($url, "GET", null, $headers, $options);
+ }
+
+ /**
+ * Perform POST request
+ *
+ * @param string $url
+ * @param $post_data
+ * @param $headers
+ * @param $options
+ * @return bool
+ */
+ public function post($url, $post_data = null, $headers = null, $options = null) {
+ return $this->request($url, "POST", $post_data, $headers, $options);
+ }
+
+ /**
+ * Execute processing
+ *
+ * @param int $window_size Max number of simultaneous connections
+ * @return string|bool
+ */
+ public function execute($window_size = null) {
+ // rolling curl window must always be greater than 1
+ if (sizeof($this->requests) == 1) {
+ return $this->single_curl();
+ } else {
+ // start the rolling curl. window_size is the max number of simultaneous connections
+ return $this->rolling_curl($window_size);
+ }
+ }
+
+ /**
+ * Performs a single curl request
+ *
+ * @access private
+ * @return string
+ */
+ private function single_curl() {
+ $ch = curl_init();
+ $request = array_shift($this->requests);
+ $options = $this->get_options($request);
+ curl_setopt_array($ch, $options);
+ $output = curl_exec($ch);
+ $info = curl_getinfo($ch);
+
+ // it's not neccesary to set a callback for one-off requests
+ if ($this->callback) {
+ $callback = $this->callback;
+ if (is_callable($this->callback)) {
+ call_user_func($callback, $output, $info, $request);
+ }
+ }
+ else
+ return $output;
+ return true;
+ }
+
+ /**
+ * Performs multiple curl requests
+ *
+ * @access private
+ * @throws RollingCurlException
+ * @param int $window_size Max number of simultaneous connections
+ * @return bool
+ */
+ private function rolling_curl($window_size = null) {
+ if ($window_size)
+ $this->window_size = $window_size;
+
+ // make sure the rolling window isn't greater than the # of urls
+ if (sizeof($this->requests) < $this->window_size)
+ $this->window_size = sizeof($this->requests);
+
+ if ($this->window_size < 2) {
+ throw new RollingCurlException("Window size must be greater than 1");
+ }
+
+ $master = curl_multi_init();
+
+ // start the first batch of requests
+ for ($i = 0; $i < $this->window_size; $i++) {
+ $ch = curl_init();
+
+ $options = $this->get_options($this->requests[$i]);
+
+ curl_setopt_array($ch, $options);
+ curl_multi_add_handle($master, $ch);
+
+ // Add to our request Maps
+ $key = (string) $ch;
+ $this->requestMap[$key] = $i;
+ }
+
+ do {
+ while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
+ if ($execrun != CURLM_OK)
+ break;
+ // a request was just completed -- find out which one
+ while ($done = curl_multi_info_read($master)) {
+
+ // get the info and content returned on the request
+ $info = curl_getinfo($done['handle']);
+ $output = curl_multi_getcontent($done['handle']);
+
+ // send the return values to the callback function.
+ $callback = $this->callback;
+ if (is_callable($callback)) {
+ $key = (string) $done['handle'];
+ $request = $this->requests[$this->requestMap[$key]];
+ unset($this->requestMap[$key]);
+ call_user_func($callback, $output, $info, $request);
+ }
+
+ // start a new request (it's important to do this before removing the old one)
+ if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) {
+ $ch = curl_init();
+ $options = $this->get_options($this->requests[$i]);
+ curl_setopt_array($ch, $options);
+ curl_multi_add_handle($master, $ch);
+
+ // Add to our request Maps
+ $key = (string) $ch;
+ $this->requestMap[$key] = $i;
+ $i++;
+ }
+
+ // remove the curl handle that just completed
+ curl_multi_remove_handle($master, $done['handle']);
+
+ }
+
+ // Block for data in / output; error handling is done by curl_multi_exec
+ if ($running)
+ curl_multi_select($master, $this->timeout);
+
+ } while ($running);
+ curl_multi_close($master);
+ return true;
+ }
+
+
+ /**
+ * Helper function to set up a new request by setting the appropriate options
+ *
+ * @access private
+ * @param Request $request
+ * @return array
+ */
+ private function get_options($request) {
+ // options for this entire curl object
+ $options = $this->__get('options');
+ if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) {
+ $options[CURLOPT_FOLLOWLOCATION] = 1;
+ $options[CURLOPT_MAXREDIRS] = 5;
+ }
+ $headers = $this->__get('headers');
+
+ // append custom options for this specific request
+ if ($request->options) {
+ $options = $request->options + $options;
+ }
+
+ // set the request URL
+ $options[CURLOPT_URL] = $request->url;
+
+ // posting data w/ this request?
+ if ($request->post_data) {
+ $options[CURLOPT_POST] = 1;
+ $options[CURLOPT_POSTFIELDS] = $request->post_data;
+ }
+ if ($headers) {
+ $options[CURLOPT_HEADER] = 0;
+ $options[CURLOPT_HTTPHEADER] = $headers;
+ }
+
+ return $options;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests);
+ }
+}
+
--- /dev/null
+++ b/lib/rolling-curl/RollingCurlGroup.php
@@ -1,1 +1,218 @@
-
+group = $group;
+ }
+
+ /**
+ * Process the request
+ *
+ *
+ */
+ function process($output, $info) {
+ if ($this->group)
+ $this->group->process($output, $info, $this);
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->group);
+ parent::__destruct();
+ }
+
+}
+
+/**
+ * A group of curl requests.
+ *
+ * @throws RollingCurlGroupException *
+ */
+class RollingCurlGroup {
+ /**
+ * @var string group name
+ */
+ protected $name;
+
+ /**
+ * @var int total number of requests in a group
+ */
+ protected $num_requests = 0;
+
+ /**
+ * @var int total number of finished requests in a group
+ */
+ protected $finished_requests = 0;
+
+ /**
+ * @var array requests array
+ */
+ private $requests = array();
+
+ /**
+ * @param string $name group name
+ * @return void
+ */
+ function __construct($name) {
+ $this->name = $name;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct() {
+ unset($this->name, $this->num_requests, $this->finished_requests, $this->requests);
+ }
+
+ /**
+ * Adds request to a group
+ *
+ * @throws RollingCurlGroupException
+ * @param RollingCurlGroupRequest|array $request
+ * @return bool
+ */
+ function add($request) {
+ if ($request instanceof RollingCurlGroupRequest) {
+ $request->setGroup($this);
+ $this->num_requests++;
+ $this->requests[] = $request;
+ }
+ else if (is_array($request)) {
+ foreach ($request as $req)
+ $this->add($req);
+ }
+ else
+ throw new RollingCurlGroupException("add: Request needs to be of instance RollingCurlGroupRequest");
+
+ return true;
+ }
+
+ /**
+ * @throws RollingCurlGroupException
+ * @param RollingCurl $rc
+ * @return bool
+ */
+ function addToRC(RollingCurl $rc){
+ $ret = true;
+
+ while (count($this->requests) > 0){
+ $ret1 = $rc->add(array_shift($this->requests));
+ if (!$ret1)
+ $ret = false;
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Override to implement custom response processing.
+ *
+ * Don't forget to call parent::process().
+ *
+ * @param string $output received page body
+ * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+ * @param RollingCurlRequest $request request used
+ * @return void
+ */
+ function process($output, $info, $request) {
+ $this->finished_requests++;
+
+ if ($this->finished_requests >= $this->num_requests)
+ $this->finished();
+ }
+
+ /**
+ * Override to execute code after all requests in a group are processed.
+ *
+ * @return void
+ */
+ function finished() {
+ }
+
+}
+
+/**
+ * Group version of rolling curl
+ */
+class GroupRollingCurl extends RollingCurl {
+
+ /**
+ * @var mixed common callback for all groups
+ */
+ private $group_callback = null;
+
+ /**
+ * @param string $output received page body
+ * @param array $info holds various information about response such as HTTP response code, content type, time taken to make request etc.
+ * @param RollingCurlRequest $request request used
+ * @return void
+ */
+ protected function process($output, $info, $request) {
+ if ($request instanceof RollingCurlGroupRequest)
+ $request->process($output, $info);
+
+ if (is_callable($this->group_callback))
+ call_user_func($this->group_callback, $output, $info, $request);
+ }
+
+ /**
+ * @param mixed $callback common callback for all groups
+ * @return void
+ */
+ function __construct($callback = null) {
+ $this->group_callback = $callback;
+
+ parent::__construct(array(&$this, "process"));
+ }
+
+ /**
+ * Adds a group to processing queue
+ *
+ * @param RollingCurlGroup|Request $request
+ * @return bool
+ */
+ public function add($request) {
+ if ($request instanceof RollingCurlGroup)
+ return $request->addToRC($this);
+ else
+ return parent::add($request);
+ }
+
+ /**
+ * Execute processing
+ *
+ * @param int $window_size Max number of simultaneous connections
+ * @return bool|string
+ */
+ public function execute($window_size = null) {
+ if (count($this->requests) == 0)
+ return false;
+
+ return parent::execute($window_size);
+ }
+}
+
--- /dev/null
+++ b/lib/rolling-curl/example.php
@@ -1,1 +1,66 @@
+(.*?)~i", $response, $out)) {
+ $title = $out[1];
+ }
+ echo "$title ";
+ print_r($info);
+ print_r($request);
+ echo " ";
+}
+
+// single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.msn.com");
+$rc->execute();
+
+// another single curl request
+$rc = new RollingCurl("request_callback");
+$rc->request("http://www.google.com");
+$rc->execute();
+
+echo " ";
+
+// top 20 sites according to alexa (11/5/09)
+$urls = array("http://www.google.com",
+ "http://www.facebook.com",
+ "http://www.yahoo.com",
+ "http://www.youtube.com",
+ "http://www.live.com",
+ "http://www.wikipedia.com",
+ "http://www.blogger.com",
+ "http://www.msn.com",
+ "http://www.baidu.com",
+ "http://www.yahoo.co.jp",
+ "http://www.myspace.com",
+ "http://www.qq.com",
+ "http://www.google.co.in",
+ "http://www.twitter.com",
+ "http://www.google.de",
+ "http://www.microsoft.com",
+ "http://www.google.cn",
+ "http://www.sina.com.cn",
+ "http://www.wordpress.com",
+ "http://www.google.co.uk");
+
+$rc = new RollingCurl("request_callback");
+$rc->window_size = 20;
+foreach ($urls as $url) {
+ $request = new RollingCurlRequest($url);
+ $rc->add($request);
+}
+$rc->execute();
+
--- /dev/null
+++ b/lib/rolling-curl/example_groups.php
@@ -1,1 +1,49 @@
+url . "\n";
+ if ($this->test_verbose)
+ print_r($info);
+
+ parent::process($output, $info);
+ }
+}
+
+class TestCurlGroup extends RollingCurlGroup {
+ function process($output, $info, $request) {
+ echo "Group CB: Progress " . $this->name . " (" . ($this->finished_requests + 1) . "/" . $this->num_requests . ")\n";
+ parent::process($output, $info, $request);
+ }
+
+ function finished() {
+ echo "Group CB: Finished" . $this->name . "\n";
+ parent::finished();
+ }
+}
+
+$group = new TestCurlGroup("High");
+$group->add(new TestCurlRequest("www.google.de"));
+$group->add(new TestCurlRequest("www.yahoo.de"));
+$group->add(new TestCurlRequest("www.newyorktimes.com"));
+$reqs[] = $group;
+
+$group = new TestCurlGroup("Normal");
+$group->add(new TestCurlRequest("twitter.com"));
+$group->add(new TestCurlRequest("www.bing.com"));
+$group->add(new TestCurlRequest("m.facebook.com"));
+$reqs[] = $group;
+
+$reqs[] = new TestCurlRequest("www.kernel.org");
+
+// No callback here, as its done in Request class
+$rc = new GroupRollingCurl();
+
+foreach ($reqs as $req)
+ $rc->add($req);
+
+$rc->execute();
--- a/postinstall
+++ b/postinstall
@@ -1,32 +1,17 @@
#!/bin/bash
-#this script should be run from a fresh git checkout from github
-#ami base must have yum install lighttpd-fastcgi, git, tomcat6
-#screen php-cli php-gd tomcat6-webapps tomcat6-admin-webapps svn maven2
-#http://www.how2forge.org/installing-lighttpd-with-php5-and-mysql-support-on-fedora-12
+#dotcloud postinstall
-cp /root/aws.php /tmp/
-mkdir /var/www/lib/staticmaplite/cache
-chcon -h system_u:object_r:httpd_sys_content_t /var/www
-chcon -R -h root:object_r:httpd_sys_content_t /var/www/*
-chcon -R -t httpd_sys_content_rw_t /var/www/lib/staticmaplite/cache
-chmod -R 777 /var/www/lib/staticmaplite/cache
-wget http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
--O /var/www/cbrfeed.zip
+curl http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip \
+-o /home/dotcloud/current/cbrfeed.zip
-createdb transitdata
-#made with pg_dump transitdata | gzip -c > transitdata.cbrfeed.sql.gz
-gunzip /var/www/transitdata.cbrfeed.sql.gz
-psql -d transitdata -f /var/www/transitdata.cbrfeed.sql
-createlang -d transitdata plpgsql
-psql -d transitdata -f /var/www/lib/postgis.sql
-php /var/www/updatedb.php
+#db setup
+#curl https://github.com/maxious/ACTBus-ui/raw/master/transitdata.cbrfeed.sql.gz -o transitdata.cbrfeed.sql.gz
+#curl https://github.com/maxious/ACTBus-ui/raw/master/lib/postgis.sql -o postgis.sql
+#createlang -d transitdata plpgsql
+#psql -d transitdata -f postgis.sql
+#gunzip /var/www/transitdata.cbrfeed.sql.gz
+#psql -d transitdata -f transitdata.cbrfeed.sql
+#createuser transitdata -SDRP
+#password transitdata
+#psql -c \"GRANT SELECT ON TABLE agency,calendar,calendar_dates,routes,stop_times,stops,trips TO transitdata;\"
-wget http://s3-ap-southeast-1.amazonaws.com/busresources/Graph.obj \
--O /tmp/Graph.obj
-rm -rfv /usr/share/tomcat6/webapps/opentripplanner*
-wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-webapp.war \
--O /usr/share/tomcat6/webapps/opentripplanner-webapp.war
-wget http://s3-ap-southeast-1.amazonaws.com/busresources/opentripplanner-api-webapp.war \
--O /usr/share/tomcat6/webapps/opentripplanner-api-webapp.war
-/etc/init.d/tomcat6 restart
-
--- a/readme.txt
+++ b/readme.txt
@@ -2,14 +2,32 @@
Based on the maxious-canberra-transit-feed @ http://s3-ap-southeast-1.amazonaws.com/busresources/cbrfeed.zip
Source code for the https://github.com/maxious/ACTBus-data transit
feed and https://github.com/maxious/ACTBus-ui this site available from github.
-Uses jQuery Mobile, PHP, Ruby, Python, Google Transit Feed Specification
-tools, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder
+Uses jQuery Mobile, PHP, PostgreSQL, OpenTripPlanner, OpenLayers, OpenStreetMap, Cloudmade Geocoder
and Tile Service
-Must have view.sh running on port 8765 for this webapp to work
+See aws/awsStartup.sh for example startup steps. You need to load the included database dump;
+for other transit networks you can use the updatedb.php script to load.
-For static maps, may have to do
+For openstreetmap static maps, may have to do
/usr/sbin/setsebool -P httpd_can_network_connect=1
-on fedora
+on Fedora and other SELinux systems.
+
+To enter a service override, you can use the psql tool. eg.
+transitdata=# COPY calendar_dates (service_id, date, exception_type) FROM stdin;
+Enter data to be copied [spaced with tabs] followed by a newline.
+End with a backslash and a period on a line by itself.
+>> saturday 20110416 2
+>> sunday 20110416 1
+>> saturday 20110423 2
+>> sunday 20110423 1
+>> weekday 20110425 2
+>> sunday 20110425 1
+>> weekday 20110422 2
+>> noservice 20110422 1
+>> weekday 20110426 2
+>> noservice 20110426 1
+>> sunday 20110424 2
+>> noservice 20110424 1
+>> \.
--- a/routeList.php
+++ b/routeList.php
@@ -19,7 +19,7 @@
echo ' ';
if (!isset($_REQUEST['firstLetter'])) {
foreach (range('A', 'Z') as $letter) {
- echo "$letter... \n";
+ echo "$letter... \n";
}
}
else {
@@ -32,62 +32,66 @@
echo ' ';
}
else if ($_REQUEST['nearby'] || $_REQUEST['suburb']) {
+ $routes = Array();
if ($_REQUEST['suburb']) {
$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
- $url = $APIurl . "/json/stopzonesearch?q=" . $suburb;
- include_header("Routes by Suburb", "routeList");
- trackEvent("Route Lists", "Routes By Suburb", $suburb);
+ include_header($suburb ." - ".ucwords(service_period()), "routeList");
+ navbar();
+ timePlaceSettings();
+ trackEvent("Route Lists", "Routes By Suburb", $suburb);
+ $routes = getRoutesBySuburb($suburb);
+
}
if ($_REQUEST['nearby']) {
- $url = $APIurl . "/json/neareststops?lat={$_SESSION['lat']}&lon={$_SESSION['lon']}&limit=15";
include_header("Routes Nearby", "routeList", true, true);
- timePlaceSettings(true);
+ trackEvent("Route Lists", "Routes Nearby", $_SESSION['lat'].",".$_SESSION['lon']);
+ navbar();
+ timePlaceSettings(true);
if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
include_footer();
die();
}
+ $routes = getRoutesNearby($_SESSION['lat'],$_SESSION['lon']);
}
- $stops = json_decode(getPage($url));
- $routes = Array();
- foreach ($stops as $stop) {
- $url = $APIurl . "/json/stoproutes?stop=" . $stop[0];
- $stoproutes = json_decode(getPage($url));
- foreach ($stoproutes as $route) {
- if (!isset($routes[$route[0]])) $routes[$route[0]] = $route;
+
+ echo ' ';
+ if ($routes) {
+ foreach ($routes as $route) {
+ echo '' . $route['route_short_name'] . " " . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")
";
+ if ($_REQUEST['nearby']) {
+ $time = getTimeInterpolatedRouteAtStop($route['route_id'], $route['stop_id']);
+ echo ''.($time['arrival_time']?$time['arrival_time']:"No more trips today")." " .floor($route['distance']) . 'm away ';
}
+ echo " \n";
}
- navbar();
- echo ' ';
- sksort($routes, 1, true);
- foreach ($routes as $row) {
- echo ''. $row[1] . " ". $row[2] . " (" . ucwords($row[4]) . ")
\n";
- }
+ } else {
+ echo " No routes nearby. ";
+ }
}
else if ($_REQUEST['bynumber'] || $_REQUEST['numberSeries']) {
include_header("Routes by Number", "routeList");
navbar();
echo ' ';
- $url = $APIurl . "/json/routes";
- $contents = json_decode(getPage($url));
- $routeSeries = Array();
- $seriesRange = Array();
- foreach ($contents as $key => $row) {
- foreach (explode(" ", $row[1]) as $routeNumber) {
- $seriesNum = substr($routeNumber, 0, -1) . "0";
- if ($seriesNum == "0") $seriesNum = $routeNumber;
- $finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
- if (isset($seriesRange[$seriesNum])) {
- if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
- if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+ if ($_REQUEST['bynumber']) {
+ $routes = getRoutesByNumber();
+ $routeSeries = Array();
+ $seriesRange = Array();
+ foreach ($routes as $key => $routeNumber) {
+ foreach (explode(" ", $routeNumber['route_short_name']) as $routeNumber) {
+ $seriesNum = substr($routeNumber, 0, -1) . "0";
+ if ($seriesNum == "0") $seriesNum = $routeNumber;
+ $finalDigit = substr($routeNumber, sizeof($routeNumber) - 1, 1);
+ if (isset($seriesRange[$seriesNum])) {
+ if ($finalDigit < $seriesRange[$seriesNum]['max']) $seriesRange[$seriesNum]['max'] = $routeNumber;
+ if ($finalDigit > $seriesRange[$seriesNum]['min']) $seriesRange[$seriesNum]['min'] = $routeNumber;
+ }
+ else {
+ $seriesRange[$seriesNum]['max'] = $routeNumber;
+ $seriesRange[$seriesNum]['min'] = $routeNumber;
+ }
+ $routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
}
- else {
- $seriesRange[$seriesNum]['max'] = $routeNumber;
- $seriesRange[$seriesNum]['min'] = $routeNumber;
- }
- $routeSeries[$seriesNum][$seriesNum . "-" . $row[1] . "-" . $row[0]] = $row;
}
- }
- if ($_REQUEST['bynumber']) {
ksort($routeSeries);
ksort($seriesRange);
foreach ($routeSeries as $series => $routes) {
@@ -98,8 +102,9 @@
}
}
else if ($_REQUEST['numberSeries']) {
- foreach ($routeSeries[$_REQUEST['numberSeries']] as $row) {
- echo ' ' . $row[1] . " ". $row[2] . " (" . ucwords($row[3]) . ")
\n";
+ $routes = getRoutesByNumber($_REQUEST['numberSeries']);
+ foreach ($routes as $route) {
+ echo ' ' . $route['route_short_name'] . " " . $route['route_long_name'] . " (" . ucwords($route['service_id']) . ")
\n";
}
}
}
@@ -107,20 +112,14 @@
include_header("Routes by Destination", "routeList");
navbar();
echo ' ';
- $url = $APIurl . "/json/routes";
- $contents = json_decode(getPage($url));
- // by destination!
- foreach ($contents as $row) {
- $routeDestinations[$row[2]][] = $row;
- }
if ($_REQUEST['routeDestination']) {
- foreach ($routeDestinations[urldecode($_REQUEST['routeDestination'])] as $row) {
- echo '' . $row[1] . ' ' . $row[2] . " (" . ucwords($row[3]) . ")
\n";
+ foreach (getRoutesByDestination(urldecode($_REQUEST['routeDestination'])) as $route) {
+ echo '' . $route["route_short_name"] . ' ' . $route["route_long_name"] . " (" . ucwords($route['service_id']) . ")
\n";
}
}
else {
- foreach ($routeDestinations as $destination => $routes) {
- echo '' . $destination . "... \n";
+ foreach (getRoutesByDestination() as $destination) {
+ echo '' . $destination['route_long_name'] . "... \n";
}
}
}
--- a/stop.php
+++ b/stop.php
@@ -33,11 +33,11 @@
// $stopNames[$key] = $sub_stop[1] . ' Stop #' . ($key + 1);
if (strpos($stop["stop_name"], "Station")) {
$stopNames[$key] = 'Platform ' . ($key + 1);
- $stopLinks.= '' . $sub_stop["stop_name"] . ' ';
+ $stopLinks.= '' . $sub_stop["stop_name"] . ' ';
}
else {
$stopNames[$key] = '#' . ($key + 1);
- $stopLinks.= '' . $sub_stop["stop_name"] . ' Stop #' . ($key + 1) . ' ';
+ $stopLinks.= '' . $sub_stop["stop_name"] . ' Stop #' . ($key + 1) . ' ';
}
$stopPositions[$key] = Array(
$sub_stop["stop_lat"],
@@ -57,27 +57,26 @@
if (!isset($allStopsTrips[$trip["trip_id"]])) $allStopsTrips[$trip["trip_id"]] = $trip;
}
}
- else {
- echo "skipped sequence $tripSequence";
- }
+ //else {
+ // echo "skipped sequence $tripSequence";
+ //}
}
}
include_header($stop['stop_name'], "stop");
timePlaceSettings();
-echo ' ';
echo $stopLinks;
if (sizeof($stops) > 0) {
trackEvent("View Stops", "View Combined Stops", $stop["stop_name"], $stop["stop_id"]);
- echo '
' . staticmap($stopPositions) . '
';
+ echo staticmap($stopPositions);
}
else {
trackEvent("View Stops", "View Single Stop", $stop["stop_name"], $stop["stop_id"]);
- echo '
' . staticmap(Array(
+ echo staticmap(Array(
0 => Array(
$stop["stop_lat"],
$stop["stop_lon"]
)
- )) . '
';
+ )) ;
}
echo '
';
+echo ' ';
include_footer();
?>
--- a/stopList.php
+++ b/stopList.php
@@ -25,7 +25,7 @@
echo ' ';
if (!isset($_REQUEST['firstLetter'])) {
foreach (range('A', 'Z') as $letter) {
- echo "$letter... \n";
+ echo "$letter... \n";
}
}
else {
@@ -48,14 +48,16 @@
}
else if ($_REQUEST['nearby']) {
$listType = 'nearby=yes';
- $stops = getNearbyStops($_SESSION['lat'],$_SESSION['lon'],15);
include_header("Nearby Stops", "stopList", true, true);
+ trackEvent("Stop Lists","Stops Nearby", $_SESSION['lat'].",".$_SESSION['lon']);
navbar();
timePlaceSettings(true);
if (!isset($_SESSION['lat']) || !isset($_SESSION['lat']) || $_SESSION['lat'] == "" || $_SESSION['lon'] == "") {
include_footer();
die();
}
+
+ $stops = getNearbyStops($_SESSION['lat'],$_SESSION['lon'],15);
}
else if ($_REQUEST['suburb']) {
$suburb = filter_var($_REQUEST['suburb'], FILTER_SANITIZE_STRING);
@@ -73,7 +75,7 @@
echo ' ';
if (!isset($_REQUEST['firstLetter']) && !$_REQUEST['suburb'] && !$_REQUEST['nearby']) {
foreach (range('A', 'Z') as $letter) {
- echo "$letter... \n";
+ echo "$letter... \n";
}
}
else {
@@ -100,7 +102,7 @@
// just a normal stop
echo '';
if (!startsWith($stop['stop_code'], "Wj")) echo ' ';
- echo '';
+ echo ' ';
if (isset($_SESSION['lat']) && isset($_SESSION['lon'])) {
echo '' . distance($stop['stop_lat'],$stop['stop_lon'], $_SESSION['lat'], $_SESSION['lon'], true) . 'm away ';
}
--- a/transitdata.sql
+++ /dev/null
@@ -1,59 +1,1 @@
---
--- PostgreSQL database dump
---
-SET statement_timeout = 0;
-SET client_encoding = 'UTF8';
-SET standard_conforming_strings = off;
-SET check_function_bodies = false;
-SET client_min_messages = warning;
-SET escape_string_warning = off;
-
-SET search_path = public, pg_catalog;
-
-SET default_tablespace = '';
-
-SET default_with_oids = false;
-
---
--- Name: trips; Type: TABLE; Schema: public; Owner: postgres; Tablespace:
---
-
-CREATE TABLE trips (
- route_id integer,
- trip_id integer NOT NULL,
- trip_headsign text,
- service_id text
-);
-
-
-ALTER TABLE public.trips OWNER TO postgres;
-
---
--- Data for Name: trips; Type: TABLE DATA; Schema: public; Owner: postgres
---
-
-COPY trips (route_id, trip_id, trip_headsign, service_id) FROM stdin;
-\.
-
-
---
--- Name: trips_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres; Tablespace:
---
-
-ALTER TABLE ONLY trips
- ADD CONSTRAINT trips_pkey PRIMARY KEY (trip_id);
-
-
---
--- Name: routetrips; Type: INDEX; Schema: public; Owner: postgres; Tablespace:
---
-
-CREATE INDEX routetrips ON trips USING btree (route_id);
-
-
---
--- PostgreSQL database dump complete
---
-
-
--- a/trip.php
+++ b/trip.php
@@ -3,95 +3,87 @@
$tripid = filter_var($_REQUEST['tripid'], FILTER_SANITIZE_NUMBER_INT);
$stopid = filter_var($_REQUEST['stopid'], FILTER_SANITIZE_NUMBER_INT);
$routeid = filter_var($_REQUEST['routeid'], FILTER_SANITIZE_NUMBER_INT);
+
$routetrips = Array();
+
if ($_REQUEST['routeid'] && !$_REQUEST['tripid']) {
- $tripid = 0;
- $url = $APIurl . "/json/routetrips?route_id=" . $routeid;
- $routetrips = json_decode(getPage($url));
- foreach ($routetrips as $trip) {
- if ($trip[2] > midnight_seconds()) {
- $tripid = $trip[1];
- break;
- }
- }
- if ($tripid == 0) $tripid = $routetrips[0][1];
+ $trip = getRouteNextTrip($routeid);
+ $tripid = $trip['trip_id'];
+} else {
+ $trip = getTrip($tripid);
+ $routeid = $trip["route_id"];
}
-$url = $APIurl . "/json/triprows?trip=" . $tripid;
-$trips = array_flatten(json_decode(getPage($url)));
-if (sizeof($routetrips) == 0) {
- $routeid = $trips[1]->route_id;
- $url = $APIurl . "/json/routetrips?route_id=" . $trips[1]->route_id;
- $routetrips = json_decode(getPage($url));
-}
-include_header("Stops on " . $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, "trip");
-trackEvent("Route/Trip View","View Route", $trips[1]->route_short_name . ' ' . $trips[1]->route_long_name, $trips[1]->route_id);
-$url = $APIurl . "/json/tripstoptimes?trip=" . $tripid;
-$json = json_decode(getPage($url));
-$stops = $json[0];
-$times = $json[1];
-$viaPoints = Array();
-foreach ($stops as $stop) {
- if (!startsWith($stop[5], "Wj")) {
- $viaPoints[] = $stop[1];
- }
-}
-echo '
Via: ' . implode(", ", $viaPoints) . '
';
-echo '
Other Trips: ';
-foreach ($routetrips as $othertrip) {
- echo '' . midnight_seconds_to_time($othertrip[0]) . ' ';
+
+$routetrips = getRouteTrips($routeid);
+
+include_header("Stops on " . $trip['route_short_name'] . ' ' . $trip['route_long_name'], "trip");
+trackEvent("Route/Trip View","View Route", $trip['route_short_name'] . ' ' . $trip['route_long_name'], $routeid);
+
+
+echo 'Via: ' . viaPointNames($tripid) . ' ';
+echo 'Other Trips: ';
+foreach (getRouteTrips($routeid) as $othertrip) {
+ echo '' . str_replace(" ",":00",str_replace(":00"," ",$othertrip['arrival_time'])). ' ';
}
flush(); @ob_flush();
-echo '
Other directions/timing periods: ';
-$url = $APIurl . "/json/routesearch?routeshortname=" . rawurlencode($trips[1]->route_short_name);
-$json = json_decode(getPage($url));
-foreach ($json as $row) {
- if ($row[0] != $routeid) echo '' . $row[2] . ' (' . ucwords($row[3]) . ') ';
+echo 'Other directions/timing periods: ';
+foreach (getRoutesByNumber($trip['route_short_name']) as $row) {
+ if ($row['route_id'] != $routeid) echo '' . $row['route_long_name'] . ' (' . ucwords($row['service_id']) . ') ';
}
flush(); @ob_flush();
echo '