root/trunk/clustr/clustr.cpp

Revision 617, 9.5 kB (checked in by aaron, 8 months ago)

update to 0.21: patch from mattb to make multi-tags files work

Line 
1 /*
2 Clustr.  Copyright (c) 2007-2009 Yahoo! Inc.
3
4 All rights reserved.  This code is free software; you can redistribute it
5 and/or modify it under the terms of the GNU General Public License (GPL),
6 version 2 only.  This code is distributed WITHOUT ANY WARRANTY, whether
7 express or implied. See the GNU GPL for more details
8 (http://www.gnu.org/licenses/gpl.html)
9
10 AS A SPECIAL EXCEPTION, YOU HAVE PERMISSION TO LINK THIS PROGRAM WITH THE
11 CGAL LIBRARY AND DISTRIBUTE EXECUTABLES, AS LONG AS YOU FOLLOW THE REQUIREMENTS
12 OF THE GNU GPL VERSION 2 IN REGARD TO ALL OF THE SOFTWARE IN THE EXECUTABLE
13 ASIDE FROM CGAL.
14 */
15
16 #include <iostream>
17 #include <fstream>
18 #include <sstream>
19 #include <vector>
20 #include <list>
21 #include <stdexcept>
22 #include <cmath>
23 #include <algorithm>
24
25 #include <unistd.h>
26 #include "clustr.h"
27 #include "shapefile.h"
28
29 using namespace std;
30 using namespace Clustr;
31
32 void write_points_to_shapefile (Shapefile &shape, vector<Point> &pts,
33                                 string text) {
34     vector<Point>::iterator begin = pts.begin(),
35                             end   = pts.end();
36     for (vector<Point>::iterator it = begin; it != end; it++) {
37         Feature pt_feat(shape);
38         Geometry pt_geom(wkbPoint);
39         pt_geom.push_back(*it);
40         pt_feat.set(pt_geom);
41         pt_feat.set("tag", text.c_str());
42         shape.add_feature(pt_feat);
43     }
44 }
45
46 void write_polygon_to_shapefile (Shapefile &shape, Polygon &poly, string text, long int count) {
47     Feature feat(shape);
48     Geometry geom(wkbPolygon);
49     geom.insert_rings(poly.begin(), poly.end());
50     feat.set(geom);
51     feat.set("tag", text.c_str());
52     feat.set("count", count);
53     feat.set("area", poly.area());
54     feat.set("perimeter", poly.perimeter());
55     feat.set("density", ((float)count)/poly.area());
56     shape.add_feature(feat);
57 }
58
59 bool extract_alpha_component (Mesh::component &c, Polygon &poly, bool verbose)
60 {
61     Mesh::vertex_circulator v(c);
62     Vertex_handle v0(*v);
63     Ring ring;
64
65     do {
66         ring.push_back(v->point());
67     } while (*(++v) != v0);
68
69     if (ring.size() < 4) {
70         if (verbose) cerr << "Discarding degenerate shape." << endl;
71         return false;
72     }
73     if (verbose)
74         cerr << "- " << ring.size() << " vertices, "
75              << "area: " << fabs(ring.area())
76              << ", perimeter: " << ring.perimeter() << endl;
77
78     poly.push_back(ring);
79     return true;
80 }
81
82 bool compare_polygon_sizes (const Polygon &a, const Polygon &b) {
83     return a.size() < b.size();
84 }
85
86 void attach_interior_rings (vector<Polygon> &input, vector<Polygon> &output, bool verbose = false) {
87     unsigned int count = 0;
88
89     /* First, sort the polygons by area. This guarantees that smaller polygons
90      * will wind up inside larger polygons. */
91     sort(input.begin(), input.end(), compare_polygon_sizes);
92
93     for (vector<Polygon>::iterator it = input.begin(); it != input.end(); it++) {
94         bool is_interior_ring = false;
95
96         for (vector<Polygon>::iterator comp = it + 1; comp != input.end(); comp++) {
97             /* check if the first point in the first ring of *it is inside the outer ring
98              * of *comp. If it is, then we assume that the entire polygon *it is actually
99              * inside *comp.
100              *
101              * Note that *comp needs to be a simple polygon -- it *should* be but sometimes
102              * the polygons that come out of the alpha shape self-intersect at a point.
103              * Not sure what to do about this. */
104             if ((*comp)[0].is_simple() && (*comp)[0].encloses((*it)[0][0])) {
105                 for (Polygon::iterator ring = it->begin(); ring != it->end(); ring++) {
106                     // this is actually going to get double-interior rings backwards,
107                     // but fixing that means making the logic in
108                     // Polygon::push_back a lot more sophisticated...
109                     comp->push_back(*ring);
110                 }
111                 is_interior_ring = true;
112                 count++;
113                 break;
114             }
115         }
116
117         if (!is_interior_ring)
118             output.push_back(*it);
119     }
120
121     if (verbose && count)
122         cerr << count << " interior rings attached." << endl;
123 }
124
125 void construct_alpha_shape (Config &config, Shapefile &shape,
126                             vector<Point> &pts, string text)
127 {
128     coord_type alpha = config.alpha;
129     Mesh mesh(pts.begin(), pts.end(), alpha);
130     vector<Polygon> polygons, output;
131
132     if (alpha == 0) {
133         if (config.verbose) cerr << "Computing optimal alpha... ";
134         alpha = *mesh.find_optimal_alpha(1);
135         if (config.verbose) cerr << alpha << endl;
136         mesh.set_alpha(alpha);
137     }
138
139     if (config.verbose)
140         cerr << mesh.number_of_solid_components() <<
141               " component(s) found for alpha value " << alpha << "." << endl;
142
143     for (Mesh::component c = mesh.components_begin(); c != mesh.components_end(); c++) {
144         Polygon poly;
145         if (extract_alpha_component(c, poly, config.verbose))
146             polygons.push_back(poly);
147     }
148
149     attach_interior_rings(polygons, output, config.verbose);
150
151     if (config.verbose) cerr << "Writing " << output.size() << " polygons to shapefile." << endl;
152     for (vector<Polygon>::iterator poly = output.begin(); poly != output.end(); poly++) {
153         write_polygon_to_shapefile(shape, *poly, text, pts.size()); // FIXME: size is broken
154     }
155 }
156
157 void construct_output (Config &config, Shapefile &shape,
158                        vector<Point> &pts, string tag) {
159     cerr << "Got " << pts.size() << " points for tag '"
160                    << tag << "'." << endl;
161     if (config.points_only) {
162         write_points_to_shapefile(shape, pts, tag);
163     } else {
164         if (pts.size() >= 3) {
165             construct_alpha_shape(config, shape, pts, tag);
166         } else {
167             if (config.verbose)
168                 cerr << "Need more than 3 points for tag '"
169                      << tag << "'" << endl;
170         }
171     }
172 }
173
174 void display_usage (void) {
175     cerr << endl
176          << "clustr "  << VERSION << " - construct polygons from tagged points"
177          << endl
178          << "written by " << AUTHOR << endl
179          << COPYRIGHT  << endl
180          << endl
181          << "Usage: clustr [-a <n>] [-p] [-v] <input> <output>" << endl
182          << "   -h, -?      this help message" << endl
183          << "   -v          be verbose (default: off)" << endl
184          << "   -a <n>      set alpha value (default: use \"optimal\" value)"
185          << endl
186          << "   -p          output points to shapefile, instead of polygons"
187          << endl
188          << endl
189          << "If <input> is missing or given as \"-\", stdin is used."
190          << endl
191          << "If <output> is missing, output is written to clustr.shp."
192          << endl
193          << "Input file should be formatted as: <tag> <lon> <lat>\\n" << endl
194          << "Tags must not contain spaces." << endl
195          << endl;
196 }
197
198 bool parse_options (Config &config, int argc, char **argv) {
199     istringstream iss;
200     string optstring("a:pvh?");
201
202     if (argc <= 1) {
203         display_usage();
204         return false;
205     }
206
207     int opt = getopt( argc, argv, optstring.c_str() );
208     while( opt != -1 ) {
209         switch( opt ) {
210             case 'a':
211                 iss.str((string) optarg);
212                 iss >> config.alpha;
213                 break;
214             case 'p':
215                 config.points_only = true;
216                 break;
217             case 'v':
218                 config.verbose = true;
219                 break;
220             case 'h':   /* fall-through is intentional */
221             case '?':
222                 display_usage();
223                 return false;
224         }
225         opt = getopt( argc, argv, optstring.c_str() );
226     }
227     if (optind < argc) {
228         config.in_file = argv[optind++];
229     }
230     if (optind < argc) {
231         config.out_file = argv[optind];
232     }
233     return true;
234 }
235
236 int main(int argc, char **argv) {
237     vector<Point> points;
238     istream *input;
239     Config config;
240     Shapefile *shape;
241
242     if (!parse_options(config, argc, argv))
243         return 1;
244
245     if (config.in_file == "-") {
246         input = &cin;
247     } else {
248         input = new ifstream(config.in_file.c_str());
249     }
250    
251     if (config.points_only) {
252         if (config.verbose) {
253             cerr << "Only converting points to Shapefile,"
254                  << " no clustering performed." << endl;
255         }
256         shape = new Shapefile(config.out_file, wkbPoint);
257         shape->add_field("tag", OFTString, 64);
258        
259     } else {
260         shape = new Shapefile(config.out_file, wkbPolygon);
261         shape->add_field("tag", OFTString, 64);
262         shape->add_field("count", OFTInteger, 8);
263         shape->add_field("area", OFTReal, 10, 2);
264         shape->add_field("perimeter", OFTReal, 10, 2);
265         shape->add_field("density", OFTReal, 10, 2);
266     }
267
268     string line, tag, previous = "";
269     vector<Point> pts;
270     Point pt;
271    
272     if (config.verbose)
273         cerr << "Reading points from input." << endl;
274
275     while (!getline(*input,line).eof()) {
276         istringstream is(line);
277         if (!(is >> tag >> pt)) {
278             cerr << "Bad input line: " << line;
279             continue;
280         }
281         if (previous == "") {
282             previous = tag;
283         }
284         if (tag != previous) {
285             construct_output(config, *shape, pts, previous);
286             previous = tag;
287             pts.clear();
288         }
289         pts.push_back(pt);
290     }
291     construct_output(config, *shape, pts, tag);
292    
293     // if you want to do memory leak testing, include <ogr_api.h> and
294     // uncomment the following line:
295     // OGRCleanupAll();
296     delete shape;
297     return 0;
298 }
299
Note: See TracBrowser for help on using the browser.