搜索在C#中操作xml可找到很多相关教程,此处就不细说了。krpano的全景xml文件和传统xml文件的不同仅在于它们的根不一样,vtour.xml文件的根为:
<krpano version="1.19" title="">
krpano>
每个全景一般都包含有include、skin_settings、action这三个子元素,之后才是场景scene,scene里有自己的属性,还有view、hotspot等子元素。
还需要明确的是,每个场景都一一个唯一名称,原始名称为图片名(没有后缀),在xml文件里,name指示这个唯一名称,在panos瓦片文件夹里,每个场景瓦片的文件夹名则为这个唯一名称,加入全景照片为123.jpg,则:
//场景名称,唯一
//注意如果不要scene,则场景名称不能以数字开头,但如果去掉scene,则很多地方也要跟着修改(同样,热点名称也不能以数字开头)
name="scene_123"
//在浏览场景时显示的名称,可以修改
title="123"
//瓦片文件夹名称,后面会加上.tiles
“123.tiles”
下面来看具体的操作:
在进行操作前要引用命名空间
using System.Xml;
加载xml文件及保存
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlpath); //xml绝对路径 加载xml文件
RenameScene(xmlDoc, "name1", "title1"); //修改title
MoveScene(xmlDoc, ["name1", "name2"]); //更改场景顺序
DeleteScene(xmlDoc, "name1"); //删除场景
RaiseScene(xmlDoc, ["name1", "name2"], "raisename", "after"); //添加场景
ReplaceScene(xmlDoc, ["name1", "name2"]); //替换场景
SetView(xmlDoc, "name1", "10", "20.5"); //设置进入场景时的初始视角
xmlDoc.Save(xmlpath); //保存xml文件
修改场景显示名称:操作xml文件
///
/// 修改场景显示名称title
///
/// xml文件
/// 场景唯一名称name
/// scenename
/// 成功/失败
private string RenameScene(XmlDocument xmlDoc, string sceneid, string scenename)
{
try
{
XmlNode renameNode = xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid + "']");
XmlElement element = (XmlElement)renameNode;
element.SetAttribute("title", scenename);
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
更换场景顺序:操作xml文件
///
/// 更换场景顺序:根据新顺序将要排放的场景结点放到栈或者队列中,然后重新排位置,注意栈和队列的出入顺序
///
/// xml文件
/// 按新顺序排列的场景名称
/// 成功/失败
public string MoveScene(XmlDocument xmlDoc, string[] sceneid)
{
try
{
XmlNode rootNode = xmlDoc.SelectSingleNode("/krpano/action").ParentNode;
Stack<XmlNode> tourNode = new Stack<XmlNode>();
for (int len=sceneid.Length, i = len - 1; i > -1; i--)
{
XmlNode moveNode = xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid[i] + "']");
tourNode.Push(moveNode);
rootNode.RemoveChild(moveNode);
}
while (tourNode.Count() > 0)
{
rootNode.AppendChild(tourNode.Pop());
}
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
删除场景:操作xml文件、panos/*.tiles文件夹
///
/// 删除场景
///
/// xml文件
/// 场景名称
/// 成功/失败
public string DeleteScene(XmlDocument xmlDoc, string sceneid)
{
try
{
XmlNode selectNode = xmlDoc.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid + "']");
selectNode.ParentNode.RemoveChild(selectNode);
//删除场景对应的瓦片图片文件夹,注意路径
//后面有“true”,表示删除这个文件夹及其子目录
Directory.Delete("../panos/" + sceneid + ".tiles", true);
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
添加场景:操作xml文件、panos/*.tiles文件夹
///
/// 添加场景:把其他全景里的一些场景添加到这个场景中来
///
/// xml文件
/// 要添加的场景的名称
/// 参考位置的场景名称:要添加在这个场景前后
/// before/after添加在参考位置的前面或者后面
/// 成功/失败
public string RaiseScene(XmlDocument xmlDoc, string[] sceneid, string sceneseat, string sceneab)
{
int len = sceneid.Length
//被添加场景所在的xml文件
XmlDocument xmlDoc_move = new XmlDocument();
xmlDoc_move.Load(xml_movepath);
try
{
//移动场景对应的瓦片图片文件夹
//此处我移动新全景里所有文件,如果只是特定场景可以根据场景名称找到对应瓦片然后移动
int count = Directory.GetDirectories("../panos").Length;
List<string> folders = new List<string>(Directory.GetDirectories("../panos"));
for (int i = 0; i < count; i++)
{
string realtile = "../panos/" + Path.GetFileName(folders[i]);
if (!Directory.Exists(realtile))
Directory.Move(folders[i], realtile);
else
return;
}
//添加其实是将场景结点从一个xml文件移动到另一个xml文件
//由于栈的出入规定,这个地方不用栈用链表要更容易懂些
Stack<XmlNode> tourNode = new Stack<XmlNode>();
for (int i = len - 1; i > -1; i++)
{
//如果场景在原xml中不是按顺序排列,则用第一句,按场景name添加进栈
//如果场景在原xml中按顺序排列,则可用第二句,直接按顺序添加
tourNode.Push(xmlDoc_move.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid[i] + "']");
tourNode.Push(xmlDoc_move.DocumentElement.SelectSingleNode("/krpano/scene[last()-" + i + "]"));
}
//直接加在xml文件末尾,目前顺序为倒序添加,如果是正序,则上面的循环要从i=0开始
while (len > 0)
{
//从其他xml文件引用结点要用import,类似的从一个datatable引用行列到另一个datatable也要用DataTable.ImportRow()等
XmlNode markNode = xmlDoc.ImportNode(tourNode.Pop(), true);
xmlDoc.DocumentElement.AppendChild(markNode);
len--;
}
//添加在指定场景的前面或者后面,按原始顺序排列
XmlNode rootNode = xmlDoc.SelectSingleNode("/krpano/action").ParentNode;
XmlNode seatNode = xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneseat + "']");
if (sceneab == "before")
{
while (tourNode.Count() > 0)
{
XmlNode markNode = xmlDoc.ImportNode(tourNode.Pop(), true);
rootNode.InsertBefore(markNode, seatNode);
}
}
else
{
while (tourNode.Count() > 0)
{
XmlNode markNode = xmlDoc.ImportNode(tourNode.Pop(), true);
rootNode.InsertAfter(markNode, seatNode);
}
}
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
替换场景:操作xml文件、panos/*.tiles文件夹
///
/// 替换场景
///
/// xml文件
/// 被替换的场景的名称
/// 成功/失败
public string ReplaceScene(XmlDocument xmlDoc, string[] sceneid)
{
int len = sceneid.Length
//被添加场景所在的xml文件
XmlDocument xmlDoc_move = new XmlDocument();
xmlDoc_move.Load(xml_movepath);
try
{
Stack<XmlNode> tourNode = new Stack<XmlNode>();
XmlNode rootNode = xmlDoc.SelectSingleNode("/krpano/action").ParentNode;
//用其他xml文件里的场景替换当前xml文件中的场景
for (int i = len - 1; i > -1; i++)
{
//如果场景在原xml中不是按顺序排列,则用第一句,按场景name添加进栈
//如果场景在原xml中按顺序排列,则可用第二句,直接按顺序添加
//注意场景入栈顺序应和sceneid内被替换顺序的反序一一对应,也即出栈顺序和sceneid顺序一一对应
tourNode.Push(xmlDoc_move.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid[i] + "']");
tourNode.Push(xmlDoc_move.DocumentElement.SelectSingleNode("/krpano/scene[last()-" + i + "]"));
}
for (int i = 0; i < len; i++)
{
XmlNode markNode = xmlDoc.ImportNode(tourNode.Pop(), true);
rootNode.ReplaceChild(markNode, xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + sceneid[i] + "']"));
}
//移动替换场景瓦片,删除被替换场景瓦片
int count = Directory.GetDirectories("../panos").Length;
List<string> folders = new List<string>(Directory.GetDirectories("../panos"));
for (int i = 0; i < count; i++)
{
string realtile = "../panos/" + Path.GetFileName(folders[i]);
if (!Directory.Exists(realtile))
Directory.Move(folders[i], realtile);
else
return;
}
for (int i = 0; i < len; i++)
{
Directory.Delete("../panos/" + sceneid[i] + ".tiles", true);
}
//同一个xml文件里场景替换
for (int i = 0; i < len; i++)
{
//假设替换场景为最后的几个场景,如果是特定场景用SelectSingleNode选定
XmlNode moveNode = rootNode.LastChild;
XmlElement element = (XmlElement)moveNode;
tourNode.Push(moveNode);
rootNode.RemoveChild(moveNode);
}
for (int i = 0; i < len; i++)
{
rootNode.ReplaceChild(tourNode.Pop(), xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='scene_" + array[i] + "']"));
Directory.Delete("../panos/" + sceneid[i] + ".tiles", true);
}
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
设置场景初始视角:操作xml文件
///
/// 设置场景初始视角
///
/// markid
/// sceneid
/// viewhlookat
/// viewvlookat
/// 成功/失败
public string SetView(XmlDocument xmlDoc, string sceneid, string viewhlookat, string viewvlookat)
{
try
{
XmlNode reviseNode = xmlDoc.DocumentElement.SelectSingleNode("/krpano/scene[@name='" + sceneid + "']").FirstChild;
XmlElement element = (XmlElement)reviseNode;
element.SetAttribute("hlookat", viewhlookat);
element.SetAttribute("vlookat", viewvlookat);
xmlDoc.Save(markidxmlpath);
return "success";
}
catch (Exception ex)
{
return "failed";
}
}
附录:用两张全景照片在MAKE VTOUR (NORMAL) droplet.bat上生成全景的xml文件
<krpano version="1.19" title="Virtual Tour">
<include url="skin/vtourskin.xml" />
<skin_settings maps="false"
maps_type="google"
maps_bing_api_key=""
maps_google_api_key=""
maps_zoombuttons="false"
gyro="true"
webvr="true"
webvr_gyro_keeplookingdirection="false"
webvr_prev_next_hotspots="true"
littleplanetintro="false"
title="true"
thumbs="true"
thumbs_width="120" thumbs_height="80" thumbs_padding="10" thumbs_crop="0|40|240|160"
thumbs_opened="false"
thumbs_text="false"
thumbs_dragging="true"
thumbs_onhoverscrolling="false"
thumbs_scrollbuttons="false"
thumbs_scrollindicator="false"
thumbs_loop="false"
tooltips_buttons="false"
tooltips_thumbs="false"
tooltips_hotspots="false"
tooltips_mapspots="false"
deeplinking="false"
loadscene_flags="MERGE"
loadscene_blend="OPENBLEND(0.5, 0.0, 0.75, 0.05, linear)"
loadscene_blend_prev="SLIDEBLEND(0.5, 180, 0.75, linear)"
loadscene_blend_next="SLIDEBLEND(0.5, 0, 0.75, linear)"
loadingtext="loading..."
layout_width="100%"
layout_maxwidth="814"
controlbar_width="-24"
controlbar_height="40"
controlbar_offset="20"
controlbar_offset_closed="-40"
controlbar_overlap.no-fractionalscaling="10"
controlbar_overlap.fractionalscaling="0"
design_skin_images="vtourskin.png"
design_bgcolor="0x2D3E50"
design_bgalpha="0.8"
design_bgborder="0"
design_bgroundedge="1"
design_bgshadow="0 4 10 0x000000 0.3"
design_thumbborder_bgborder="3 0xFFFFFF 1.0"
design_thumbborder_padding="2"
design_thumbborder_bgroundedge="0"
design_text_css="color:#FFFFFF; font-family:Arial;"
design_text_shadow="1"
/>
<action name="startup" autorun="onstart">
if(startscene === null OR !scene[get(startscene)], copy(startscene,scene[0].name); );
loadscene(get(startscene), null, MERGE);
if(startactions !== null, startactions() );
action>
<scene name="scene_Ghm1_color" title="Ghm1_color" onstart="" thumburl="panos/Ghm1_color.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="range" vlookatmin="-54.349" vlookatmax="54.349" />
<preview url="panos/Ghm1_color.tiles/preview.jpg" />
<image>
<cube url="panos/Ghm1_color.tiles/pano_%s.jpg" />
<cube url="panos/Ghm1_color.tiles/mobile/pano_%s.jpg" devices="mobile" />
image>
scene>
<scene name="scene_Ghm2_color" title="Ghm2_color" onstart="" thumburl="panos/Ghm2_color.tiles/thumb.jpg" lat="" lng="" heading="">
<view hlookat="0" vlookat="0" fovtype="MFOV" fov="120" maxpixelzoom="2.0" fovmin="70" fovmax="140" limitview="range" vlookatmin="-54.349" vlookatmax="54.349" />
<preview url="panos/Ghm2_color.tiles/preview.jpg" />
<image>
<cube url="panos/Ghm2_color.tiles/pano_%s.jpg" />
<cube url="panos/Ghm2_color.tiles/mobile/pano_%s.jpg" devices="mobile" />
image>
scene>
krpano>