步骤一作品解析获取id
<?php class PanoPageTool { public static function downloadWithNaming($url, $options = []) { $defaults = [ 'save_dir' => './downloads/', 'file_prefix' => '', 'file_suffix' => '.html', 'overwrite' => false ]; $options = array_merge($defaults, $options); // 提取文件名 $path = parse_url($url, PHP_URL_PATH); $baseName = basename($path); if (empty($baseName) || $baseName === 't') { $fileName = $options['file_prefix'] . uniqid() . $options['file_suffix']; } else { $fileName = $options['file_prefix'] . $baseName . $options['file_suffix']; } $filePath = rtrim($options['save_dir'], '/') . '/' . $fileName; // 检查文件是否已存在 if (!$options['overwrite'] && file_exists($filePath)) { return [ 'success' => false, 'error' => '文件已存在', 'file_path' => $filePath ]; } // 下载内容 $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => 'Mozilla/5.0', CURLOPT_SSL_VERIFYPEER => false, CURLOPT_TIMEOUT => 30 ]); $content = curl_exec($ch); $info = curl_getinfo($ch); curl_close($ch); if ($content && $info['http_code'] === 200) { // 确保目录存在 if (!is_dir($options['save_dir'])) { mkdir($options['save_dir'], 0755, true); } if (file_put_contents($filePath, $content)) { return [ 'success' => true, 'file_path' => $filePath, 'file_name' => $fileName, 'size' => filesize($filePath), 'url' => $url ]; } } return [ 'success' => false, 'error' => '下载失败', 'http_code' => $info['http_code'] ?? 0, 'url' => $url ]; } public static function extractPanoDataIdRegex($htmlContent) { // 正则表达式匹配id="pano"的div和data-id属性 $pattern = '/<div\s+id="pano"[^>]*data-id="([^"]*)"[^>]*>/i'; if (preg_match($pattern, $htmlContent, $matches)) { return [ 'success' => true, 'data_id' => $matches[1], 'method' => 'regex' ]; } // 备用模式:可能属性顺序不同 $pattern2 = '/<div[^>]*data-id="([^"]*)"[^>]*id="pano"[^>]*>/i'; if (preg_match($pattern2, $htmlContent, $matches)) { if (!empty($matches[1])) { return [ 'success' => true, 'data_id' => $matches[1], 'method' => 'regex_alt' ]; } } return [ 'success' => false, 'error' => '未找到匹配的div元素或data-id属性' ]; } }
步骤二全景图还原
多层切图仅需还原最高分辨率
<?php class KrpanoRestorer { private $baseUrl; public function __construct($baseUrl = '') { $this->baseUrl = rtrim($baseUrl, '/'); } /** * 完整还原流程:下载 → 拼接 → 转换 */ public function fullRestoreProcess($xmlContent, $outputDir = 'output/') { $scenes = $this->parseScenes($xmlContent); foreach ($scenes as $scene) { echo "开始处理场景: {$scene['name']}\n"; // 1. 下载所有瓦片 $tilesDir = $this->downloadAllTiles($scene, $outputDir); // 2. 使用 KRPano 工具生成全景图 $outputFile = $outputDir . $scene['name'] . '_equirectangular.jpg'; $this->generateWithKrpanoTools($tilesDir, $scene, $outputFile); echo "场景 {$scene['name']} 处理完成: {$outputFile}\n"; } } /** * 下载场景的所有瓦片 */ private function downloadAllTiles($scene, $baseOutputDir) { $level = $scene['levels'][0]; // 使用最高分辨率层级 $tilesize = (int)$scene['tilesize']; $width = (int)$level['tiledimagewidth']; $height = (int)$level['tiledimageheight']; // 计算瓦片数量 $cols = ceil($width / $tilesize); $rows = ceil($height / $tilesize); $faces = ['l', 'f', 'r', 'b', 'u', 'd']; $tilesDir = $baseOutputDir . 'tiles/' . $scene['name'] . '/'; foreach ($faces as $face) { $faceDir = $tilesDir . $face . '/'; if (!file_exists($faceDir)) { mkdir($faceDir, 0777, true); } echo "下载 {$face} 面瓦片...\n"; for ($row = 1; $row <= $rows; $row++) { for ($col = 1; $col <= $cols; $col++) { $tileUrl = $this->buildTileUrl($level['cube_url'], $face, $row, $col); $tileFile = $faceDir . "tile_{$row}_{$col}.jpg"; if (!file_exists($tileFile)) { $this->downloadTile($tileUrl, $tileFile); } } } } return $tilesDir; } /** * 构建瓦片URL */ private function buildTileUrl($urlPattern, $face, $row, $col) { return $this->baseUrl . str_replace( ['%s', '%v', '%h'], [$face, $row, $col], $urlPattern ); } /** * 下载单个瓦片 */ private function downloadTile($url, $outputPath) { $context = stream_context_create([ 'http' => [ 'timeout' => 30, 'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n" ] ]); $content = @file_get_contents($url, false, $context); if ($content && file_put_contents($outputPath, $content)) { echo "."; return true; } echo "F"; // 下载失败 return false; } /** * 使用 KRPano 工具生成全景图 */ private function generateWithKrpanoTools($tilesDir, $scene, $outputFile) { $level = $scene['levels'][0]; $tilesize = (int)$scene['tilesize']; // 首先拼接每个面的完整图像 $stitchedDir = $tilesDir . 'stitched/'; if (!file_exists($stitchedDir)) { mkdir($stitchedDir, 0777, true); } $faces = [ 'l' => 'left', 'f' => 'front', 'r' => 'right', 'b' => 'back', 'u' => 'up', 'd' => 'down' ]; $stitchedFiles = []; foreach ($faces as $faceCode => $faceName) { $stitchedFile = $stitchedDir . $faceName . '.jpg'; $this->stitchFace($tilesDir . $faceCode . '/', $stitchedFile); $stitchedFiles[$faceCode] = $stitchedFile; } // 构建 KRPano 命令 $krpanoToolsPath = '/www/wwwroot/test/krpano/krpanotools'; //KRPano 工具路径 // 方法1:使用明确的文件参数 $command = sprintf( '%s cube2sphere -l="%s" -f="%s" -r="%s" -b="%s" -u="%s" -d="%s" -o="%s" -jpegquality=90', $krpanoToolsPath, $stitchedFiles['l'], $stitchedFiles['f'], $stitchedFiles['r'], $stitchedFiles['b'], $stitchedFiles['u'], $stitchedFiles['d'], $outputFile ); echo "执行命令: " . $command . "\n"; exec($command, $output, $returnCode); if ($returnCode === 0) { echo "KRPano 转换成功: {$outputFile}\n"; return true; } else { echo "KRPano 转换失败 (" . $returnCode . "): " . implode("\n", $output) . "\n"; return false; } } /** * 拼接某个面的所有瓦片成完整图像 */ private function stitchFace($faceDir, $outputFile) { // 获取所有瓦片文件并按行列排序 $tiles = []; $files = glob($faceDir . 'tile_*.jpg'); foreach ($files as $file) { if (preg_match('/tile_(\d+)_(\d+)\.jpg$/', basename($file), $matches)) { $row = (int)$matches[1]; $col = (int)$matches[2]; $tiles[$row][$col] = $file; } } if (empty($tiles)) { return false; } // 获取网格尺寸和瓦片大小 $rows = count($tiles); $cols = count($tiles[1]); // 假设第一行有所有列 $firstTile = imagecreatefromjpeg($tiles[1][1]); $tileWidth = imagesx($firstTile); $tileHeight = imagesy($firstTile); imagedestroy($firstTile); // 创建完整的面图像 $faceWidth = $cols * $tileWidth; $faceHeight = $rows * $tileHeight; $faceImage = imagecreatetruecolor($faceWidth, $faceHeight); // 拼接所有瓦片 for ($row = 1; $row <= $rows; $row++) { for ($col = 1; $col <= $cols; $col++) { if (isset($tiles[$row][$col])) { $tileImg = imagecreatefromjpeg($tiles[$row][$col]); $x = ($col - 1) * $tileWidth; $y = ($row - 1) * $tileHeight; imagecopy($faceImage, $tileImg, $x, $y, 0, 0, $tileWidth, $tileHeight); imagedestroy($tileImg); } } } // 保存拼接后的面图像 imagejpeg($faceImage, $outputFile, 90); imagedestroy($faceImage); return true; } /** * 解析场景信息 */ private function parseScenes($xmlContent) { $xml = simplexml_load_string($xmlContent); $scenes = []; foreach ($xml->scene as $scene) { $sceneInfo = [ 'name' => (string)$scene['name'], 'view_id' => (string)$scene['view_id'], 'tilesize' => (string)$scene->image['tilesize'], 'levels' => [] ]; foreach ($scene->image->level as $level) { $sceneInfo['levels'][] = [ 'tiledimagewidth' => (string)$level['tiledimagewidth'], 'tiledimageheight' => (string)$level['tiledimageheight'], 'cube_url' => (string)$level->cube['url'] ]; } $scenes[] = $sceneInfo; } return $scenes; } } ?>
调用还原类还原图片
<?php include_once('PanoPageTool.php'); include_once('KrpanoRestorer.php'); class DownloadPanoProjectPics { private $url; private $project_html_file_name; private $project_id = 0; public function __construct($url) { $this->url = $url; } //步骤1:下载作品HTML页面 public function step1() { $options = [ 'save_dir' => './my_pages/', 'file_prefix' => 'page_', 'overwrite' => true ]; $result = PanoPageTool::downloadWithNaming($this->url ,$options); if($result['success']) { $this->project_html_file_name = $result['file_name']; echo "步骤1:成功下载作品页面: " . $result['file_name'] . "\n"; }else{ echo "步骤1:失败\n"; exit; } } //步骤2: 作品页面正则匹配作品id public function step2() { $content = file_get_contents("./my_pages/".$this->project_html_file_name); $result = PanoPageTool::extractPanoDataIdRegex($content); if($result['success']){ $this->project_id = $result['data_id']; echo "步骤2:解析project_id成功,准备下载xml文件\n"; }else{ echo "步骤2:解析project_id失败\n"; exit; } } //步骤3:下载xml文件 public function step3() { $url = "https://xxx/xml/".$this->project_id.".html"; file_put_contents("./my_pages/".$this->project_id.".html",file_get_contents($url)); if(file_exists(__DIR__."/my_pages/".$this->project_id.".html")) { echo "步骤3:下载xml文件成功\n"; }else{ echo "步骤3:下载xml文件失败\n"; } } //步骤4:解析xml文件 解析图片 f b l r u d public function step4() { $restorer = new KrpanoRestorer('https://xxx/'); $xmlContent = file_get_contents("./my_pages/".$this->project_id.".html"); // 设置更大的内存限制和时间限制 ini_set('memory_limit', '1024M'); set_time_limit(0); $restorer->fullRestoreProcess($xmlContent, 'restored_output/'.$this->project_id.'/'); } public function start() { $this->step1(); $this->step2(); $this->step3(); $this->step4(); } } $tool = new DownloadPanoProjectPics("https://xxx/pano_id"); $tool->start(); ?>