User:Dbenbenn/procshape.cc
Jump to navigation
Jump to search
Here is a simple C++ program I use for converting Shapefile output to SVG format. Specifically, after using shpdump to decode the Shapefile, and proj
to project the data, I use this program to select the shapes I want and make the output SVG-ready.
Suppose you have projected data in data
, a list of names for the shapes in names
, and a list of shape numbers you want (1-based) in numbers
. Use
procshape data numbers names > foo.svg
Here's the code:
#include <fstream> #include <limits> #include <list> #include <map> #include <sstream> #include <iostream> #include <iomanip> #include <cmath> // The input numbers are xxx.yy #define PREC 100 #define PRECDIGS 2 /* Usage: procshape shapes numbers names shapes is the file that shpdump outputs. numbers is the shape numbers to select. names is the name of each shape. */ // A county can have multiple shapes. E.g. individual islands. class shape { public: shape(long _shapenum, long _parts, long _points); void add_point(long x, long y); void new_part(void); void optimize(void); // drop redundant points void output_shape(std::ostream &output) const; long get_shapenum(void) const { return shapenum; }; private: std::list<std::list<std::pair<long, long> > > parts; std::list<std::list<std::pair<long, long> > >::iterator this_part; long shapenum; long expected_parts; long expected_points; long num_parts; long num_points; }; shape::shape(long _shapenum, long _parts, long _points) : shapenum(_shapenum), expected_parts(_parts), expected_points(_points), num_parts(0), num_points(0) { this_part = parts.end(); new_part(); } void shape::add_point(long x, long y) { this_part->push_back(std::pair<long, long>(x, y)); ++num_points; } void shape::new_part(void) { parts.push_back(std::list<std::pair<long, long> >()); ++this_part; ++num_parts; } void shape::optimize(void) { for (std::list<std::list<std::pair<long, long> > >::iterator i = parts.begin(); i != parts.end(); ++i) { if (i->empty() || ++(i->begin()) == i->end()) continue; std::list<std::pair<long, long> >::const_iterator j1 = i->begin(); std::list<std::pair<long, long> >::iterator j2 = ++(i->begin()); std::list<std::pair<long, long> >::iterator j3 = j2; ++j3; while (j3 != i->end()) { if ((j2->second - j1->second) * (j3->first - j2->first) == (j3->second - j2->second) * (j2->first - j1->first)) i->erase(j2++); else { ++j1; ++j2; } ++j3; } j3 = i->begin(); if ((j2->second - j1->second) * (j3->first - j2->first) == (j3->second - j2->second) * (j2->first - j1->first)) i->erase(j2); else j1 = j2; j2 = j3; ++j3; if ((j2->second - j1->second) * (j3->first - j2->first) == (j3->second - j2->second) * (j2->first - j1->first)) i->erase(j2); } } inline std::ostream& operator<<(std::ostream &out, const shape& shp) { shp.output_shape(out); return out; } void shape::output_shape(std::ostream &out) const { if (num_points != expected_points || num_parts != expected_parts) throw(42); std::list<std::list<std::pair<long, long> > >::const_iterator part; for (part = parts.begin(); part != parts.end(); ++part) { if (part->empty() || ++(part->begin()) == part->end()) throw(2); std::list<std::pair<long, long> >::const_iterator point = part->begin(); out << "M " << point->first << ',' << point->second << " l"; long lastx = point->first; long lasty = point->second; ++point; for (; point != part->end(); ++point) { out << ' ' << point->first - lastx << ',' << point->second - lasty; lastx = point->first; lasty = point->second; } out << " z"; } } class County { public: // Skip ahead to shape number shapenum in input, and read it. void read_shape(std::istream &input, long shapenum); void write_county(std::ostream &output, std::string name) const; County(void); // Bounding box of all counties static long get_x(void) { return minx; }; static long get_y(void) { return miny; }; static long get_width(void) { return maxx - minx; }; static long get_height(void) { return maxy - miny; }; private: std::list<shape> shapes; long numshapes; // Bounding box for all counties that were stored (not skipped) static long minx; static long miny; static long maxx; static long maxy; long read_header(std::istream &input, long &numparts, long &numpoints); }; long County::minx = std::numeric_limits<long>::max(); long County::miny = std::numeric_limits<long>::max(); long County::maxx = std::numeric_limits<long>::min(); long County::maxy = std::numeric_limits<long>::min(); County::County(void) : numshapes(0) { } long County::read_header(std::istream &input, long &numparts, long &numpoints) { std::string head; std::getline(input, head); std::istringstream format(head); std::string polygon, parts, points; long shapenum; format >> polygon >> shapenum >> parts >> numparts >> points >> numpoints; if (polygon.compare("polygon") != 0 || parts.compare("parts") != 0 || points.compare("points") != 0) throw(1); return shapenum; } void paranoid_check(const std::string &line, long x, long y) { long absx = std::abs(x), absy = std::abs(y); std::ostringstream foo; if (x < 0 || x == 0 && line[0] == '-') foo << '-'; foo << absx / PREC << '.' << std::setfill('0') << std::setw(PRECDIGS) << absx % PREC << std::setw(0) << '\t'; if (y < 0 || y == 0 && line[line.find('\t') + 1] == '-') foo << '-'; foo << absy / PREC << '.' << std::setfill('0') << std::setw(PRECDIGS) << std::abs(absy) % PREC << std::setw(0); if (line.compare(foo.str()) != 0) { std::cerr << "Paranoid, line is " << line << ", foo is " << foo.str() << ", x is " << x << ", y is " << y << '\n'; // exit(2); } } void County::read_shape(std::istream &input, long shapenum) { long numparts, numpoints; while (shapenum != read_header(input, numparts, numpoints)) { for (long i = 0; i < numparts - 1 + numpoints; ++i) { std::string line; std::getline(input, line); } } numshapes++; shape newshape(shapenum, numparts, numpoints); for (long i = 0; i < numparts + numpoints - 1; ++i) { std::string line; std::getline(input, line); if (line.compare("part") == 0) newshape.new_part(); else { double xdbl, ydbl; long x, y; std::istringstream foo(line); foo >> xdbl >> ydbl; x = (long) round(xdbl * PREC); y = (long) round(-ydbl * PREC); // These two lines are not equivalent to the above. Consider 232.73 // x = (long) (PREC * xdbl); // y = (long) (PREC * ydbl); paranoid_check(line, x, -y); minx = std::min(x, minx); miny = std::min(y, miny); maxx = std::max(x, maxx); maxy = std::max(y, maxy); newshape.add_point(x, y); } } newshape.optimize(); shapes.push_back(newshape); } void County::write_county(std::ostream &output, std::string name) const { if (numshapes == 0) throw(4); if (numshapes == 1) { output << "<path id=\"" << name << "\" d=\"" << *shapes.begin() << "\" />\n"; } else { #ifndef STATE output << "<g id=\"" << name << "\">\n"; #endif for (std::list<shape>::const_iterator i = shapes.begin(); i != shapes.end(); ++i) output << "<path id=\"" << i->get_shapenum() << "\" d=\"" << *i << "\" />\n"; #ifndef STATE output << "</g>\n"; #endif } } void write_output_file(const std::map<std::string, County>& counties, std::ostream &output) { long width = County::get_width(); long height = County::get_height(); long scalewidth = width; long widthremainder = 0; long scaleheight = height; long heightremainder = 0; long decpos = 1; int digs = 0; while (scalewidth > 12800 && scaleheight > 10240) { widthremainder += decpos * (scalewidth % 10); scalewidth /= 10; heightremainder += decpos * (scaleheight % 10); scaleheight /= 10; decpos *= 10; ++digs; } long thick_line = (long) (0.007 * width); long thin_line = (long) (0.0016 * width); #ifndef STATE output << "<?xml version=\"1.0\" standalone=\"yes\"?>\n" "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n" "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" width=\"" << scalewidth << "." << std::setfill('0') << std::setw(digs) << widthremainder << std::setw(0) << "\" height=\"" << scaleheight << "." << std::setw(digs) << heightremainder << std::setw(0) << "\" viewBox=\"" << County::get_x() << ',' << County::get_y() << ' ' << width << ',' << height << "\">\n" << "<clipPath id=\"state_clip_path\">\n" "<g id=\"state_outline\">\n" "STATEHERE\n" "</g>\n" "</clipPath>\n" "<g stroke=\"black\" fill=\"none\" stroke-linejoin=\"round\" stroke-width=\"" << thin_line << "\" clip-path=\"url(#state_clip_path)\">\n" "<use xlink:href=\"#state_outline\" fill=\"white\" stroke-width=\"" << thick_line << "\" />\n"; #endif for (std::map<std::string, County>::const_iterator i = counties.begin(); i != counties.end(); ++i) i->second.write_county(output, i->first); #ifndef STATE output << "</g>\n" "<use xlink:href=\"#FOO\" stroke=\"none\" fill=\"red\" />\n" "</svg>\n"; #endif } int main(int argc, char **argv) { std::ifstream shapes_f(argv[1], std::ios::in); std::ifstream nums_f(argv[2], std::ios::in); std::ifstream names_f(argv[3], std::ios::in); long names_line = 0; std::map<std::string, County> counties; std::string line; std::getline(shapes_f, line); if (line.compare("type polygon") != 0) { std::cerr << "I can only deal with polygons right now\n"; throw (1); } while (!nums_f.eof()) { long shapenum; std::string name; nums_f >> shapenum; if (nums_f.eof()) break; while (shapenum > names_line) { std::getline(names_f, name); ++names_line; } if (name.empty()) std::cerr << "Ignoring unnamed shape " << shapenum << '\n'; else counties[name].read_shape(shapes_f, shapenum); } write_output_file(counties, std::cout); return 0; }