天天看点

Krpano学习:在C#中修改全景场景属性(C#操作全景vtour.xml文件及相关瓦片数据/panos/*.tiles)

  搜索在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”
           

  下面来看具体的操作:

在进行操作前要引用命名空间

加载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文件

/// <summary>
/// 修改场景显示名称title
/// </summary>
/// <param name="xmlDoc">xml文件</param>
/// <param name="sceneid">场景唯一名称name</param>
/// <param name="scenename">scenename</param>
/// <returns>成功/失败</returns>
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文件

/// <summary>
/// 更换场景顺序:根据新顺序将要排放的场景结点放到栈或者队列中,然后重新排位置,注意栈和队列的出入顺序
/// </summary>
/// <param name="xmlDoc">xml文件</param>
/// <param name="sceneid">按新顺序排列的场景名称</param>
/// <returns>成功/失败</returns>
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文件夹

/// <summary>
/// 删除场景
/// </summary>
/// <param name="xmlDoc">xml文件</param>
/// <param name="sceneid">场景名称</param>
/// <returns>成功/失败</returns>
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文件夹

/// <summary>
/// 添加场景:把其他全景里的一些场景添加到这个场景中来
/// </summary>
/// <param name="xmlDoc">xml文件</param>
/// <param name="sceneid">要添加的场景的名称</param>
/// <param name="sceneseat">参考位置的场景名称:要添加在这个场景前后</param>
/// <param name="sceneab">before/after添加在参考位置的前面或者后面</param>
/// <returns>成功/失败</returns>
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文件夹

/// <summary>
/// 替换场景
/// </summary>
/// <param name="xmlDoc">xml文件</param>
/// <param name="sceneid">被替换的场景的名称</param>
/// <returns>成功/失败</returns>
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文件

/// <summary>
/// 设置场景初始视角
/// </summary>
/// <param name="markid">markid</param>
/// <param name="sceneid">sceneid</param>
/// <param name="viewhlookat">viewhlookat</param>
/// <param name="viewvlookat">viewvlookat</param>
/// <returns>成功/失败</returns>
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" />
	<!-- customize skin settings: maps, gyro, webvr, thumbnails, tooltips, layout, design, ... -->
	<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"
	               />

	<!-- startup action - load the first scene -->
	<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>
		<!-- place your scene hotspots here -->
	</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>
		<!-- place your scene hotspots here -->
	</scene>
</krpano>