步骤一作品解析获取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();
?>