对开源库使用 AutoCAD 文件格式

小卒过河 发布于 2011/12/02 17:20
阅读 2K+
收藏 3
GIS

【开源中国 APP 全新上线】“动弹” 回归、集成大模型对话、畅读技术报告”

简介

很多开发人员和地理信息系统 (GIS) 专家一直无法使用 Drawing Interchange Format (DXF) 或 "drawing" (DWG) 格式的文件。这些 AutoCAD 格式的文件通常需要安装了 Windows® 和 AutoCAD 才能打开。借助几个便捷的开源库,您的应用程序就可以读取任何操作系统上的 DXF 和 DWG 文件,并且不产生任何成本。在本文中,您将构建一个转换器来将这些文件格式转换成更为开放的 ESRI shapefile 或 keyhole markup language (KML) 格式。商业的和开源软件大都使用 ESRI Shapefile 格式,而 Google Earth 和 Google Maps 则主要使用 KML格式。

AutoCAD DWG 和 LibreDWG

最为常见的 AutoCAD 格式是 “drawing” 格式,文件扩展名的结尾为 .dwg。只有少数几款软件应用程序可以读取此格式,这个格式是在 AutoCAD 中保存文件时的默认格式。不过,您可以使用开源库 LibreDWG(参阅 参考资料)来读取这些文件。该文件格式包含一个控件块(内含代表此文件内的形状的额外块),以及面向 model spacepaper space 的块(它们代表的存在于此文档内的坐标偏移量)。

若要使用这个库,可以打开此文档并读取文件,然后依次循环主控制块内的每个块,如 清单 1 所示。


清单 1. 打开一个 DWG 文件并依次循环主控制块
				
Dwg_Data dwg = new Dwg_Data();
int errno = dwg_read_file((char *)inputFilename, dwg);
if (errno) {
  fprintf(stderr, "Could not open DWG. Returned error code: $d\n", errno);
  delete dwg;
}

Dwg_Object_BLOCK_CONTROL * ctrl = dwg->object[0].tio.object->tio.BLOCK_CONTROL;
dumpBlock(ctrl->model_space);
dumpBlock(ctrl->paper_space);	

for (int i = 0; i < ctrl->num_entries; i++) {
  dumpBlock(ctrl->block_headers[i]);
}	
dwg_free(dwg);

每个块可代表任一几何形状:线、圆、弧、锚定在某个位置的文本或插入(要被应用到后面块的一个偏移量)。可以通过访问 get_first_owned_objectget_next_owned_object 所返回的块对象的属性来依次处理它们,如 清单 2 所示。


清单 2. 用 get_first_owned_object 和 get_next_owned_object 读取对象
				
void InputFormatDWG::dumpBlock(Dwg_Object_Ref * block) {
  if (!block) return;
  if (!block->obj) return;
  if (!block->obj->tio.object) return;

  Dwg_Object_BLOCK_HEADER * header = block->obj->tio.object->tio.BLOCK_HEADER;
  Dwg_Object * obj = get_first_owned_object(block->obj, header);
  while (obj) {
    if (obj->type == DWG_TYPE_LINE) {
      Dwg_Entity_LINE * line = obj->tio.entity->tio.LINE;
      printf("Line starting at (%f, %f, %f) ending at (%f, %f, %f)\n", line->start.x, 
              line->start.y, 0, line->end.x, line->end.y, 0);
      // Don't delete "line" - dwg_free will do this
    }

    obj = get_next_owned_object(block->obj, obj, header);
  }
}

这样一来,用 LibreDWG 读取一个 DWG 文件就成为了一个有始有终的顺序流。当在 C++ 内实现 LibreDWG 时,务必要将 dwg.h 包括在 extern "C" 块内以避免日后遇到链接器错误。以下是一个例子:

extern "C" {
	#include <dwg.h>
}

这个库所需的前提条件是 autoconfswigtexinfopython-dev 包以及编译器包(如果使用 Debian 或 Ubuntu 要用到 build-essential)。在命令行输入如下命令来通过下载构建这个库:

git clone git://git.sv.gnu.org/libredwg.git

. . . 然后再输入:

./autogen.sh && ./configure && make && sudo make install

AutoCAD DXF 和 dxflib

DXF 格式是 AutoCAD 中的一个导出选项。正因如此,相比 DWG,这个格式在更多应用程中受到支持,并且相关的规范也已发布(参阅 参考资料 获得完整 DXF 规范的链接)。可以使用开源 dxflib 库读取这些文件。与 LibreDWG 不同,通过自己的顺序编码来读取一个 DXF 文件的受驱动感要少。实际上,使用 dxflib 感觉上就如同是编写事件驱动代码。

通过调用 DL_Dxf 对象的 in 函数并将指针传递给一个继承 DL_CreationAdapter 抽象类来打开此文件。in 函数运行时,会调用传递给它的类中的几个函数。如今,有数十个这样的函数(参阅 参考资料 中的 DXFLib 程序员手册链接),但是在大多数情况下,受关注最多的有如下这几个函数:addPointaddLineaddCircleaddVertex。您只需实现您所关注的那些函数;剩下的您可以忽略。清单 3 显示了一个加载 DXF 文件并从此文件只读取线的简单例子。


清单 3. 加载 DXF 文件并只读取线
				
LineReader.h:
#ifndef LINEREADER_H
#define LINEREADER_H

#include "dxflib/src/dl_dxf.h"
#include "dxflib/src/dl_creationadapter.h"
#include <stdio.h>

class LineReader: public DL_CreationAdapter {
	public:
		// Our functions:
		void readLines(const char * filename);

		// Overloading from parent DL_CreationAdapter:
		void addLine(const DL_LineData& data);


};
#endif

LineReader.cpp:
void LineReader::readLines(const char * filename) {
  DL_Dxf * getData = new DL_Dxf();
  if (!getData->in(filename, this)) {
    fprintf(stderr, "Could not retrieve data from input file.\n");
    delete getData;
    exit(1);
  }  
  delete getData;
}

void LineReader::addLine(const DL_LineData& data) {
  printf("Line starting at (%f, %f, %f) ending at (%f, %f, %f)\n", 
          data.x1, data.y1, data.z1, data.x2, data.y2, data.z2);
}

与 DWG 相似,DXF 格式也可以包含 insert,它代表的是要应用到插入后所遇到的几何特性中的偏移量。这些插入必须存储在内部且被应用到它们遇到的坐标。同样地,添加 polyline(具有多个顶点的线)需要存储一些数据。这个库先是调用 addPolyline,表明要创建的是线,然后再为这条线的每个顶点调用一次 addVertex。最后,当调用 endEntityendBlock 时,加线操作结束,这时就具有了一条完整的线,并可呈现它,将它导出至一个新格式或采取其他操作。

可以通过简单地在命令行输入如下命令来构建和安装 DXF 库:

./configure && make && sudo make install

您可能会收到有关 strcasecmp 以及 strlen 未声明的错误消息。dxflib 库在构建之初是为了用于 GCC/G++ 4.2 ,但是在 4.3 版本中使用会发生一些头文件重组。要修复这个错误,需要在其他包含附近的 src/dl_writer.h 和 src/dl_writer_ascii.h 内以及 #include <cstring>#include <cstdlib> 内添加几个包含。

注意:在 下载 部分给出的转换器源代码内所包含的 dxflib 副本都已实施这些修改,所以只有从 dxflib 网站直接下载 dxflib 时,才需要应用这个修改。

KML 和纯文本/Xerces-C++

Google Earth 和 Google Maps 使用的 KML 格式是 XML 的一种专门形式;因此,可以使用诸如 Xerces-C++ XML Parser(参阅 参考资料 获得相关链接)这样的库来处理 KML 文件。 在读取这些格式时,使用像 Xerces-C++ 这样的正式库来处理可能遇到的更为复杂的结构是相当可取的。而在编写时, 通常只要使用简单的内置语言函数就可以来编写文本文件,并生成合适的文本。

一个基本的 KML 文件包含一个 Document 节,内含一个名称和描述。这个文件还可能包含一个或更多的文件夹(用来逻辑地组织各种形状),并且在每个文件夹内是一些 placemark。这些 placemark 是一些实际的形状,可定义为 LineString、Point、Polygon 或其他类型。编写一个 KML 文件需要编写代表各种形状的相应格式化文本,如 清单 4 所示。


清单 4. 一个简单的 KML 文件
				
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
  <name>test</name>
  <open>1</open>
  <description>Converted from AutoCAD DXF or DWG</description>
  <Folder>
    <name>test</name>
    <Placemark>
      <LineString>
        <tessellate>0</tessellate>
        <coordinates>27.54998,82.27393,0.00000 39.72346,9.25601,0.00000</coordinates>
      </LineString>
    </Placemark>
  </Folder>
</Document>
</kml>

您可能还会碰到 KMZ 文件,它们是用 ZIP 压缩了的 KML 文件。参阅 参考资料 获得有关 KML 和完整 KML 文档的初学者教程的链接。

ESRI Shapefile 和 GDAL/OGR

shapefile 格式是由 ESRI(参阅 参考资料 获得有关完整技术描述的链接)发布的一个商业(但是开放)二进制数据格式。您可以使用开源 OGR Simple Feature Library 来轻松访问这些文件,OGR Simple Feature Library 是 Geospatial Data Abstraction Layer(GDAL)的一部分。在这个示例转换器中,只需要输出,但是这个库还查以简化形状数据的读取。写数据则需要打开一个输出数据源并在其内创建一 个数据层 。您可以创建字段来存储每个形状的非空间数据。创建了输出数据源之后,就可以将多个形状(称为 特性)添加到数据源。创建一个特性还需要创建一个几何形状,比如一个点或线,并将它与此特性相关联。此特性可充当所有字段/非空间数据和几何数据的容器。

清单 5 显示了创建一个新的 shapefile 数据源和数据集内的一个点所需要的代码。参阅 参考资料 中的 OGR C++ 读/写教程和 OGR 类的分层结构和文档链接,来获得有关使用 OGR 的更多信息以及相关库的下载链接。大多数 Linux® 发布存储库都将 GDAL 库和头文件包含在 libgdal1-1.7.0libgdal1-dev(各版本可能会有所不同)。


清单 5. 创建一个内含一个点的 shapefile
				
OGRRegisterAll();
OGRSFDriver drv = OGRSFDriverRegistrar::GetRegistrar()->GetDriverByName("ESRI Shapefile");
if (drv == NULL) {
  fprintf(stderr, "Could not open Shapefile OGR driver.\n");
  return;
}  

OGRDataSource ds = drv->CreateDataSource(filename, NULL);
if (ds == NULL) {
  fprintf(stderr, "Could not create output file.\n");
  return;
}

OGRLayer lyr = ds->CreateLayer("Layer", NULL, wkbPoint, NULL);
if (lyr == NULL) {
  fprintf(stderr, "Could not create layer.\n");
  return;
}

// Add an ID field
OGRFieldDefn newField("id", OFTInteger);
newField.SetWidth(32);
lyr->CreateField(&newField);

if (!lyr) {
  fprintf(stderr, "No output layer is available.");
  return;
}
  
OGRFeature * newFeat = OGRFeature::CreateFeature(lyr->GetLayerDefn());
newFeat->SetField("id", lyr->GetFeatureCount(1));
OGRPoint point;
point.setX(15.653);
point.setY(43.783);
point.setZ(0);
newFeat->SetGeometry(&point);
lyr->CreateFeature(newFeat);
  
// Clean up your memory
OGRFeature::DestroyFeature(newFeat);

if (ds) {
  // Will trigger saving the file and also 
  // clean up any layer references from Create/Get Layer calls
  OGRDataSource::DestroyDataSource(ds);
}

实现文件格式库

有三种主要的方式可在软件内实现一个文件格式库。第一种方式为应用程序内的本地实现,它要求您必须从代码中直接使用这些库,这就意味着必须要 根据绑定的可用性以特定的一组语言来编写代码。它还需要牢固的链接,在更新库版本时,可能会出现小错误。不过,应用程序内的直接实现的确会让用户在使用此 格式时感觉更为顺畅。

另一种方法是为支持所想要的文件格式的这个应用程序编写一个插件或扩展。这种方法提供了文件格式库和应用程序代码之间某种程度上的分离。如果 应用程序已经具备了一种插件框架,那么这种方法将是一个很好的选择。比如,Quantum GIS(一种常见的桌面 GIS 应用程序)就使用了一种插件架构;这样的插件就让您可以直接在应用程序内使用界定了的文本文件。

最后也是最为简单的一种方法是创建一个独立的转换器,它可以转换两个或多个文件格式。这种技术的优势是可以不管数据的最终目标而进行重用,缺点是为用户增加了一个步骤。

从 DXF/DWG 到 KML/Shapefile 的一个命令行转换器

在本例中,采用的是更为简单、可用性更好的创建文件格式转换器的方式。我们的目标是创建一种转换器,既能轻松进行扩展来处理额外格式又能尽可能地将任何特定的输入或输出格式的逻辑保持分离。为实现这些目标,需要定义两个抽象类:InputFormatOutputFormat(参见 清单 6)。


清单 6. InputFormat 和 OutputFormat 抽象类定义
				
OutputFormat.h:
#ifndef OUTPUTFORMAT_H
#define OUTPUTFORMAT_H

#include "OutputFormatTypes.h"
#include <vector>

class OutputFormat {
  public:
    OutputFormat() {};
    ~OutputFormat() {};

    // Standard feature types:
    virtual void addPoint(OutputFeaturePoint newPoint) = 0;
    virtual void addLine(OutputFeatureLine newLine) = 0;
    virtual void addPolyLine(std::vector<OutputFeaturePoint *> newPoints) = 0;
    virtual void addPolygon(std::vector<OutputFeaturePoint *> newPoints) = 0;

    // For approximating text on DXF/DWG with a separate point layer with
    // a label attribute:
    virtual void addText(OutputFeaturePoint location, const char * text) = 0;

    // The cleanup function
    virtual void finalizeOutput() = 0;
};
#endif

InputFormat.h:
#ifndef INPUTFORMAT_H
#define INPUTFORMAT_H

#include "OutputFormat.h"

class InputFormat {
  public:
    InputFormat() {};
    ~InputFormat() {};
    virtual void readFeaturesInto(OutputFormat * outputHandler) = 0; 
};
#endif

所实现的任何格式都必须继承自这些类中的一个。这样一来,主函数以及程序的入口点就能专门确定要实例化哪个类。在定义好输入文件和输出文件后,通过如下的一行代码就可以执行转换:

input->readFeaturesInto(output);

主函数的作用(参见 清单 7)就继而变成了单纯地实例化恰当的输入和输出格式,并最后以 readFeaturesInto 函数调用告终。


清单 7. AutoCAD 转换器的 main() 函数
				
void usage(char * me) {
  printf("Usage: %s inputfile outputfile [point|line|polygon]\n", me);
  printf("Input formats supported:\n");
  printf("\tAutoCAD DXF (*.dxf)\n");
  printf("\tAutoCAD DWG (*.dwg)\n");
  printf("Output formats supported:\n");
  printf("\tText (*.txt)\n");
  printf("\tESRI Shapefile (*.shp)\n");
  printf("\tKeyhold Markup Language (*.kml)\n");
  printf("\nInput format and output format are determined automatically by file extension.
		  \n");
  printf("If you use a shapefile as the output format, please additionally specify\n");
  printf("point, line, or polygon output shapefile type.\n");
}

int main(int argc, char * argv[]) {
  if (argc < 3) {
    usage(argv[0]);
    return 1;
  }

  OutputFormat * output = NULL;  
  InputFormat * input = NULL;
  struct stat fileExists;

  // Set up output format first...
  std::string outFile = argv[2];
  if (outFile.rfind('.') == std::string::npos) {
    printf("I couldn't make sense of your output filename's extension: %s. Please use 
            filename.shp, filename.kml, or filename.txt.\n", argv[2]);
    return 1;
  }
  if (outFile.substr(outFile.rfind('.')+1) == "txt") {
    printf("Setting up output file %s...\n", argv[2]);
    output = new OutputFormatText(argv[2]);
  }
  else if (outFile.substr(outFile.rfind('.')+1) == "shp") {
    if (argc < 4) {
      printf("When specifying shapefile output, please also specify 'point', 'line', or 
              'polygon'. See usage.\n");
      return 1;
    }
    std::string textAttributeFile = outFile.substr(0, outFile.rfind('.')) + "_text.shp";
    OGRwkbGeometryType type;
    if (strcmp(argv[3], "line") == 0) {
      type = wkbLineString;
    }
    else if (strcmp(argv[3], "point") == 0) {
      type = wkbPoint;
    }
    else if (strcmp(argv[3], "polygon") == 0) {
      type = wkbPolygon;
    }
    else {
      printf("I didn't understand %s. Please use point, line, or polygon.\n", argv[3]);
      return 1;
    }
    printf("Setting up output file %s...\n", argv[2]);
    output = new OutputFormatSHP(argv[2], textAttributeFile.c_str(), outFile.substr(0, 
                 outFile.rfind('.')).c_str(), type);
  }
  else if (outFile.substr(outFile.rfind('.')+1) == "kml") {
    printf("Setting up output file %s...\n", argv[2]);
    output = new OutputFormatKML(argv[2], outFile.substr(0, outFile.rfind('.')).c_str());
  }

  // Next grab the input file
  std::string inFile = argv[1];
  if (inFile.rfind('.') == std::string::npos) {
    printf("I couldn't make sense of your input filename's extension: %s. Please use 
            filename.dxf or filename.dwg.\n", argv[1]);
    delete output;
    return 1;
  }
  if (stat(argv[1], &fileExists) != 0) {
    printf("The specified input file does not exist or is not accessible: %s\n", argv[1]);
    return 1;
  }
  if (inFile.substr(inFile.rfind('.')+1) == "dxf") {
    input = new InputFormatDXF(argv[1]);
    printf("Setting up input file %s...\n", argv[1]);
  }
  else if (inFile.substr(inFile.rfind('.')+1) == "dwg") {
    input = new InputFormatDWG(argv[1]);
    printf("Setting up input file %s...\n", argv[1]);
  }

  if (!input) {
    printf("The input file was not recognized or could not be opened.\n");
    return 1;
  }
  if (!output) {
    printf("The output file was not recognized or could not be opened.\n");
    return 1;
  }

  printf("Converting file...\n");
  input->readFeaturesInto(output);
  output->finalizeOutput();
  printf("Done!\n");

  delete input;
  delete output;
  return 0;
}

InputFormat 类内惟一必要的函数就是 readFeaturesInto,该函数向各个输入格式公开它是如何真的将特性(形状)提供给所配备的输出类。 然而,OutputFormat 类则具有多个更必要的函数,如用来添加各种几何形状的函数。您可以为点和线定义您自己的类,以便您可以提供双精度值而非整数值的参数以及 z-轴。

ESRI Shapefile 格式具有一个重要的限制;它只能存储单个类型的形状(比如,只存储线、点或多边形)。而本文中所讨论的其他格式 DXF、DWG 和 KML 均没有这个限制。在 OutputFormatSHP 类内,查看将要创建的 shapefile 类型;如果不是正确的类型,就要尽力处理好它。在创建一个点 shapefile 时,若系统提示添加一个多线形状,那么就以单个点添加每个顶点。在创建一个线 shapefile 时,如果系统提示添加一个点,一个警告就会写入到标准错误以表明此形状被忽略。DXF 和 DWG 文件格式还支持弧和圆,您可以在输出格式中通过创建多个小线段来近似模拟圆或弧。

在编译这个示例应用程序时,可能会收到与 dwg.h 相关的错误消息。dxflib 和 LibreDWG 库均定义了全局定义 THICKNESS,所以要在其安装的位置(比如 /usr/local/include/dwg.h)编辑这个 dwg.h 文件及更改 THICKNESS 常数的名称或在最后添加一个下划线(_)。(在本示例代码中并未这么做。)

注意: 参阅本文 下载 部分获得本示例文件格式转换器的全部源代码。

将这个转换器包裹到一个网站

此该,您已经拥有一个功能完善的命令行文件格式转换器。但是,大多数询问如何读取这些文件的用户通常都是不了解命令行,或不能自己从源代码构 建一个转换器。所以,很有必要为这个工具提供一个简单的基于 Web 的界面,以便需要快速转换一两个文件的人们能够轻松地实现转换。同时,还可以创建一个系统,在这个系统中当文件被添加到磁盘上的一个特定的目录时可自动实 现转换,这个对于 FTP 交互或通过一个 CIFS 文件共享访问文件非常有用。

首先,为各种 shapefile 输出格式类型创建一组目录:

  • uploads_KML 和 uploads_Text 分别用于 KML 和文本输出格式
  • uploads_Point、uploads_Polyline 和 uploads_Polygon 分别用于各种类型的 shapefile 输出格式

上载至上述任何一个位置的 DXF 或 DWG 文件会被转换成该目录名所指示的那种格式。输出也会被放置在另一个名为 outputs 的目录中。

有了这样的设置,就可以构造一个简单的 BASH shell 脚本(参见 清单 8)来检查此位置的 DWG 和 DXF 文件并且通过适当的命令行参数触发这个转换器。为了有助于之后的调试,这个脚本还能将所有已上载的输出和所生成的输出一起保存到一个具有时间戳的 .zip 文件以供管理使用。还可以将这个脚本设置成定期运行的 cron 作业或供 FTP 和 CIFS (Windows 文件共享)使用自动转换器。


清单 8. 用来查找待转换的文件并将这些文件传递至转换器的 BASH 脚本
				
#!/bin/bash -u

# Polyline Shapefile queue
cd /var/www/acadconverter.chrismichaelis.com/uploads_Polyline
for file in /var/www/acadconverter.chrismichaelis.com/uploads_Polyline/*.d*; do
  [[ "$file" =~ .dxf$ || "$file" =~ dwg$ ]] && {
    base=$(basename "$file")
    base=${base%.dwg}
    base=${base%.dxf}
    /var/www/acadconverter.chrismichaelis.com/bin/AutoCADConverter \
                "$file" "$base.shp" line
    [ -e "../outputs/$base.zip" ] && rm -f "../outputs/$base.zip"
    zip "../outputs/$base.zip" "$base.shx" "$base.shp" "$base.dbf" \
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf"
    zip "../uploads_done/$(date +%s)_$base.zip" "$base.shx" "$base.shp" "$base.dbf" \ 
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf" "$file"
    rm -f "$file" "$base.shx" "$base.shp" "$base.dbf" "${base}_text.shp" \ 
                "${base}_text.shx" "${base}_text.dbf"
  }
done

# Polygon Shapefile queue
cd /var/www/acadconverter.chrismichaelis.com/uploads_Polygon
for file in /var/www/acadconverter.chrismichaelis.com/uploads_Polygon/*.d*; do
  [[ "$file" =~ .dxf$ || "$file" =~ dwg$ ]] && {
    base=$(basename "$file")
    base=${base%.dwg}
    base=${base%.dxf}
    /var/www/acadconverter.chrismichaelis.com/bin/AutoCADConverter "$file" \ 
                "$base.shp" polygon 
    [ -e "../outputs/$base.zip" ] && rm -f "../outputs/$base.zip"
    zip "../outputs/$base.zip" "$base.shx" "$base.shp" "$base.dbf" \ 
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf"
    zip "../uploads_done/$(date +%s)_$base.zip" "$base.shx" "$base.shp" "$base.dbf" \ 
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf" "$file"
    rm -f "$file" "$base.shx" "$base.shp" "$base.dbf" "${base}_text.shp" \ 
                "${base}_text.shx" "${base}_text.dbf"
  }
done

# Point Shapefile queue
cd /var/www/acadconverter.chrismichaelis.com/uploads_Point
for file in /var/www/acadconverter.chrismichaelis.com/uploads_Point/*.d*; do
  [[ "$file" =~ .dxf$ || "$file" =~ dwg$ ]] && {
    base=$(basename "$file")
    base=${base%.dwg}
    base=${base%.dxf}
    /var/www/acadconverter.chrismichaelis.com/bin/AutoCADConverter "$file" \
                "$base.shp" point
    [ -e "../outputs/$base.zip" ] && rm -f "../outputs/$base.zip"
    zip "../outputs/$base.zip" "$base.shx" "$base.shp" "$base.dbf" \  
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf"
    zip "../uploads_done/$(date +%s)_$base.zip" "$base.shx" "$base.shp" "$base.dbf" \ 
                "${base}_text.shp" "${base}_text.shx" "${base}_text.dbf" "$file"
    rm -f "$file" "$base.shx" "$base.shp" "$base.dbf" "${base}_text.shp" \  
                "${base}_text.shx" "${base}_text.dbf"
  }
done

# KML queue
cd /var/www/acadconverter.chrismichaelis.com/uploads_KML
for file in /var/www/acadconverter.chrismichaelis.com/uploads_KML/*.d*; do
  [[ "$file" =~ .dxf$ || "$file" =~ dwg$ ]] && {
    base=$(basename "$file")
    base=${base%.dwg}
    base=${base%.dxf}
    /var/www/acadconverter.chrismichaelis.com/bin/AutoCADConverter "$file" "$base.kml" 
    [ -e "../outputs/$base.zip" ] && rm -f "../outputs/$base.zip"
    zip "../outputs/$base.zip" "$base.kml"
    zip "../uploads_done/$(date +%s)_$base.zip" "$base.kml" "$file"
    rm -f "$file" "$base.kml"
  }
done

# Text queue
cd /var/www/acadconverter.chrismichaelis.com/uploads_Text
for file in /var/www/acadconverter.chrismichaelis.com/uploads_Text/*.d*; do
  [[ "$file" =~ .dxf$ || "$file" =~ dwg$ ]] && {
    base=$(basename "$file")
    base=${base%.dwg}
    base=${base%.dxf}
    /var/www/acadconverter.chrismichaelis.com/bin/AutoCADConverter "$file" "$base.txt" 
    [ -e "../outputs/$base.zip" ] && rm -f "../outputs/$base.zip"
    zip "../outputs/$base.zip" "$base.txt"
    zip "../uploads_done/$(date +%s)_$base.zip" "$base.txt" "$file"
    rm -f "$file" "$base.txt"
  }
done

此网站本身可以是一个简单的文件上载页面。在这种情况下,包含一个上载进度栏是一个很好的增强。名为 Ajax Upload 的开源工具(参阅 参考资料 获得相关链接)就是使用了 XMLHttpRequest 来帮助生成一个更为流畅的上载界面。在 HTML 页,使用 jQuery ready 函数在页面加载后创建这个文件加载程序并传递所选中的输出格式与所加载的文件(参见 清单 9)。此外,还使用了Ajax Upload 工具提供的 PHP upload 脚本,并在该脚本的末尾,修改了 handleUpload 命令来将所上载的文件保存到与将要输出类型相对应的那个目录。这时,可以不必等待计划的 cron 作业的发生,而是使用 exec PHP 函数来启动这个脚本并转换该文件。此 HTML 页会等待片刻直至转换完成,然后再将访客引导到包含了所生成的输出文件的 .zip 文件中。


清单 9. 用来设置文件加载程序的 jQuery ready() 函数
				
$(document).ready(function() {
  var uploader = new qq.FileUploader({
    'element': $('#inputFile')[0],
    'action': 'fileuploader.php',
    'params': { 'outputFormat': $('#outputFormat option:selected').attr('value') },
    'onComplete': function(id, file, response) {
      if (!response.success) return;
      // Please Wait
      $('#postLoader').hide()
      $('#postLoader').html('<img src="clock.png" height="210" width="210"><br />
                             Please Wait...');
      $('#postLoader').slideDown();

      // for IE compatibility, don't use anonymous function in setTimeout
      setTimeout("showDownload('" + file.replace(".dwg", "").replace(".dxf", "") + "');",
                 5000);
      return true;
    }
  });

  $('#outputFormat').change(function() {
    uploader.setParams({
      'outputFormat': $('#outputFormat option:selected').attr('value')
    });
  });
});

function showDownload(file) {
  $('#postLoader').slideUp('slow', function() { $('#postLoader').html('<img 
        src="download.png" height="48" width="48" align="absmiddle"> 
        <a href="outputs/' + file + '.zip">Download Ouptut</a>').slideDown(); });
}

上述代码可显著简化这个转换器,实现了方便需要进行文件转换的人们使用的目标。参阅 参考资料 获得该转换器的完整 web 界面的链接,或参阅本文 下载 部分获得这个转换器 Web 页面的完整源代码。

结束语

您可以扩展本示例中的这个简单的文件格式转换器,用它来处理更多几何和地理数据的文件格式。您可以使用本文中所展示的这些库扩展任一软件工具,以便本地处理这些文件格式。除了本文中所展示的 C++ 的用法和语法之外,在很多语言中也有针对这些库的绑定。

加载中
0
为上帝谱曲
该评论暂时无法显示,详情咨询 QQ 群:点此入群
3xxx
3xxx
你这个靠谱啊,厉害。估计很多都是按你这个搞得。
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部