Osmium
0.1
|
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