Академический Документы
Профессиональный Документы
Культура Документы
Many experts use AutoCAD and its formats in engineering, design, architecture, geography,
and related fields. It's often useful to combine the data that AutoCAD produces with GIS data
such as shapefiles for placement on maps or for use inside Google Earth or Google Maps.
Learn about two open source libraries—LibreDWG and dxflib—that make the AutoCAD DXF
and DWG file formats more accessible. You also create a translator tool that will write to KML
and shapefile formats and use the GDAL library to facilitate working with GML and shapefile
formats.
Introduction
Many developers and geographic information system (GIS) professionals have been unable to use
a Drawing Interchange Format (DXF) or "drawing" (DWG) file. These AutoCAD formats generally
require that you have Windows® and a copy of AutoCAD to open them. Using a few handy open
source libraries, you can make your applications able to natively read DXF and DWG files on any
operating system at no cost. In this article, you build a converter to translate these file formats into
the more open ESRI shapefile or keyhole markup language (KML) formats. Commercial and open
source software use ESRI Shapefiles, while Google Earth and Google Maps mainly use KML.
You use the library by opening the document and reading the file, followed by looping through each
block in the main control block, as shown in Listing 1.
Listing 1. Opening a DWG file and looping through the main control block
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;
}
Each block can represent any of several types of geometry: a line, circle, arc, text anchored to a
location, or an insert (an offset to be applied to later blocks). You handle each in turn by accessing
properties on the block objects that get_first_owned_object and get_next_owned_object return,
as illustrated in Listing 2.
In this way, reading a DWG file with LibreDWG is a sequential flow from start to finish. When
implementing LibreDWG in C++, it's important that you include dwg.h inside an extern "C" block to
avoid encountering linker errors later. Here's an example:
extern "C" {
#include <dwg.h>
}
Prerequisites for this library are the autoconf, swig, texinfo, and python-dev packages as well
as compiler packages (build-essential if using Debian or Ubuntu). You can build the library by
downloading it by entering the following on the command line:
git clone git://git.sv.gnu.org/libredwg.git
. . . followed by:
./autogen.sh && ./configure && make && sudo make install
You open the file by calling the in function of the DL_Dxf object and passing a pointer to a
class that inherits from the DL_CreationAdapter abstract class. As the in function runs, it calls
several functions in the class passed to it. There are dozens of such functions (see the DXFLib
Programmer's Guide link in Resources), but you'll care only about a handful in most cases
—typically, addPoint, addLine, addCircle, and addVertex. You only need to implement those
functions you care about; the rest you can omit. Listing 3 shows a simple example of loading a
DXF file and reading only lines from it.
#include "dxflib/src/dl_dxf.h"
#include "dxflib/src/dl_creationadapter.h"
#include <stdio.h>
};
#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;
}
Like DWG, the DXF format can contain inserts, which represent offsets to be applied to geometric
features encountered after the insert. These inserts must be stored internally and applied to
coordinates as they are encountered. Similarly, adding polylines (lines with multiple vertices)
requires storing some data. The library first calls addPolyline, indicating that a line is coming; it
then calls addVertex once for each vertex of the line. Finally, the line is ended when endEntity or
endBlock is called, at which point you have the complete line and can render it, export it to a new
format, or take other action.
You can build and install the DXF library by simply entering the following on the command line:
./configure && make && sudo make install
You may receive error messages about strcasecmp as well as about strlen being undeclared.
The dxflib library was built to use GCC/G++ 4.2, and in version 4.3, some reorganization of
header files took place. Fixing this error requires adding a few includes in src/dl_writer.h and
src/dl_writer_ascii.h as well as in #include <cstring> and #include <cstdlib> near the other
includes.
Note: These changes were already made to the copy of dxflib included with the converter source
code available in Download, so this change applies only if you download dxflib directly from the
dxflib website.
A basic KML file consists of a Document section that contains a name and description. The file may
also contain one or more folders (used to organize shapes logically), and inside each folder are
placemarks. The placemarks are the actual shape, which you can define as LineString, Point,
Polygon, or other types. Writing a KML file requires writing properly formatted text representing the
shapes, as shown in Listing 4.
You may also encounter KMZ files, which are just KML files compressed with ZIP compression.
See Resources for a link to a beginner tutorial on KML and complete KML documentation.
Listing 5 shows the code for creating a new shapefile data source and a single point inside the
dataset. See the OGR C++ read/write tutorial and the OGR class hierarchy and documentation
links in Resources for more information on using OGR and for a download link to the library itself.
Most Linux® distribution repositories include the GDAL library and header files as libgdal1-1.7.0
and libgdal1-dev (the version may vary).
// Add an ID field
OGRFieldDefn newField("id", OFTInteger);
newField.SetWidth(32);
lyr->CreateField(&newField);
if (!lyr) {
fprintf(stderr, "No output layer is available.");
return;
}
if (ds) {
// Will trigger saving the file and also
// clean up any layer references from Create/Get Layer calls
OGRDataSource::DestroyDataSource(ds);
}
Another method is to write a plug-in or extension for your application that provides support for
the desired file formats. This method gives you some degree of separation between a file format
library and your application code. If your application already has a plug-in framework, this may be
a good option. For example, Quantum GIS, a common desktop GIS application, uses a plug-in
architecture; one such plug-in allows you to use delimited text files directly in the application.
The last and simplest method of all— a stand-alone converter— can be created to translate
between two or more file formats. This technique has the advantage of being reusable regardless
of your end goals for the data at the cost of adding another step for users.
#include "OutputFormatTypes.h"
#include <vector>
class OutputFormat {
public:
OutputFormat() {};
~OutputFormat() {};
InputFormat.h:
#ifndef INPUTFORMAT_H
#define INPUTFORMAT_H
#include "OutputFormat.h"
class InputFormat {
public:
InputFormat() {};
~InputFormat() {};
virtual void readFeaturesInto(OutputFormat * outputHandler) = 0;
};
#endif
Any format you implement must inherit from one of these classes. In this way, the main function
and entry point of the program can deal exclusively with determining which class to instantiate.
After an input file and output file have been defined, conversion can take place with a single line of
code:
input->readFeaturesInto(output);
The role of the main function (see Listing 7) then becomes solely instantiating the proper input and
output formats, culminating in the readFeaturesInto function call.
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;
}
The only required function in the InputFormat class is readFeaturesInto, which leaves it open to
the individual input format how it actually wants to provide features (shapes) to the provided output
class. The OutputFormat class has a few more required functions, however— functions to add
various types of geometric shapes. You define your own classes for point and line so that you can
provide all arguments as double values instead of integers and so that you can provide a z-axis.
The ESRI Shapefile format has one important limitation; it can only store shapes of a single type
(for example, only lines, only points, or only polygons). No other formats discussed in this article
— DXF, DWG, and KML— have this restriction. In the OutputFormatSHP class, you check to see
what kind of shapefile you're creating; if it's not the right type, you handle it the best you can. While
creating a point shapefile, when you are prompted to add a polyline shape, add each vertex as
an individual point. While creating a line shapefile, if you are prompted to add a point, a warning
is written to standard error indicating that the shape was ignored. The DXF and DWG file formats
also support arcs and circles, which you can approximate in your output formats by creating
multiple small line segments approximating the circle or arc.
When compiling the example application, you may receive error messages relating to dwg.h. Both
the dxflib and LibreDWG libraries define the global definition THICKNESS, so edit the dwg.h file in
its installed location (for example, /usr/local/include/dwg.h) and change the name of the THICKNESS
constant or add an underscore (_) at the end. (This is not actually used in this example code.)
Note: See the Download section for the full source code of this example file format translator.
First, create a handful of directories for the various shapefile output format types:
Given such a setup, you can construct a simple BASH shell script (see Listing 8) to check for
DWG and DXF files in this location and trigger the converter with the appropriate command-line
arguments. To aid in future debugging, this script can also save all uploaded inputs together with
generated outputs into a time-stamped .zip file for administrative use. You can set this script up
to run periodically as a cron job or for FTP and CIFS (Windows file share) use as an automatic
converter.
Listing 8. BASH script to check for files pending conversion and pass them to
the converter
#!/bin/bash -u
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
# 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
The website itself can be a simple file upload page. In this case, including an upload progress bar
would be a good enhancement. An open source tool called Ajax Upload (see Resources for a link)
uses XMLHttpRequest to help produce a more fluid upload interface. In your HTML page, use the
jQuery ready function to create the file uploader once the page has loaded and pass the selected
output format along with the uploaded file (see Listing 9). The stock PHP upload script provided
with the Ajax Upload tool is used, and at the end of that script, the handleUpload command is
altered to save the uploaded file into the correct directory for the desired output type. Rather than
wait for the scheduled cron job to take place, you then use the exec PHP function to start the
script and convert the file. The HTML page waits a few moments for conversion to finish, and then
directs the visitor to the generated .zip file containing the produced output files.
$('#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(); });
}
This code simplifies the converter considerably, accomplishing the goal of making it easily
accessible to anyone who needs a file converted. See Resources for a link to the completed web
interface to the converter, or see the Download section for complete source code to the converter
web page.
Conclusion
You can extend the simple file format converter in this example to handle more file formats for
geometric and geographic data. You can use the libraries demonstrated here to extend any
software tool to natively handle these file formats. Bindings for these libraries are available in
multiple languages beyond the C++ usage and syntax demonstrated here.
Downloads
Description Name Size
AutoCAD file format converter source code AutoCADConverter-SourceCode.zip 1.6MB
Converter website source code ConverterWebsite-SourceCode.zip 0.36MB
Resources
Learn
• The dxflib library website includes source code downloads and documentation.
• GNU LibreDWG website: Find source code as well as documentation.
• Xerces-C++ XML Parser: Use this excellent and reliable way to parse complex XML
documents (including derivatives like GML and KML).
• GDAL download page: Get the source code for OGR—a part of GDAL—.
• Ajax Upload: Get this tool that uses XmlHTTPRequest to display friendly upload progress
bars on HTTP file uploads.
• C/C++ development: You might also be interested in the optimization you can achieve with
IBM's XL C/C++ for AIX and Linux.
Discuss
• developerWorks blogs: Check out these blogs and get involved in the developerWorks
community.