osgb文件批量转化为Cesium可以展示的三维模型——以gltf格式为例

MD 专栏收录该内容
11 篇文章 0 订阅

本篇为第一篇,交代一下背景及文件格式,之后的具体代码我会贴出来,但是如果这一篇不够写我就换下一篇。

一、背景

       我手头上拿到的是一个表达巨量三维模型的文件夹,据估计应该是伟景行软件出的数据,其中有三种文件类型(.xml , .osgb , .dds)和一个投影文件,其中osgb是一种倾斜摄影测量三维模型文件格式,dds文件是三维模型的纹理,而xml文件组织这些模型、纹理和参数的文件。等我写完这段话,咱们再具体看一下他们的格式。我们拿到了这些文件,关键是Cesium上并不能显示osgb这种类型的三维模型,所以我们必须要进行格式的转化工作。顺便说一下,Cesium支持的三维模型有以下几种:gltf,glb,dae(暂时是这些啊,1.47版本),纹理用png来表示。如果是小白的话,可以看一下Cesium提供的HelloWorld Demo中Apps/SampleData/models中的文件和示例代码。

       osgb文件,我截取了一部分,它是以十六进制来存的,不用管它,知道有这么个东西就行了

a10e 916c 4545 fb1a 0100 0000 6f00 0000
0400 0000 0100 0000 300a 0000 006f 7367
3a3a 4772 6f75 709d b102 0001 0000 0000
0000 0001 0000 0000 0000 0000 0001 ffff
ffff 0001 0100 0000 7cb1 0200 0a00 0000
6f73 673a 3a47 656f 6465 6ab1 0200 0200
0000 0000 0000 0100 0000 0000 0000 0000

      xml文件,我截了其中一段,足以表达它的意思了。ModelPointClass表明这个xml文件中有一堆模型点,而其中的每一个ModelPoint都对应着一个具体的osgb的模型。其中的参数ModelName直接就是osgb文件名,而LocationX、LocationY、LocationZ参数代表着这个模型的位置(我们这里是北京54坐标系,投影参数啥的我就不写了,根据自己的投影文件自己去看),Matrix3参数是一个三阶矩阵,代表的是这个模型的姿态信息(埋个伏笔,Cesium中用的是四阶矩阵,所以到时候还得转)。

<ModelPointClass>
   <ModelPoint>
      <ModelName>c08e0c43-860b-444a-a149-b5bdd9b13d4d</ModelName>
      <LocationX>15370.576733328226</LocationX>
      <LocationY>35978.118729236339</LocationY>
      <LocationZ>5.4999997053893237</LocationZ>
      <Matrix3>-2.407508,2.586386,0,-2.379427,-2.616909,0,0,0,1</Matrix3>
   </ModelPoint>
   <ModelPoint>...</ModelPoint>
</ModelPointClass>

至于dds文件,没啥好贴出来的,自己在PS上看一下就好了,这个和代码无关。

 

二、具体的流程

注:gltf的转化工具详见这里。截至目前我们看到没有osgb和gltf的直接转换方式,如果大家自己写了,可以上传上去,做一份贡献。所以我们必须通过中间格式来进行转换,我们选择的过渡就是obj格式的三维模型。

1.使用FME软件将osgb文件转化为obj文件。注意,在这里,我们要选择其纹理文件!下面我具体写一下批量转化的步骤:

    在开始导引页中-->点击生成工作空间-->读模块-->格式选择osgb格式,数据集用右边加号按钮添加文件夹-->参数里面选择纹理文件夹,其他的根据自己的需要来改-->写模块-->格式选择obj,数据集同上选择一个输出的文件夹-->参数里面自己设,我没有把FME版本号写进去-->确定-->点击运行按钮即可得到结果

注意:在我使用FME转化过程中发现了一些问题,虽然已经导入了纹理文件夹,但是在生成的mtl文件中老是没有纹理信息(如果您的可以,请留言讨论),所以我考虑使用其他方法进行转换。具体见下一篇文章

2.使用上面的gltf的转化工具,将obj模型转为gltf模型,在这里我们用的是Cesium的obj2gltf工具。安装过程和命令行我就不讲了,人家写的很详细,我就说一下如何将obj2gltf作为一个库来使用,以达到批量转化的目的。

    首先找到obj2gltf的安装位置,我的是在C:\Users\Administrator\AppData\Roaming\npm下,所以我们就在这里建一个js脚本文件,node一下就可转化完毕,具体代码如下:

var obj2gltf = require('obj2gltf');
var fs = require('fs');
fs.readdir('D:\\input-directory',function(err,files){
	if(err){
		return console.error(err);
	}
	files.forEach(function(file){
		let suffix=file.split(".")[1];
		if(suffix=='obj'){
			let InputFilePath="D:\\input-directory\\"+file;
			let OutputFilePath="D:\\output-directory\\"+file.split(".")[0]+".gltf";
			obj2gltf(InputFilePath)
			.then(function(gltf){
				let data=Buffer.from(JSON.stringify(gltf));
				fs.writeFileSync(OutputFilePath, data);
			})
			console.log(file+" finshed"+"\n");
		}else{
			console.log(file+" is not a correct file");
		}
	});
});

3.xml文件解析我是自己写的Java代码,非常简单,大家可以根据自己的文件来写或修改。我使用了jackson-1.9.0和jdom-2.0.6库来解析xml和json文件,下面请看关键代码,由于写的仓促,仅供参考啊偷笑

/**
 * @author zxz
 * 将指定的 xml 文件解析为 包含X/Y/Z坐标的文件 和 包含模型文件名链接 的两个文件
 */
public class PhaseXML {
    private ArrayList<ModelElse> modelElse = new ArrayList<>();
    private ArrayList<ModelPoint> modelPositions = new ArrayList<>();

    public static void main(String[] args) {
        PhaseXML MyPhase = new PhaseXML();
        String InputPath = "你的输入文件的地址";
        String OutputDir = "你的输出文件的地址";

        String[] s1 = InputPath.split("[//]");
        String s2 = s1[s1.length - 1];
        String FileName = s2.split("[.]")[0];
        System.out.println("file name is " + FileName);

        try {
            MyPhase.phase(InputPath, OutputDir);
            MyPhase.WriteToFiles(FileName, OutputDir);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JDOMException e) {
            e.printStackTrace();
        }
    }

    /**
     * 解析xml文件,将数据分别保存至容器中
     * @param InputPath 输入文件名
     * @param OutputDirPath 输出文件路径
     * @throws IOException
     * @throws JDOMException
     */
    public void phase(String InputPath, String OutputDirPath) throws IOException, JDOMException {
        File OutputDir = new File(OutputDirPath);
        if (!OutputDir.exists()) {
            OutputDir.mkdirs();
        }

        SAXBuilder saxBuilder = new SAXBuilder();
        Document document = saxBuilder.build(new FileInputStream(InputPath));

        Element root = document.getRootElement();//获得根节点
        List list = root.getChildren();//将根节点下的所有子节点放入List中
        for (int i = 0; i < list.size(); i++) {
            Element item = (Element) list.get(i);//取得节点实例

            Element ModelName = item.getChild("ModelName");
            String ModelName_text = ModelName.getText();

            Element LocationX = item.getChild("LocationX");
            String LocationX_text = LocationX.getText();

            Element LocationY = item.getChild("LocationY");
            String LocationY_text = LocationY.getText();

            Element LocationZ = item.getChild("LocationZ");
            String LocationZ_text = LocationZ.getText();

            Element Matrix3 = item.getChild("Matrix3");
            String Matrix3_text = Matrix3.getText();

            ModelElse model = new ModelElse(ModelName_text, Matrix3_text);
            modelElse.add(model);
            ModelPoint modelPosition = new ModelPoint(ModelName_text, LocationX_text, LocationY_text, LocationZ_text);
            modelPositions.add(modelPosition);
        }
    }

    /**
     * 将数据写入到文件中,使用多线程技术
     * @param FileName 文件名的开头
     * @param OutputDirPath
     */
    public void WriteToFiles(String FileName, String OutputDirPath) {
        new Thread() {
            @Override
            public void run() {
                try {
                    //输出包含文件名数据和matrix数据的文件
                    ObjectMapper MapperPosition = new ObjectMapper();
                    String text = MapperPosition.writeValueAsString(modelElse);

                    String FilePathModelElse = OutputDirPath + "/" + FileName + "-Else.txt";
                    PrintWriter out = new PrintWriter(new FileWriter(FilePathModelElse));
                    out.print(text);
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

                //输出坐标文件
                String allData = "";
                String FilePathModelPoints = OutputDirPath + "/" + FileName + "-Points.txt";
                PrintWriter out = null;
                try {
                    out = new PrintWriter(new FileWriter(FilePathModelPoints));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < modelPositions.size(); i++) {
                    allData += modelPositions.get(i).Name;
                    allData += ",";
                    allData += modelPositions.get(i).LocationX;
                    allData += ",";
                    allData += modelPositions.get(i).LocationY;
                    allData += ",";
                    allData += modelPositions.get(i).LocationZ;
                    allData += "\n";

                    if (i % 50 == 0) {
                        out.print(allData);
                        allData = "";
                    }
                }
                out.print(allData);
                out.close();
            }
        }.start();
    }
}
public class ModelPoint {
    public String Name;
    public String LocationX;
    public String LocationY;
    public String LocationZ;

    public ModelPoint(String name, String locationX, String locationY, String locationZ) {
        Name = name;
        LocationX = locationX;
        LocationY = locationY;
        LocationZ = locationZ;
    }
}
public class ModelElse {
    public String ModelName;
    public String Matrix3;

    public ModelElse(String modelName, String matrix3) {
        ModelName = modelName;
        Matrix3 = matrix3;
    }
}

4.输出完成后是两个文件,为什么是两个文件呢?因为这里的坐标是北京54坐标系下的,而在Cesium中坐标系统是WGS84,所以我们必须进行坐标转化才能正确的显示。为了批量转化坐标,我使用的是中海达的HGO软件包中的坐标转换工具(坐标转换工具不限,手头上有什么就可以用什么),它里面有一个文件转换功能,在设置完那些转化参数(从投影文件里面找)后,使用此功能完成坐标转化,它的文件输入输出格式为:

输入文件格式:
point1,15160.6045214886,35822.8313535075,5.91647965661388
point2,15101.5734032396,35778.3501290405,11.9063661852996
输出文件格式:
point1,037:09:25.03703N,111:09:06.57204E,5.9165
point2,037:09:23.04340N,111:09:04.92601E,11.9064

大家可以看到,输出里面很坑的一点就是它用度/分/秒来表示经纬度,所以我们到时候还需要转一下格式。

5.当我们转化好格式之后,我们再将两个文件合成一个json文件,以方便使用js代码进行解析并显示,那么我就直接贴代码的,还是java:

/**
 * @author zxz
 * 将 坐标转换完成后的文件 与 包含模型文件名的文件 组合在一起
 */
public class CombineFiles {
    private ArrayList<ModelPoint> points = new ArrayList<>();
    private ArrayList<Model> models = new ArrayList<>();

    public static void main(String[] args) {
        String InputPathPoints = "D://PhaseOut/Building-Points.txt_output.txt";
        String InputPathElse = "D://PhaseOut/Building-Else.txt";
        String OutputPath="D://PhaseOut/Building_finished.txt";

        new Thread() {
            @Override
            public void run() {
                CombineFiles combineFiles = new CombineFiles();
                try {
                    combineFiles.combine(InputPathPoints, InputPathElse,OutputPath);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    public void combine(String InputPathPoints, String InputPathElse,String OutputPath) throws IOException {
        //读取坐标文件数据
        BufferedReader inPoints = new BufferedReader(new FileReader(InputPathPoints));
        String PointsLineData = inPoints.readLine();
        while (PointsLineData != null) {
            String[] data = PointsLineData.split("[,]");

            String LatitudeString = data[1];
            String LongitudeString = data[2];
            String[] Latitudes = LatitudeString.split("[:]");
            String[] Longitudes = LongitudeString.split("[:]");

            double latDegrees = Double.parseDouble(Latitudes[0]);
            double latMinutes = Double.parseDouble(Latitudes[1]);
            String latSeconds_String = Longitudes[2];
            double latSeconds = Double.parseDouble(latSeconds_String.substring(0, latSeconds_String.length() - 1));
            String latitude = String.valueOf(latDegrees + latMinutes / 60 + latSeconds / 3600);

            double longDegrees = Double.parseDouble(Longitudes[0]);
            double longMinutes = Double.parseDouble(Longitudes[1]);
            String longSeconds_String = Longitudes[2];
            double longSeconds = Double.parseDouble(longSeconds_String.substring(0, longSeconds_String.length() - 1));
            String longitude = String.valueOf(longDegrees + longMinutes / 60 + longSeconds / 3600);

            ModelPoint modelPoint = new ModelPoint(data[0], latitude, longitude, data[3]);
            points.add(modelPoint);

            PointsLineData = inPoints.readLine();
        }

        //读取其他文件数据
        BufferedReader inElse = new BufferedReader(new FileReader(InputPathElse));
        String ElseData = inElse.readLine();
        ObjectMapper mapper = new ObjectMapper();
        ArrayList beanList = mapper.readValue(ElseData,ArrayList.class);
        for (int i=0;i<beanList.size();i++){
            LinkedHashMap map= (LinkedHashMap) beanList.get(i);
            String ModeName= String.valueOf(map.get("ModelName"));
            String Matrix3= String.valueOf(map.get("Matrix3"));

            if (ModeName.equals(points.get(i).Name)){
                Model model=new Model(ModeName,points.get(i).LocationX,points.get(i).LocationY,points.get(i).LocationZ,Matrix3);
                models.add(model);
            }else {
                System.out.println("data is wrong,please check!!!");
                break;
            }
        }
        ObjectMapper MapperPosition = new ObjectMapper();
        String text = MapperPosition.writeValueAsString(models);
        PrintWriter out = new PrintWriter(new FileWriter(OutputPath));
        out.print(text);
        out.close();
    }
}

好了,至此我就把文件重新组织了一遍,大家可以看一下它的形式变成这样了

[{"ModelName":"point1",
"LocationX":"37.151825566666666",
"LocationY":"111.15182556666667",
"LocationZ":"5.9165",
"Matrix3":"0.7722797,0.6352827,-2.980228E-08,-2.494678E-08,7.723833E-08,1,0.6352827,-0.7722797,7.549784E-08"},
{...},{...}]

 

我觉得写的太长了好像已经有点不方便看了,所以我在另一篇文章中写一下Cesium如何批量导入显示还有我的一些心得。

  • 8
    点赞
  • 4
    评论
  • 18
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值