Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 694 lines (632 sloc) 29.77 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
<?php
/*
*DISCLAIMER
*
*THIS SOFTWARE IS PROVIDED BY THE AUTHOR 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @author: Olivier G. <olbibigo_AT_gmail_DOT_com>
* @history:
* 1.0 creation
* 1.1 distance overlay added ('ov' parameter)
* 1.2 code refactoring, interpolation, 480 (SD) and 720 (HD) video format added ('hd' parameter)
* 1.3 iti nerary trace overlay added
* 1.4 waypoints added ('wa' parameter), video title added ('ti' parameter)
* 1.5 code refactoring, algorithm based on pano metadata added ('fp' parameter)
* 1.6 bug in interpolation fixed
* 1.7 bug fixed
* @prerequisite:
* FFMEPG software in the PATH (see http://www.ffmpeg.org/)
* GoogleMapUtility.php and [$params->font_name] in working folder
* @param:
* or string origin address (mandatory)
* de string destination address (mandatory)
* wa string waypoints separated by '|' ('' by default)
* ti string title of the video. If not set, '[or] -> [de]' string is used
* ov [true/false] true: display name + distance + iti trace overlay, false: hide (false by default)
* hd [true/false] true: HD video format, false: SD video format (SD by default)
* fp [true/false] true: frames based on SV panorama metadata (FP mode), false: frames based on iti vertices (false by default)
* in [true/false] true: interpolate vertices, false: use only route vertices (false by default)
*
* ex: get_panoramas_v2.php?or=Paris,FR&de=Orleans,FR&hd=true&ov=true
* get_panoramas_v2.php?or=48.82510,2.38580&de=48.82536,2.38583&wa=48.90123,2.35286|48.84658,2.25458&hd=true&ov=true&fp=true&ti=Boulevard Peripherique, Paris
*/
require_once('GoogleMapUtility.php');

set_time_limit(3600);//in s
ini_set('memory_limit', '256M');

$startTime = microtime();

//Video constants
define('VIDEO_FORMAT_SD', 480);//Standard definition (SD) video format in pixels
define('VIDEO_FORMAT_HD', 720);//High definition (HD) video format in pixels
define('VIDEO_RATIO', 16/9);//HD video format in pixels
//SV constants
define('SV_TILE_SIZE', 512);//Street View tile size
define('SV_DEG_PX_RATIO', 4 * SV_TILE_SIZE / 450);//At zoom level 2, 4 tiles (so 2048px) are used to give a 450° field of view
define('SV_UTURN_OFFSET', round(SV_DEG_PX_RATIO * 180));//in px
//Misc constants
define('TEMP_FOLDER', 'pics/'.time());

$header[0] = "Accept: text/xml,application/xml,application/xhtml+xml,";
$header[0] .= "text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
$header[] = "Cache-Control: max-age=0";
$header[] = "Connection: keep-alive";
$header[] = "Keep-Alive: 300";
$header[] = "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7";
$header[] = "Accept-Language: en-us,en;q=0.5";
$header[] = "Pragma: "; // browsers keep this blank.

$curl_options = array( CURLOPT_USERAGENT => "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11",
CURLOPT_HTTPHEADER => $header,
CURLOPT_REFERER => 'http://www.test.com',
CURLOPT_ENCODING => 'gzip,deflate',
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER => true);
//Store input parameters in a singleton class
class Params{
private static $instance = null;
//Direct parameters
public $origin = '';
public $destination = '';
public $waypoints = '';
public $has_overlay = false;
public $has_interpolation = false;
public $video_height = VIDEO_FORMAT_SD;
public $next_from_pano = false;

//Constants
public $max_nb_vertices = 2000;//max number of vertices in the route to process. Related to route complexity
public $video_fps = 11;//Number of frames per second in output video
public $font_name = 'digital-7.ttf';
public $font_size = 20;//Font size in point in image overlay
public $max_distance_vertices = 100;//Maximal distance in meters between vertices. Interpolation occures if current distance is bigger than that
public $overlay_size = 200;//Maximal width and height in pixels of iti trace overlay
public $sv_width_tiles = 4;//Street View level 2 panorama is made of 4 tile columns
//sv_level2_height_tiles depends on chosen video format (SD=1 tile row, HD=2 tile row)
public $modulo_fp = 2;//in FP mode, there are SV panoramas about every 20m. This factor is used to extract panoramas with bigger distances by subsampling.

//Built parameters
public $video_name = '';//depends on origin and destination
public $route_name = '';//depends on origin and destination
public $video_width = 0;//depends on chosen video format
public $sv_height_tiles = 0;//depends on chosen video format
public $overlay_margin = 0;
public $ffmpeg = '';

private function __construct() {
//Check input parameters
if(!isset($_GET['or'])){
die('Oups! Origin (or=...) parameter is not set.');
}
$this->origin = $_GET['or'];

if(!isset($_GET['de'])){
die('Oups! Destination (de=...) parameter is not set.');
}
$this->destination = $_GET['de'];

if( isset($_GET['wa']) ){
$this->waypoints = $_GET['wa'];
}

if( isset($_GET['ti']) ){
$this->video_name = str_replace(' ', '+', $_GET['ti']);
$this->route_name = $_GET['ti'];
}else{
$this->video_name = str_replace(' ', '+', $this->origin.'_'.$this->destination);
$this->route_name = $this->origin.' -> '.$this->destination;
}

$this->ffmpeg = 'ffmpeg -qscale 10 -r '.$this->video_fps.' -i '.TEMP_FOLDER.'/'.$this->video_name.'_%d.jpg '.$this->video_name.'.mp4 2>&1';
//Delete existing video file
if(file_exists($this->video_name.'.mp4')){
unlink($this->video_name.'.mp4');
}

if(isset($_GET['ov']) & (0 == strcasecmp($_GET['ov'], 'true'))){
$this->has_overlay = true;
//Check if font file is available
if(!file_exists($this->font_name)){
die($this->font_name.' font file was not found!');
}
}

if(isset($_GET['fp']) & (0 == strcasecmp($_GET['fp'], 'true'))){
$this->next_from_pano = true;
}

if(isset($_GET['in']) & (0 == strcasecmp($_GET['in'], 'true'))){
$this->has_interpolation = true;
}

if(isset($_GET['hd']) & (0 == strcasecmp($_GET['hd'], 'true'))){
$this->video_height = VIDEO_FORMAT_HD;//in px
}
$this->video_width = ceil($this->video_height * VIDEO_RATIO);//in px
$this->sv_height_tiles = ((VIDEO_FORMAT_SD == $this->video_height) ? 1 : 2);//in nb of row

$this->overlay_margin = ceil($this->overlay_size/2);

//Check if FFMPEG is available
exec('ffmpeg', $ret, $err);
if(0 == count($ret)){
//die('FFMEPG executable was not found!');
}
}
public static function getInstance() {
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}
return self::$instance;
}
public function __clone() {}
}//Params

///////////
// MAIN //
///////////
//Get input parameters
$params = Params::getInstance();

//Create temporary folder to store SV thumbnails
if(!is_dir(TEMP_FOLDER)){
mkdir(TEMP_FOLDER);
}

$ch = curl_init();
curl_setopt_array($ch, $curl_options);
//Get directions from Google
//echo 'http://maps.google.com/maps/api/directions/xml?sensor=false&origin='.urlencode($params->origin).'&destination='.urlencode($params->destination).'&waypoints='.$params->waypoints.'<br/>';exit;
curl_setopt($ch, CURLOPT_URL, 'http://maps.google.com/maps/api/directions/xml?sensor=false&origin='.urlencode($params->origin).'&destination='.urlencode($params->destination).'&waypoints='.$params->waypoints);
$doc = new DOMDocument();

global $images;
$images = array();
$xml = curl_exec($ch);
file_put_contents(TEMP_FOLDER.'/waypoints.xml', $xml);

if(FALSE == $doc->loadXML($xml)){
die('Oups! Route cannot be determined between '.$params->origin.' and '.$params->destination);
}
curl_close($ch);
//Get total distance
$legs = $doc->getElementsByTagName('leg');
$total_distance = 0;
foreach ($legs as $leg){
$distances = $leg->getElementsByTagName('distance');
$total_distance += floatval($distances->item($distances->length-1)->nodeValue);//With the floatval, we don't need to get the 'value' node
}
//Get itinerary polyline
$points_str = $doc->getElementsByTagName('points');
$points = array();//array of panoPoint
for ($i = 0; $i < $points_str->length-1; $i++) {
$points = array_merge($points, fDecodeLine(trim($points_str->item($i)->nodeValue)));
}
//Remove doublons
$max_index = count($points) - 1;
$panoPoints[] = $points[0];//Add start point
for ($i = 1; $i <= $max_index; ++$i){//loop through vertices of the polyline
if(0 != strcmp($points[$i-1],$points[$i])){
if($params->has_interpolation){//Interpolate
fInterpolateVertices($points[$i-1],$points[$i], $panoPoints);
}
$panoPoints[] = $points[$i];
}
}
if(count($panoPoints) > $params->max_nb_vertices){
die('Oups! Too many vertices for this route ('.count($panoPoints).'). You cannot exceed '. $params->max_nb_vertices.'.');
}

if($params->has_overlay){//Get iti trace overlay
$overlay_image = fGetTraceFullOverlay($panoPoints, $z, $overlay_offset);
$green = imagecolorallocate($overlay_image, 0, 200, 0);
imagesetthickness ($overlay_image, 6);
}
if($params->next_from_pano){
//Frame are based on pano metadata as long as 'text' is the same
fCreateFramesFromPanoMetadata($panoPoints[0], $overlay_image, $overlay_offset, $green, $z, $total_distance);
}else{
//Frame are based on iti vertices (standard mode)
fCreateFramesFromVertices($panoPoints, $overlay_image, $overlay_offset, $green, $z, $total_distance);
}
if($params->has_overlay){
imagedestroy($overlay_image);
unset($overlay_image);
}
//Create video from images
/////exec($params->ffmpeg, $ret, $err);
/* sleep(ceil(count($panoPoints)*0.01));//give enough time to ffmpeg to do its business
//Delete temporary thumbnails
if ($handle = opendir(TEMP_FOLDER)) {
while (false !== ($file = readdir($handle))) {
if ($file != "." && $file != "..") {
unlink(TEMP_FOLDER.'/'.$file);
}
}
closedir($handle);
}
*/
//echo 'Over. Executed in '.number_format(round(fMicrotimeDiff($startTime)/1000)).'s. Memory peak: '.number_format(memory_get_peak_usage()).' bytes';

$filejson = TEMP_FOLDER.'/pics.json';
file_put_contents($filejson, json_encode($images));

header("Content-Type: application/json;charset=UTF-8");
echo json_encode($images);

/////////////////
// FUNCTIONS //
////////////////

//Download a whole panorama from google servers
//Return the panorama as a GD image
function fDownloadPanorama($pano_id){
global $curl_options;

$params = Params::getInstance();
//Initialize Curl multi
for($y=0; $y<$params->sv_height_tiles; ++$y){
$curl_handlers[] = array();
for($x=0; $x<$params->sv_width_tiles; ++$x){
$ch = curl_init();
curl_setopt_array($ch, $curl_options);
$curl_handlers[$y][] = $ch;
}
}
$mh = curl_multi_init();
//Retrieve tiles from Google servers
for($y=0; $y<$params->sv_height_tiles; ++$y){
for($x=0; $x<$params->sv_width_tiles; ++$x){
curl_setopt($curl_handlers[$y][$x], CURLOPT_URL, 'http://cbk'.$x.'.google.com/cbk?output=tile&zoom=2&panoid='.$pano_id.'&y='.$y.'&x='.$x);
curl_multi_add_handle($mh,$curl_handlers[$y][$x]);
}
}
$running=null;
//execute the handlers
do {
usleep(10000);
curl_multi_exec($mh,$running);
} while($running > 0);
$merged_image = imagecreatetruecolor($params->sv_width_tiles * SV_TILE_SIZE, $params->sv_height_tiles * SV_TILE_SIZE);
for($y=0; $y<$params->sv_height_tiles; ++$y){
for($x=0; $x<$params->sv_width_tiles; ++$x){
//Merge all tiles
$tile = imagecreatefromstring(curl_multi_getcontent($curl_handlers[$y][$x]));
imagecopy($merged_image, $tile, SV_TILE_SIZE*$x, SV_TILE_SIZE*$y, 0, 0, SV_TILE_SIZE, SV_TILE_SIZE);
imagedestroy($tile);
unset($tile);
}
}
//Curl clean up
for($y=0; $y<$params->sv_height_tiles; ++$y){
for($x=0; $x<$params->sv_width_tiles; ++$x){
curl_multi_remove_handle($mh, $curl_handlers[$y][$x]);
curl_close($curl_handlers[$y][$x]);
}
}
curl_multi_close($mh);
//Return the panorama as a GD image
return $merged_image;
}//fDownloadPanorama

//creates a set of frames based on iti vertices (panoPoints). This is the standard mode
function fCreateFramesFromVertices($panoPoints, $overlay_image, $overlay_offset, $green, $z, $total_distance){
global $curl_options;

$params = Params::getInstance();

$distance = 0;
$index_panos = 0;
$max_index = count($panoPoints)-1;

for ($i = 0; $i <= $max_index; ++$i){//loop through vertices of the polyline
if($i>0){//Get current distance
$distance += fGetDistance($panoPoints[$i-1], $panoPoints[$i]);
}
$ch = curl_init();
curl_setopt_array($ch, $curl_options);
curl_setopt($ch, CURLOPT_URL, 'http://cbk0.google.com/cbk?output=xml&ll='.$panoPoints[$i]->y.','.$panoPoints[$i]->x);
$xml = @simplexml_load_string(curl_exec($ch));
curl_close($ch);
if( (FALSE !== $xml) && isset($xml->data_properties) ){
$pano_id = $xml->data_properties['pano_id'];
}else{
continue;
}
//Download a whole panorama from google servers
$merged_image = fDownloadPanorama($pano_id);
//Get bearing
if($i == $max_index){//last point
$yaw = fGetBearing($panoPoints[$i-1], $panoPoints[$i]);
}else{
$yaw = fGetBearing($panoPoints[$i], $panoPoints[$i+1]);
}
$merged_offset = fGetFOVOffset($yaw, $xml);
fCreateFrame($panoPoints[$i], (($i==0)?null:$panoPoints[$i-1]), $merged_image, $merged_offset, $overlay_image, $overlay_offset, $green, $z, $distance, $total_distance, $index_panos++);
}
}//fCreateFramesFromVertices()

//creates a set of frames based on SV metadata as long as 'text' is the same as in the first pano.
//this mode target iti using a unique road (ex: Boulevard Peripherique of Paris)
function fCreateFramesFromPanoMetadata($origin_point, $overlay_image, $overlay_offset, $green, $z, $total_distance){
global $curl_options;

$params = Params::getInstance();

//Get data from first pano
$ch = curl_init();
curl_setopt_array($ch, $curl_options);
curl_setopt($ch, CURLOPT_URL, 'http://cbk0.google.com/cbk?output=xml&ll='.$origin_point->y.','.$origin_point->x);
$xml = @simplexml_load_string(curl_exec($ch));
if( (FALSE !== $xml) && isset($xml->data_properties) ){
$ref_text = $xml->data_properties->text;
$pano_id = $xml->data_properties['pano_id'];
$origin_pano_id = $pano_id;
$yaw = floatval($xml->projection_properties['pano_yaw_deg']);
}else{
die('Oups! Cannot extract reference text from first panorama: '.$pano_id);
}
$previous_panoPoint = null;
$panoPoint = $origin_point;
if(!isset($_GET['re'])){
$distance = 0;
$index_panos = 0;//analysed panos
$index_frames = 0;//converted panos (subsamples of analysed panos)
}else{
//Use backup data
list($distance, $index_panos, $index_frames,$x, $y) = explode(';', $_GET['re']);
$overlay_offset = new Point($x, $y);
//Load backup overlay
imagedestroy($overlay_image);
$overlay_image = imagecreatefrompng($params->video_name.'_'.$index_panos.'_ov.png');
}

$max_distance = 1.1 * $total_distance;
do{//loop forever until..
//Download a whole panorama from google servers
//if(!($index_panos % $params->modulo_fp)){//Convert panos to frames
$merged_image = fDownloadPanorama($pano_id);
fCreateFrame($panoPoint, $previous_panoPoint, $merged_image, fGetFOVOffset(), $overlay_image, $overlay_offset, $green, $z, $distance, $total_distance, $index_frames++);
//echo $index_panos.'OK '.$pano_id.': '.$distance.'/'.$total_distance.'<br/>';
//}else{
//echo $index_panos.'KO '.$pano_id.': '.$distance.'/'.$total_distance.'<br/>';
//}
//get POV and next panorama from metadata
if(isset($xml->annotation_properties) ){
$yaw = floatval($xml->projection_properties['pano_yaw_deg']);
$panos_found = array();
foreach($xml->annotation_properties->link as $link){//loop through all links and extract suitable ones.
if( (abs(floatval($link['yaw_deg']) - $yaw) < 90) && (0 == strcasecmp($link->link_text, $ref_text)) ){
$panos_found[(string)$link['pano_id']] = floatval($link['yaw_deg']);
}
}
if(empty($panos_found)){
//try to jump over a small hole in SV coverage
$new_lat = $panoPoint->y + 2 * ($panoPoint->y - $previous_panoPoint->y);
$new_lng = $panoPoint->x + 2 * ($panoPoint->x - $previous_panoPoint->x);
//echo 'NF: http://cbk'.rand(0,3).'.google.com/cbk?output=xml&ll='.$new_lat.','.$new_lng.'<br/>';
curl_setopt($ch, CURLOPT_URL, 'http://cbk'.rand(0,3).'.google.com/cbk?output=xml&ll='.$new_lat.','.$new_lng);
$xml = @simplexml_load_string(curl_exec($ch));
if( (FALSE !== $xml) && isset($xml->data_properties) && (0 == strcasecmp($xml->data_properties->text, $ref_text)) ){
$pano_id = $xml->data_properties['pano_id'];
}else{
//Store overlay for future use
imagepng($overlay_image, $params->video_name.'_'.$index_panos.'_ov.png');
die('Oups! Cannot guess next panorama: '.$pano_id.' &re='.$distance.';'.$index_panos.';'.$index_frames.';'.$overlay_offset->x.';'.$overlay_offset->y);
}
}else{
//Take link with the smallest deviation
asort($panos_found);
reset($panos_found);
$pano_id = key($panos_found);
}
//Loop as long as road label keeps the same, origin pano is not met again and total distance is below what it should be (with a little margin).
if( ($pano_id != $origin_pano_id) && ($distance < $max_distance) /*&& ($index_panos < 20)*/){
//Get data from next pano
curl_setopt($ch, CURLOPT_URL, 'http://cbk'.rand(0,3).'.google.com/cbk?output=xml&panoid='.$pano_id);
$xml = @simplexml_load_string(curl_exec($ch));
if( (FALSE !== $xml) && isset($xml->data_properties) ){
$previous_panoPoint = $panoPoint;
$panoPoint = new Point(floatval($xml->data_properties['lng']), floatval($xml->data_properties['lat']));
$distance += fGetDistance($previous_panoPoint, $panoPoint);
$yaw = floatval($xml->projection_properties['pano_yaw_deg']);
}else{
die('Oups! Cannot extract data from next panorama: '.$pano_id);
}
}else{
break;
}
}else{
break;
}
++$index_panos ;
}while(1);
curl_close($ch);
}//fCreateFramesFromPanoMetadata

//Create a frame based on an exract from the whole panorama
function fCreateFrame($panoPoint, $previous_panoPoint, $merged_image, $merged_offset, $overlay_image, $overlay_offset, $iti_color, $z, $distance, $total_distance, $index_panos){
$params = Params::getInstance();

//Extract part of the marged image corresponding to field of view
$fov_image = imagecreatetruecolor($params->video_width, $params->video_height);
$merged_width = $params->sv_width_tiles * SV_TILE_SIZE;
$merged_height = $params->sv_height_tiles * SV_TILE_SIZE;
//Depending on video format, we extract part of the merged image.
switch($params->video_height){
case VIDEO_FORMAT_SD:
//Merged image is 4*SV_TILE_SIZE, SV_TILE_SIZE. We keep the whole bottom part
imagecopy($fov_image, $merged_image, 0, 0, $merged_offset, $merged_height - $params->video_height, $params->video_width, $params->video_height);
break;
case VIDEO_FORMAT_HD:
//Merged image is 4*SV_TILE_SIZE, 2*SV_TILE_SIZE.
imagecopy($fov_image, $merged_image, 0, 0, $merged_offset, 0, $params->video_width, $params->video_height);
break;
default:
die('Oups! Unknown format.');
}

if($params->has_overlay){//Add overlay (route name, current distance, iti trace)
$xy2 = GoogleMapUtility::getPixelCoords($panoPoint->y, $panoPoint->x, $z);
if( !is_null($previous_panoPoint) ){//Add current iti trace (in green)
$xy1 = GoogleMapUtility::getPixelCoords($previous_panoPoint->y, $previous_panoPoint->x, $z);
imageline ($overlay_image, $xy1->x - $overlay_offset->x, $xy1->y - $overlay_offset->y, $xy2->x - $overlay_offset->x, $xy2->y - $overlay_offset->y, $iti_color);
}
//Merge part of the overlay centred on current position
imagecopymerge($fov_image, $overlay_image, $params->video_width - $params->overlay_size - 20, $params->video_height - $params->overlay_size - 20, $xy2->x - $overlay_offset->x - $params->overlay_margin, $xy2->y - $overlay_offset->y -$params->overlay_margin, $params->overlay_size, $params->overlay_size, 100);

$gray = imagecolorallocate($fov_image, 128, 128, 128);
$red = imagecolorallocate($fov_image, 255, 0, 0);
imagettftext($fov_image, $params->font_size, 0, 11, 31, $gray, $params->font_name, $params->route_name);
imagettftext($fov_image, $params->font_size, 0, 10, 30, $red, $params->font_name, $params->route_name);
if($total_distance < 10000){//10km
$total_distance = number_format($total_distance / 1000, 1).'Km';//X.Y Km
}else{
$total_distance = round($total_distance / 1000).'Km';//round KM
}
$text = number_format($distance / 1000, 1).'/'.$total_distance;
imagettftext($fov_image, $params->font_size, 0, 11, 61, $gray, $params->font_name, $text);
imagettftext($fov_image, $params->font_size, 0, 10, 60, $red, $params->font_name, $text);
}
imagejpeg($fov_image, TEMP_FOLDER.'/'.$params->video_name.'_'.$index_panos.'.jpg', 85);

global $images;
$images[] = TEMP_FOLDER.'/'.$params->video_name.'_'.$index_panos.'.jpg';

// echo "<pre>";
//echo TEMP_FOLDER.'/'.$params->video_name.'_'.$index_panos.'.jpg\n-\n';
// print_r($images);
// flush();
//Clean up
imagedestroy($merged_image);
unset($merged_image);
imagedestroy($fov_image);
unset($fov_image);
}//fCreateFrame

//Decode an encoded polyline
//Return an array of Point(lat, lng)
function fDecodeLine($encoded){
$len = strlen($encoded);
$index = 0;
$lat = 0;
$lng = 0;

while ($index < $len) {
$shift = 0;
$result = 0;
do {
$b = ord(substr($encoded,$index++,1)) - 63;
$result |= ($b & 0x1f) << $shift;
$shift += 5;
} while ($b >= 0x20);
$dlat = (($result & 1) ? ~($result >> 1) : ($result >> 1));
$lat += $dlat;

$shift = 0;
$result = 0;
do {
$b = ord(substr($encoded,$index++,1)) - 63;
$result |= ($b & 0x1f) << $shift;
$shift += 5;
} while ($b >= 0x20);
$dlng = (($result & 1) ? ~($result >> 1) : ($result >> 1));
$lng += $dlng;
$points[] = new Point($lng * 1e-5, $lat * 1e-5);//lng -> x and lat -> y
}

return $points;
}//fDecodeLine

//Get angle between 2 vertices (Point type)
function fGetBearing($origin, $destination){
if ( 0 == strcmp($origin,$destination)) {
return 0;
}
$lat1 = deg2rad($origin->y);
$lat2 = deg2rad($destination->y);
$dLon = deg2rad($destination->x - $origin->x);
$y = sin($dLon) * cos($lat2);
$x = cos($lat1) * sin($lat2) - sin($lat1) * cos($lat2) * cos($dLon);
return (rad2deg(atan2($y, $x)) +360 ) % 360;
}//fGetBearing

//Get offset in pixel corresponding to the left border of the FOV in the merged image
function fGetFOVOffset($yaw=null, $xml=null){
$params = Params::getInstance();

$pivot = 840;

if( !is_null($yaw) ){
if( isset($xml->projection_properties) ){
$pano_yaw_deg = floatval($xml->projection_properties['pano_yaw_deg']);

//The dirty part: we assume a few things
//only 2 roads: one centered on $pano_yaw_deg and another one on ($pano_yaw_deg+180)%360
//$pano_yaw_deg = offset of 840px (rule of thumbs)

//There are for sure a more accurate way of finding the offset but until now, I don't know yet
//how to use correctly pano_yaw_deg, tilt_yaw_deg and link yaw_deg to compute a more
//correct FOV offset :(

//Compute delta angle between target yaw and pano yaw
$angle = abs($yaw-$pano_yaw_deg) % 200;
if($angle< 90){//we use the left road window
return $pivot - ceil($params->video_width/2);
}else{//we use the right road window
return $pivot + SV_UTURN_OFFSET - ceil($params->video_width/2);
}
}else{
return 2 * SV_TILE_SIZE - ceil($params->video_width/2);//middle
}
}else{//Return always left part of the panorama
return $pivot - ceil($params->video_width/2);
}
}//fGetOffset

//Get distance between 2 locations (Point type)
function fGetDistance($point1, $point2){
//based on Haversine formula
$dLat = deg2rad($point2->y - $point1->y);
$dLon = deg2rad($point2->x - $point1->x);
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($point1->y)) * cos(deg2rad($point2->y)) * sin($dLon/2) * sin($dLon/2);
return floor(6371000 * 2 * atan2(sqrt($a), sqrt(1-$a)));
}//fGetDistance

//Extract interpolated points
function fInterpolateVertices($point1, $point2, &$panoPoints){
$params = Params::getInstance();

if(fGetDistance($point1, $point2) > $params->max_distance_vertices){
$new_point = new Point($point1->x + ($point2->x - $point1->x)/2 , $point1->y + ($point2->y - $point1->y)/2);
//recursivity on left section
fInterpolateVertices($point1, $new_point, &$panoPoints);
//Create a new vertice in the middle
$panoPoints[] = $new_point;
//recursivity on right section
fInterpolateVertices($new_point, $point2, &$panoPoints);
}
}//fIntepolateVertices

//Performance timer
    function fMicrotimeDiff( $aStart, $aEnd=NULL ) {
        if( !$aEnd ) {
            $aEnd= microtime();
        }
        list($start_usec, $start_sec) = explode(" ", $aStart);
        list($end_usec, $end_sec) = explode(" ", $aEnd);
        $diff_sec = intval($end_sec) - intval($start_sec);
        $diff_usec = floatval($end_usec) - floatval($start_usec);
        return intval(1000 * (floatval( $diff_sec ) + $diff_usec));//in ms
    }//fMicrotimeDiff

//Get itinerary trace overlay
//Return the overlay as a GD image
function fGetTraceFullOverlay($panoPoints, &$z, &$overlay_offset){
global $curl_options;

$params = Params::getInstance();
//Find geo-bounds
$min_lat = 90.0;
$max_lat = -90.0;
$min_lng = 180.0;
$max_lng = -180.0;
for ($i = 0; $i < count($panoPoints); ++$i){
$min_lat = min($min_lat, $panoPoints[$i]->y);
$max_lat = max($max_lat, $panoPoints[$i]->y);
$min_lng = min($min_lng, $panoPoints[$i]->x);
$max_lng = max($max_lng, $panoPoints[$i]->x);
}
$z = 17;
do{//Find zoom level that fits target height
--$z;
$xy_min = GoogleMapUtility::getPixelCoords($max_lat, $min_lng, $z);
$xy_max = GoogleMapUtility::getPixelCoords($min_lat, $max_lng, $z);
if( (($xy_max->y - $xy_min->y) < $params->overlay_size) && (($xy_max->x - $xy_min->x) < $params->overlay_size)){
//Zoom in 2 levels
if(15 >= $z){
$z +=2;
}
//Get tile bounds (with margin)
$xy_min = GoogleMapUtility::getPixelCoords($max_lat, $min_lng, $z);
$xy_max = GoogleMapUtility::getPixelCoords($min_lat, $max_lng, $z);
$Tile_min = new Point(floor(($xy_min->x - $params->overlay_margin) / GoogleMapUtility::TILE_SIZE), floor(($xy_min->y - $params->overlay_margin) / GoogleMapUtility::TILE_SIZE));
$Tile_max = new Point(floor(($xy_max->x + $params->overlay_margin) / GoogleMapUtility::TILE_SIZE), floor(($xy_max->y + $params->overlay_margin) / GoogleMapUtility::TILE_SIZE));
$overlay_offset = new Point($xy_min->x - $params->overlay_margin, $xy_min->y - $params->overlay_margin);
break;
}
}while($z>0);
//Initialize complete overlay image
$overlay_image = imagecreatetruecolor( 2 * $params->overlay_margin + $xy_max ->x - $xy_min->x, 2 * $params->overlay_margin + $xy_max->y-$xy_min->y);
$white = imagecolorallocate($overlay_image, 255, 255, 255);
imagefilledrectangle($overlay_image, 0, 0, imagesx($overlay_image), imagesy($overlay_image), $white);
imagecolortransparent ($overlay_image, $white);

//get hybrid tiles from google
$ch = curl_init();
curl_setopt_array($ch, $curl_options);
for($y=$Tile_min->y; $y<=$Tile_max->y; ++$y){
for($x=$Tile_min->x; $x<=$Tile_max->x; ++$x){
curl_setopt($ch, CURLOPT_URL, 'http://mt'.rand(0,3).'.google.com/vt/lyrs=h@132&x='.$x.'&y='.$y.'&z='.$z);
$tile_image = imagecreatefromstring (curl_exec($ch));
imagecopymerge($overlay_image, $tile_image, $x * GoogleMapUtility::TILE_SIZE - $overlay_offset->x, $y * GoogleMapUtility::TILE_SIZE - $overlay_offset->y, 0, 0, GoogleMapUtility::TILE_SIZE, GoogleMapUtility::TILE_SIZE, 99);
imagedestroy($tile_image);
unset($tile_imag);
}
}
curl_close($ch);

//Add full iti trace (in red)
$red = imagecolorallocate($overlay_image, 255, 0, 0);
$max_index = count($panoPoints)-1;
$xy1 = GoogleMapUtility::getPixelCoords($panoPoints[0]->y, $panoPoints[0]->x, $z);
imagesetthickness ($overlay_image, 2);
for ($i = 1; $i <= $max_index; ++$i){//loop through vertices of the polyline
$xy2 = GoogleMapUtility::getPixelCoords($panoPoints[$i]->y, $panoPoints[$i]->x, $z);
imageline ($overlay_image, $xy1->x - $overlay_offset->x, $xy1->y - $overlay_offset->y, $xy2->x - $overlay_offset->x, $xy2->y - $overlay_offset->y, $red);
$xy1= $xy2;
}
//Return the overlay as a GD image
return $overlay_image;
}//fGetTraceFullOverlay
?>
Something went wrong with that request. Please try again.