Osmium  0.1
include/osmium/export/shapefile.hpp
Go to the documentation of this file.
00001 #ifndef OSMIUM_EXPORT_SHAPEFILE_HPP
00002 #define OSMIUM_EXPORT_SHAPEFILE_HPP
00003 
00004 /*
00005 
00006 Copyright 2011 Jochen Topf <jochen@topf.org> and others (see README).
00007 
00008 This file is part of Osmium (https://github.com/joto/osmium).
00009 
00010 Osmium is free software: you can redistribute it and/or modify it under the
00011 terms of the GNU Lesser General Public License or (at your option) the GNU
00012 General Public License as published by the Free Software Foundation, either
00013 version 3 of the Licenses, or (at your option) any later version.
00014 
00015 Osmium is distributed in the hope that it will be useful, but WITHOUT ANY
00016 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
00017 PARTICULAR PURPOSE. See the GNU Lesser General Public License and the GNU
00018 General Public License for more details.
00019 
00020 You should have received a copy of the Licenses along with Osmium. If not, see
00021 <http://www.gnu.org/licenses/>.
00022 
00023 */
00024 
00025 #ifdef OSMIUM_WITH_SHPLIB
00026 
00027 #include <fstream>
00028 #include <sstream>
00029 #include <shapefil.h>
00030 #include <boost/utility.hpp>
00031 
00032 namespace Osmium {
00033 
00034     namespace Export {
00035 
00036         class Shapefile : boost::noncopyable {
00037 
00038             // the following limits are defined by the shapefile spec
00039             static const unsigned int max_dbf_fields            =  16;
00040             static const unsigned int max_dbf_field_name_length =  11;
00041             static const          int max_dbf_field_length      = 255;
00042 
00043             class Field {
00044 
00045             public:
00046 
00047                 Field(const std::string& name, DBFFieldType type, int width=1, int decimals=0) : m_name(name), m_type(type), m_width(width), m_decimals(decimals) {
00048                     if (name == "" || name.size() > max_dbf_field_name_length) {
00049                         throw std::invalid_argument("field name must be between 1 and 11 characters long");
00050                     }
00051                 }
00052 
00053                 const std::string& name() const {
00054                     return m_name;
00055                 }
00056 
00057                 DBFFieldType type() const {
00058                     return m_type;
00059                 }
00060 
00061                 int width() const {
00062                     return m_width;
00063                 }
00064 
00065                 int decimals() const {
00066                     return m_decimals;
00067                 }
00068 
00069             private:
00070 
00071                 std::string m_name;
00072                 DBFFieldType m_type;
00073                 int m_width;
00074                 int m_decimals;
00075 
00076             };
00077 
00078         public:
00079 
00080             virtual ~Shapefile() {
00081                 close();
00082             }
00083 
00084             void close() {
00085                 if (m_dbf_handle) {
00086                     DBFClose(m_dbf_handle);
00087                     m_dbf_handle = 0;
00088                 }
00089                 if (m_shp_handle) {
00090                     SHPClose(m_shp_handle);
00091                     m_shp_handle = 0;
00092                 }
00093             }
00094 
00098             void add_field(Field& field) {
00099                 if (m_fields.size() < max_dbf_fields) {
00100                     int field_num = DBFAddField(m_dbf_handle, field.name().c_str(), field.type(), field.width(), field.decimals());
00101                     if (field_num != (int)m_fields.size()) {
00102                         throw std::runtime_error("Failed to add field:" + field.name());
00103                     }
00104                     m_fields.push_back(field);
00105                 } else {
00106                     throw std::out_of_range("Can't have more than 16 fields in a shapefile.");
00107                 }
00108             }
00109 
00113             void add_field(const std::string& name, 
00114                            DBFFieldType type,       
00115                            int width=1,             
00116                            int decimals=0           
00117                           ) {
00118                 Field field(name, type, width, decimals);
00119                 add_field(field);
00120             }
00121 
00125             void add_field(const std::string& name, 
00126                            const std::string& type, 
00127                            int width=1,             
00128                            int decimals=0           
00129                           ) {
00130 
00131                 DBFFieldType ftype;
00132                 if (type == "string") {
00133                     ftype = FTString;
00134                     decimals = 0;
00135                 } else if (type == "integer") {
00136                     ftype = FTInteger;
00137                     decimals = 0;
00138                 } else if (type == "double") {
00139                     ftype = FTDouble;
00140                 } else if (type == "bool") {
00141                     ftype = FTLogical;
00142                     width = 1;
00143                     decimals = 0;
00144                 } else {
00145                     throw std::runtime_error("Unknown field type:" + type);
00146                 }
00147 
00148                 add_field(name, ftype, width, decimals);
00149             }
00150 
00162             void add_geometry(SHPObject* shp_object) {
00163                 if (!shp_object || shp_object->nSHPType != m_shp_handle->nShapeType) {
00164                     throw Osmium::Exception::IllegalGeometry();
00165                 }
00166                 m_current_shape = SHPWriteObject(m_shp_handle, -1, shp_object);
00167                 if (m_current_shape == -1 && errno == EINVAL) {
00168                     // second chance if likely cause is having reached the 2GB limit
00169                     close();
00170                     m_sequence_number++;
00171                     open();
00172                     m_current_shape = SHPWriteObject(m_shp_handle, -1, shp_object);
00173                 }
00174                 if (m_current_shape == -1) {
00175                     throw std::runtime_error("error writing to shapefile");
00176                 }
00177                 SHPDestroyObject(shp_object);
00178             }
00179 
00180             void add_attribute(const int field, const bool value) const {
00181                 int ok = DBFWriteLogicalAttribute(m_dbf_handle, m_current_shape, field, value ? 'T' : 'F');
00182                 if (!ok) {
00183                     throw std::runtime_error(std::string("Can't add bool to field"));
00184                 }
00185             }
00186 
00187             void add_attribute(const int field, const int value) const {
00188                 int ok = DBFWriteIntegerAttribute(m_dbf_handle, m_current_shape, field, value);
00189                 if (!ok) {
00190                     throw std::runtime_error(std::string("Can't add integer to field"));
00191                 }
00192             }
00193 
00194             void add_attribute(const int field, const std::string& value) const {
00195                 int ok = DBFWriteStringAttribute(m_dbf_handle, m_current_shape, field, value.c_str());
00196                 if (!ok) {
00197                     throw std::runtime_error(std::string("Can't add string to field"));
00198                 }
00199             }
00200 
00201             void add_attribute(const int field, const char *value) const {
00202                 int ok = DBFWriteStringAttribute(m_dbf_handle, m_current_shape, field, value);
00203                 if (!ok) {
00204                     throw std::runtime_error(std::string("Can't add char* to field"));
00205                 }
00206             }
00207 
00208             void add_attribute(const int field) const {
00209                 int ok = DBFWriteNULLAttribute(m_dbf_handle, m_current_shape, field);
00210                 if (!ok) {
00211                     throw std::runtime_error(std::string("Can't add null to field"));
00212                 }
00213             }
00214 
00215             // truncates UTF8 string to fit in shape field
00216             void add_attribute_with_truncate(const int field, const char* value) {
00217                 char dest[max_dbf_field_length+1];
00218                 size_t length = m_fields[field].width();
00219                 memset(dest, 0, length+1);
00220                 strncpy(dest, value, length);
00221                 size_t i = length-1;
00222                 if (dest[i] & 128) {
00223                     if (dest[i] & 64) {
00224                         dest[i] = '\0';
00225                     } else if ((dest[i-1] & 224) == 224) {
00226                         dest[i-1] = '\0';
00227                     } else if ((dest[i-2] & 240) == 240) {
00228                         dest[i-2] = '\0';
00229                     }
00230                 }
00231                 add_attribute(field, dest);
00232             }
00233 
00234             void add_attribute_with_truncate(const int field, const std::string& value) {
00235                 add_attribute_with_truncate(field, value.c_str());
00236             }
00237 
00238 #ifdef OSMIUM_WITH_JAVASCRIPT
00239             int add_string_attribute(int n, v8::Local<v8::Value> value) const {
00240                 uint16_t source[(max_dbf_field_length+2)*2];
00241                 char dest[(max_dbf_field_length+1)*4];
00242                 memset(source, 0, (max_dbf_field_length+2)*4);
00243                 memset(dest, 0, (max_dbf_field_length+1)*4);
00244                 int32_t dest_length;
00245                 UErrorCode error_code = U_ZERO_ERROR;
00246                 value->ToString()->Write(source, 0, max_dbf_field_length+1);
00247                 u_strToUTF8(dest, m_fields[n].width(), &dest_length, source, std::min(max_dbf_field_length+1, value->ToString()->Length()), &error_code);
00248                 if (error_code == U_BUFFER_OVERFLOW_ERROR) {
00249                     // thats ok, it just means we clip the text at that point
00250                 } else if (U_FAILURE(error_code)) {
00251                     throw std::runtime_error("UTF-16 to UTF-8 conversion failed");
00252                 }
00253                 return DBFWriteStringAttribute(m_dbf_handle, m_current_shape, n, dest);
00254             }
00255 
00256             int add_logical_attribute(int n, v8::Local<v8::Value> value) const {
00257                 v8::String::Utf8Value str(value);
00258 
00259                 if (atoi(*str) == 1 || !strncasecmp(*str, "T", 1) || !strncasecmp(*str, "Y", 1)) {
00260                     return DBFWriteLogicalAttribute(m_dbf_handle, m_current_shape, n, 'T');
00261                 } else if ((!strcmp(*str, "0")) || !strncasecmp(*str, "F", 1) || !strncasecmp(*str, "N", 1)) {
00262                     return DBFWriteLogicalAttribute(m_dbf_handle, m_current_shape, n, 'F');
00263                 } else {
00264                     return DBFWriteNULLAttribute(m_dbf_handle, m_current_shape, n);
00265                 }
00266             }
00267 
00271             bool add(Osmium::Geometry::Geometry* geometry, 
00272                      v8::Local<v8::Object> attributes) {   
00273 
00274                 try {
00275                     add_geometry(geometry->create_shp_object());
00276                 } catch (Osmium::Exception::IllegalGeometry) {
00277                     return false;
00278                 }
00279 
00280                 int ok = 0;
00281                 for (size_t n=0; n < m_fields.size(); n++) {
00282                     v8::Local<v8::String> key = v8::String::New(m_fields[n].name().c_str());
00283                     if (attributes->HasRealNamedProperty(key)) {
00284                         v8::Local<v8::Value> value = attributes->GetRealNamedProperty(key);
00285                         if (value->IsUndefined() || value->IsNull()) {
00286                             DBFWriteNULLAttribute(m_dbf_handle, m_current_shape, n);
00287                         } else {
00288                             switch (m_fields[n].type()) {
00289                                 case FTString:
00290                                     ok = add_string_attribute(n, value);
00291                                     break;
00292                                 case FTInteger:
00293                                     ok = DBFWriteIntegerAttribute(m_dbf_handle, m_current_shape, n, value->Int32Value());
00294                                     break;
00295                                 case FTDouble:
00296                                     throw std::runtime_error("fields of type double not implemented");
00297                                     break;
00298                                 case FTLogical:
00299                                     ok = add_logical_attribute(n, value);
00300                                     break;
00301                                 default:
00302                                     ok = 0; // should never be here
00303                                     break;
00304                             }
00305                             if (!ok) {
00306                                 std::string errmsg("failed to add attribute '");
00307                                 errmsg += m_fields[n].name();
00308                                 errmsg += "'\n";
00309                                 throw std::runtime_error(errmsg);
00310                             }
00311                         }
00312                     } else {
00313                         DBFWriteNULLAttribute(m_dbf_handle, m_current_shape, n);
00314                     }
00315                 }
00316                 return true;
00317             }
00318 
00319             v8::Local<v8::Object> js_instance() const {
00320                 return JavascriptTemplate::get<JavascriptTemplate>().create_instance((void*)this);
00321             }
00322 
00323             v8::Handle<v8::Value> js_add_field(const v8::Arguments& args) {
00324                 if (args.Length() < 3 || args.Length() > 4) {
00325                     throw std::runtime_error("Wrong number of arguments to add_field method.");
00326                 }
00327 
00328                 v8::String::Utf8Value name(args[0]);
00329                 std::string sname(*name);
00330 
00331                 v8::String::Utf8Value type(args[1]);
00332                 std::string stype(*type);
00333 
00334                 int width = args[2]->Int32Value();
00335                 int decimals = (args.Length() == 4) ? args[3]->Int32Value() : 0;
00336 
00337                 add_field(sname, stype, width, decimals);
00338 
00339                 return v8::Integer::New(1);
00340             }
00341 
00342             v8::Handle<v8::Value> js_add(const v8::Arguments& args) {
00343                 if (args.Length() != 2) {
00344                     throw std::runtime_error("Wrong number of arguments to add method.");
00345                 }
00346 
00347                 v8::Local<v8::Object> xxx = v8::Local<v8::Object>::Cast(args[0]);
00348                 Osmium::Geometry::Geometry* geometry = (Osmium::Geometry::Geometry*) v8::Local<v8::External>::Cast(xxx->GetInternalField(0))->Value();
00349 
00350                 try {
00351                     add(geometry, v8::Local<v8::Object>::Cast(args[1]));
00352                 } catch (Osmium::Exception::IllegalGeometry) {
00353                     std::cerr << "Ignoring object with illegal geometry." << std::endl;
00354                     return v8::Integer::New(0);
00355                 }
00356 
00357                 return v8::Integer::New(1);
00358             }
00359 
00360             v8::Handle<v8::Value> js_close(const v8::Arguments& /*args*/) {
00361                 close();
00362                 return v8::Undefined();
00363             }
00364 
00365             struct JavascriptTemplate : public Osmium::Javascript::Template {
00366 
00367                 JavascriptTemplate() : Osmium::Javascript::Template() {
00368                     js_template->Set("add_field", v8::FunctionTemplate::New(function_template<Shapefile, &Shapefile::js_add_field>));
00369                     js_template->Set("add",       v8::FunctionTemplate::New(function_template<Shapefile, &Shapefile::js_add>));
00370                     js_template->Set("close",     v8::FunctionTemplate::New(function_template<Shapefile, &Shapefile::js_close>));
00371                 }
00372 
00373             };
00374 
00375 #endif // OSMIUM_WITH_JAVASCRIPT
00376 
00377         protected:
00378 
00383             Shapefile(const std::string& filename, int type) : m_filename_base(filename), m_fields(), m_type(type), m_sequence_number(0) {
00384                 open();
00385             }
00386 
00387         private:
00388 
00390             const std::string m_filename_base;
00391 
00393             std::vector<Field> m_fields;
00394 
00395             // handles to the shapelib objects
00396             SHPHandle m_shp_handle;
00397             DBFHandle m_dbf_handle;
00398 
00400             int m_current_shape;
00401 
00403             int m_type;
00404 
00406             int m_sequence_number;
00407 
00412             void open() {
00413                 std::ostringstream filename;
00414                 filename << m_filename_base;
00415                 if (m_sequence_number) {
00416                     filename << "_" << m_sequence_number;
00417                 }
00418 
00419                 m_shp_handle = SHPCreate(filename.str().c_str(), m_type);
00420                 if (m_shp_handle == 0) {
00421                     throw std::runtime_error("Can't open shapefile: " + filename.str() + ".shp/shx");
00422                 }
00423                 m_dbf_handle = DBFCreate(filename.str().c_str());
00424                 if (m_dbf_handle == 0) {
00425                     throw std::runtime_error("Can't open shapefile: " + filename.str() + ".dbf");
00426                 }
00427 
00428                 std::ofstream file;
00429                 file.open((filename.str() + ".prj").c_str());
00430                 if (file.fail()) {
00431                     throw std::runtime_error("Can't open shapefile: " + filename.str() + ".prj");
00432                 }
00433                 file << "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\",6378137,298.257223563]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0.017453292519943295]]" << std::endl;
00434                 file.close();
00435 
00436                 file.open((filename.str() + ".cpg").c_str());
00437                 if (file.fail()) {
00438                     throw std::runtime_error("Can't open shapefile: " + filename.str() + ".cpg");
00439                 }
00440                 file << "UTF-8" << std::endl;
00441                 file.close();
00442 
00443                 // If any fields are defined already, add them here. This will do nothing if
00444                 // called from the constructor.
00445                 for (std::vector<Field>::const_iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
00446                     DBFAddField(m_dbf_handle, it->name().c_str(), it->type(), it->width(), it->decimals());
00447                 }
00448             }
00449 
00450         }; // class Shapefile
00451 
00455         class PointShapefile : public Shapefile {
00456 
00457         public:
00458 
00464             PointShapefile(const std::string& filename) : Shapefile(filename, SHPT_POINT) {
00465             }
00466 
00467         };
00468 
00472         class LineStringShapefile : public Shapefile {
00473 
00474         public:
00475 
00481             LineStringShapefile(const std::string& filename) : Shapefile(filename, SHPT_ARC) {
00482             }
00483 
00484         };
00485 
00489         class PolygonShapefile : public Shapefile {
00490 
00491         public:
00492 
00498             PolygonShapefile(const std::string& filename) : Shapefile(filename, SHPT_POLYGON) {
00499             }
00500 
00501         };
00502 
00503     } // namespace Export
00504 
00505 } // namespace Osmium
00506 
00507 #endif // OSMIUM_WITH_SHPLIB
00508 
00509 #endif // OSMIUM_EXPORT_SHAPEFILE_HPP
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines