blib/lib/GD/Map/Mercator.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 7 | 9 | 77.7 |
branch | n/a | ||
condition | n/a | ||
subroutine | 3 | 3 | 100.0 |
pod | n/a | ||
total | 10 | 12 | 83.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | =pod | ||||||
2 | |||||||
3 | =begin classdoc | ||||||
4 | |||||||
5 | Renders map images using a Mercator projection | ||||||
6 | applied to GIS datasets available from | ||||||
7 | CIA World DataBank II project. | ||||||
8 |
|
||||||
9 | Copyright© 2008, Dean Arnold, Presicient Corp., USA | ||||||
10 |
|
||||||
11 | Permission is granted to use, copy, modify, and redistribute this software | ||||||
12 | under the terms of the | ||||||
13 | Perl Artistic License. | ||||||
14 | |||||||
15 | @author D. Arnold | ||||||
16 | @since 2008-Jan-29 | ||||||
17 | @see |
||||||
18 | @see |
||||||
19 | |||||||
20 | =end classdoc | ||||||
21 | |||||||
22 | =cut | ||||||
23 | |||||||
24 | package GD::Map::Mercator; | ||||||
25 | |||||||
26 | 1 | 1 | 40645 | use strict; | |||
1 | 2 | ||||||
1 | 232 | ||||||
27 | 1 | 1 | 7 | use warnings; | |||
1 | 3 | ||||||
1 | 34 | ||||||
28 | |||||||
29 | 1 | 1 | 989 | use GD; | |||
0 | |||||||
0 | |||||||
30 | use GD::Polyline; | ||||||
31 | |||||||
32 | our $VERSION = '1.03'; | ||||||
33 | |||||||
34 | |||||||
35 | # | ||||||
36 | # keep map of regions to lat/long bounding boxes, | ||||||
37 | # so we can optimize dataset loading | ||||||
38 | # | ||||||
39 | our %regions = ( | ||||||
40 | 'africa-bdy' => [ -30.658889, -17.075556, 41.594722,53.089722], | ||||||
41 | 'africa-cil' => [ -54.462778, -25.360556, 65.650278,77.588889], | ||||||
42 | 'africa-riv' => [ -34.400278, -16.708611, 42.038056,55.2525], | ||||||
43 | 'asia-bdy' => [ -9.126944, 19.001389, 54.476667,141.008889], | ||||||
44 | 'asia-cil' => [ -54.753889, -190.351944, 81.851944,180.0], | ||||||
45 | 'asia-riv' => [ -46.569722, -179.988056, 74.412222,180.0], | ||||||
46 | 'europe-bdy' => [ 36.151389, -8.751389, 70.088889,31.586667], | ||||||
47 | 'europe-cil' => [ 34.808889, -31.29, 71.165000,31.250278], | ||||||
48 | 'europe-riv' => [ 36.566111, -21.791944, 69.999722,29.468889], | ||||||
49 | 'namer-bdy' => [ 41.675556, -141.003056, 69.645556,-66.901111], | ||||||
50 | 'namer-cil' => [ 24.538333, -168.132778, 83.623611,-12.155], | ||||||
51 | 'namer-pby' => [ 29.706944, -139.047778, 68.900556,-57.1], | ||||||
52 | 'namer-riv' => [ 25.768333, -166.053056, 74.032222,-54.470833], | ||||||
53 | 'samer-bdy' => [ -55.556389, -117.1225, 32.718333,-51.682778], | ||||||
54 | 'samer-cil' => [ -85.470278, -179.987778, 33.003333,179.976111], | ||||||
55 | 'samer-riv' => [ -52.733333, -117.126111, 32.718333,-34.917222], | ||||||
56 | ); | ||||||
57 | |||||||
58 | our %datasets = ( | ||||||
59 | africa => [ 'bdy', 'cil', 'riv' ], | ||||||
60 | asia => [ 'bdy', 'cil', 'riv' ], | ||||||
61 | europe => [ 'bdy', 'cil', 'riv' ], | ||||||
62 | namer => [ 'bdy', 'cil', 'riv', 'pby' ], | ||||||
63 | samer => [ 'bdy', 'cil', 'riv' ], | ||||||
64 | ); | ||||||
65 | |||||||
66 | our %colors = ( | ||||||
67 | white => [255,255,255], | ||||||
68 | lgray => [191,191,191], | ||||||
69 | gray => [127,127,127], | ||||||
70 | dgray => [63,63,63], | ||||||
71 | black => [0,0,0], | ||||||
72 | lblue => [0,0,255], | ||||||
73 | blue => [0,0,191], | ||||||
74 | dblue => [0,0,127], | ||||||
75 | gold => [255,215,0], | ||||||
76 | lyellow => [255,255,0], | ||||||
77 | yellow => [191,191,0], | ||||||
78 | dyellow => [127,127,0], | ||||||
79 | lgreen => [0,255,0], | ||||||
80 | green => [0,191,0], | ||||||
81 | dgreen => [0,127,0], | ||||||
82 | lred => [255,0,0], | ||||||
83 | red => [191,0,0], | ||||||
84 | dred => [127,0,0], | ||||||
85 | lpurple => [255,0,255], | ||||||
86 | purple => [191,0,191], | ||||||
87 | dpurple => [127,0,127], | ||||||
88 | lorange => [255,183,0], | ||||||
89 | orange => [255,127,0], | ||||||
90 | pink => [255,183,193], | ||||||
91 | dpink => [255,105,180], | ||||||
92 | marine => [127,127,255], | ||||||
93 | cyan => [0,255,255], | ||||||
94 | lbrown => [210,180,140], | ||||||
95 | dbrown => [165,42,42], | ||||||
96 | transparent => [1,1,1] | ||||||
97 | ); | ||||||
98 | |||||||
99 | my %valid_fmt = qw(png newFromPng gif newFromGif jpg newFromJpeg jpeg newFromJpeg); | ||||||
100 | |||||||
101 | =pod | ||||||
102 | |||||||
103 | =begin classdoc | ||||||
104 | |||||||
105 | @constructor | ||||||
106 | |||||||
107 | Create an instance of GD::Map::Mercator. Either creates a | ||||||
108 | new basemap image from the specified minimum/maximum latitude/longitude | ||||||
109 | values, or loads an existing basemap of the given name. Applies | ||||||
110 | the Mercator Projection | ||||||
111 | to datapoints collected from the | ||||||
112 | CIA World Databank II dataset | ||||||
113 | to render a map image. | ||||||
114 |
|
||||||
115 | The map region to be rendered is specified by providing a set of | ||||||
116 | minimum/maximum latitude/longitude values (in degrees) defining the | ||||||
117 | bounding box of the region to be mapped. If the bounding box coordinates | ||||||
118 | are not specified, this object attempts to load a pre-existing | ||||||
119 | map image and data. When a map is rendered, an additional file | ||||||
120 | of configuration data is saved containing the bounding box coordinates | ||||||
121 | in both latitude/longitude and Mercator distances (in meters). | ||||||
122 |
|
||||||
123 | Once the base map has been created or loaded, the application may | ||||||
124 | use this object to | ||||||
125 | |||||||
126 | |
||||||
127 | |
||||||
128 | |
||||||
129 | within the image | ||||||
130 | |
||||||
131 | |
||||||
132 | |
||||||
133 | |||||||
134 |
|
||||||
135 | |||||||
136 | NOTES: | ||||||
137 | |
||||||
138 | |
||||||
139 | distortions near the poles. Use of map coordinates above 70 degrees or | ||||||
140 | below -70 degrees latitude is discouraged. | ||||||
141 | |||||||
142 | |
||||||
143 | +90 and -90, where negative values are in the southern hemisphere; | ||||||
144 | longitude values are in degrees between +180 and -180, with negative values | ||||||
145 | in the western hemisphere. | ||||||
146 | |||||||
147 | |
||||||
148 | by |
||||||
149 | using this package. | ||||||
150 | |||||||
151 | |||||||
152 | @optional basemap_path directory path for basemap image and datafile. If a | ||||||
153 | new map is generated, its image and config files will ba saved in this | ||||||
154 | path; if using an existing map, its image and config files are loaded | ||||||
155 | from this path. Note that, if creating a new map, this parameter is optional. | ||||||
156 | |||||||
157 | @optional basemap_name filename of basemap image and data; may contain | ||||||
158 | the image format suffix ('.png', '.gif', '.jpg', '.jpeg'); if not | ||||||
159 | suffixed, defaults to '.png'. The config data for the map is stored | ||||||
160 | in a file of the same name, but with the format suffix replaced by | ||||||
161 | '.conf'. Note that, if creating a new map, this parameter is optional. | ||||||
162 | |||||||
163 | @optional data_path directory path to the WDB data files; required if a new basemap | ||||||
164 | is being rendered, or a submap may be extracted/scaled. | ||||||
165 | |||||||
166 | @optional min_lat minimum latitude in degrees; may be fractional. If not specified, | ||||||
167 | and no other latitude/longitude values are specified, this object attempts to | ||||||
168 | load a pre-existing basemap. Dies if not specified when other lat/long values | ||||||
169 | are specified. | ||||||
170 | @optional min_long minimum longitude in degrees; same rules apply as for min_lat |
||||||
171 | @optional max_lat maximum latitude in degrees; same rules apply as for min_lat |
||||||
172 | @optional max_long maximum longitude in degrees; same rules apply as for min_lat |
||||||
173 | Note that max_long may be negative, and min_long postitive, in which case | ||||||
174 | the rendered basemap will span the antipodal meridian (i.e., where longitude crosses from +180 | ||||||
175 | to -180) | ||||||
176 | @optional width width of the basemap image in pixels; default 400. This | ||||||
177 | parameter is actually an upper bound, as the process of rendering adjusts the | ||||||
178 | image width and height to properly reflect the scaling of the Mercator | ||||||
179 | projection. | ||||||
180 | @optional height height of the basemap image in pixels; default 400. This | ||||||
181 | parameter is actually an upper bound, as the process of rendering adjusts the | ||||||
182 | image width and height to properly reflect the scaling of the Mercator | ||||||
183 | projection. | ||||||
184 | @optional background background color of basemap image; default 'white'. May be either | ||||||
185 | a named color supported by |
||||||
186 | @optional foreground foreground color of basemap image (i.e., color of lines drawn); | ||||||
187 | default 'black'. May be either a named color supported by |
||||||
188 | or an arrayref of RGB values. | ||||||
189 | @optional thickness thickness of lines in pixels; default 2 | ||||||
190 | @optional omit arrayref of dataset types to omit. Each continent may have | ||||||
191 | any of 'bdy' (body), 'cli' (coast/islands), 'pby' (political boundry), or | ||||||
192 | 'riv' (rivers) datasets. To omit one or more of them, include them | ||||||
193 | in this omit list, e.g., omit => [ 'riv' ] omits rivers. | ||||||
194 | @optional keep hashref mapping dataset types to an arrayref of | ||||||
195 | latitude, longitude bounding box coordinates. Useful for filtering, | ||||||
196 | e.g., all coasts/islands internal to a region, but keeping | ||||||
197 | ocean coastal regions. | ||||||
198 | @optional save_coords name of file to write pixel coordinates of the rendered | ||||||
199 | image segments. Useful for e.g., creating HTML imagemaps, etc. | ||||||
200 | @optional silent if true, no progress or diagnostic information is emitted; | ||||||
201 | default false | ||||||
202 | |||||||
203 | @returns an instance of GD::Map::Mercator | ||||||
204 | |||||||
205 | =end classdoc | ||||||
206 | |||||||
207 | =cut | ||||||
208 | |||||||
209 | sub new { | ||||||
210 | my ($class, %opts) = @_; | ||||||
211 | |||||||
212 | if ($opts{data_path}) { | ||||||
213 | die "data_path $opts{data_path} not found" | ||||||
214 | unless(-d $opts{data_path}); | ||||||
215 | |||||||
216 | $opts{data_path} .= '/' | ||||||
217 | unless (substr($opts{data_path}, -1) eq '/'); | ||||||
218 | } | ||||||
219 | |||||||
220 | if ($opts{basemap_path}) { | ||||||
221 | die "basemap_path $opts{basemap_path} not found." | ||||||
222 | unless(-d $opts{basemap_path}); | ||||||
223 | |||||||
224 | $opts{basemap_path} .= '/' | ||||||
225 | unless (substr($opts{basemap_path}, -1) eq '/'); | ||||||
226 | } | ||||||
227 | else { | ||||||
228 | $opts{basemap_path} = ''; | ||||||
229 | } | ||||||
230 | |||||||
231 | my ($name, $fmt) = ('', 'png'); | ||||||
232 | if ($opts{basemap_name}) { | ||||||
233 | $opts{basemap_name} .= '.png' | ||||||
234 | unless $opts{basemap_name}=~/\.(?:png|gif|jpg|jpeg)$/; | ||||||
235 | |||||||
236 | ($name, $fmt) = ($opts{basemap_name}=~/^(.+)\.(png|gif|jpg|jpeg)$/); | ||||||
237 | } | ||||||
238 | |||||||
239 | $opts{silent} = 0 unless exists $opts{silent}; | ||||||
240 | my $self = { | ||||||
241 | data_path => $opts{data_path}, | ||||||
242 | basemap_path => $opts{basemap_path}, | ||||||
243 | basemap_loc => $opts{basemap_path} . $name, | ||||||
244 | imgfmt => $fmt, | ||||||
245 | verbose => !$opts{silent}, | ||||||
246 | thickness => $opts{thickness} || 2, | ||||||
247 | foreground => $opts{foreground} || 'black', | ||||||
248 | background => $opts{background} || 'white', | ||||||
249 | keeps => $opts{keep} || {}, | ||||||
250 | omit => $opts{omit}, | ||||||
251 | save_coords => $opts{save_coords}, | ||||||
252 | }; | ||||||
253 | bless $self, $class; | ||||||
254 | # | ||||||
255 | # if min/max info is provided, create a new basemap | ||||||
256 | # else attempt to load existing basemap | ||||||
257 | # | ||||||
258 | my $haspts = 0; | ||||||
259 | $haspts += defined($opts{$_}) ? 1 : 0 | ||||||
260 | foreach (qw(min_lat min_long max_lat max_long)); | ||||||
261 | |||||||
262 | die "Incomplete latitude/longitude datapoints provided." | ||||||
263 | if ($haspts > 0) && ($haspts < 4); | ||||||
264 | # | ||||||
265 | # attempt to load existing basemap; dies on any error | ||||||
266 | # | ||||||
267 | die "No basemap path or coordinates provided." | ||||||
268 | unless $haspts || ($self->{basemap_path} && $self->{basemap_loc}); | ||||||
269 | |||||||
270 | return $self->_load_basemap() | ||||||
271 | unless $haspts; | ||||||
272 | # | ||||||
273 | # create a new basemap (maybe we should go ahead and check for a matching | ||||||
274 | # existing basemap ?) | ||||||
275 | # | ||||||
276 | die "No data_path specified." | ||||||
277 | unless $self->{data_path}; | ||||||
278 | |||||||
279 | my ($minlat, $minlong, $maxlat, $maxlong, $width, $height) = | ||||||
280 | ($opts{min_lat}, $opts{min_long}, | ||||||
281 | $opts{max_lat}, $opts{max_long}, | ||||||
282 | $opts{width} || 400, $opts{height} || 400); | ||||||
283 | |||||||
284 | my $mercator; | ||||||
285 | $self->{mercator} = $mercator = GD::Map::Mercator::Projector->new( | ||||||
286 | $minlat, $minlong, $maxlat, $maxlong, $width, $height, $self->{verbose}); | ||||||
287 | |||||||
288 | my ($bg, $fg, $linew) = @$self{qw(background foreground thickness)}; | ||||||
289 | $| = 1 if $self->{verbose}; | ||||||
290 | # | ||||||
291 | # create empty image before loading data | ||||||
292 | # Note that image dimensions are adjusted by the projector | ||||||
293 | # | ||||||
294 | print "Creating GD image ($width x $height)\n" | ||||||
295 | if $self->{verbose}; | ||||||
296 | ($width, $height) = $mercator->dimensions(); | ||||||
297 | my $img = $self->{image} = GD::Image->new($width, $height); | ||||||
298 | $self->{fg} = ref $fg ? $img->colorAllocate(@$fg) : | ||||||
299 | ($fg=~/^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) | ||||||
300 | ? $img->colorAllocate(hex($1), hex($2), hex($3)) | ||||||
301 | : $img->colorAllocate(@{$colors{$fg}}); | ||||||
302 | $self->{bg} = ref $bg ? $img->colorAllocate(@$bg) : | ||||||
303 | ($bg=~/^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) | ||||||
304 | ? $img->colorAllocate(hex($1), hex($2), hex($3)) | ||||||
305 | : $img->colorAllocate(@{$colors{$bg}}); | ||||||
306 | $img->filledRectangle(0,0,$width-1,$height-1,$self->{bg}); | ||||||
307 | $img->setThickness($linew); | ||||||
308 | |||||||
309 | my @xy; | ||||||
310 | my $seg = []; | ||||||
311 | # | ||||||
312 | # check which of the datasets we need | ||||||
313 | # | ||||||
314 | my @regions = (); | ||||||
315 | my %omits = (); | ||||||
316 | map $omits{$_} = 1, @{$opts{omit}} | ||||||
317 | if $opts{omit}; | ||||||
318 | |||||||
319 | foreach (keys %regions) { | ||||||
320 | if ((!$omits{substr($_, -3)}) && | ||||||
321 | ( | ||||||
322 | (($regions{$_}[0] <= $minlat) && | ||||||
323 | ($regions{$_}[2] >= $minlat)) || | ||||||
324 | (($regions{$_}[0] <= $maxlat) && | ||||||
325 | ($regions{$_}[2] >= $maxlat)) || | ||||||
326 | (($regions{$_}[0] >= $minlat) && | ||||||
327 | ($regions{$_}[2] <= $maxlat)) | ||||||
328 | ) && | ||||||
329 | ( | ||||||
330 | (($regions{$_}[1] <= $minlong) && | ||||||
331 | ($regions{$_}[3] >= $minlong)) || | ||||||
332 | (($regions{$_}[1] <= $maxlong) && | ||||||
333 | ($regions{$_}[3] >= $maxlong)) || | ||||||
334 | (($regions{$_}[1] >= $minlong) && | ||||||
335 | ($regions{$_}[3] <= $maxlong)) | ||||||
336 | )) { | ||||||
337 | print "Using $_\n" if $self->{verbose}; | ||||||
338 | push(@regions, $_); | ||||||
339 | } | ||||||
340 | } | ||||||
341 | # open each of the data files | ||||||
342 | my $oldfd = select(STDOUT); | ||||||
343 | $| = 1; | ||||||
344 | select $oldfd; | ||||||
345 | foreach (@regions) { | ||||||
346 | my $datapath = "$opts{data_path}$_.bin"; | ||||||
347 | print "\nLoading datafile $datapath\n" | ||||||
348 | if $self->{verbose}; | ||||||
349 | my $fd; | ||||||
350 | print "Skipping $datapath ($!)\n" and next | ||||||
351 | unless open $fd, $datapath; | ||||||
352 | binmode $fd; | ||||||
353 | $mercator->filter($fd, $self, $_, $self->{keep}{$_}); | ||||||
354 | close $fd; | ||||||
355 | } | ||||||
356 | |||||||
357 | return $self->{basemap_loc} | ||||||
358 | ? $self->save("$self->{basemap_loc}.$self->{imgfmt}") | ||||||
359 | : $self; | ||||||
360 | } | ||||||
361 | |||||||
362 | =pod | ||||||
363 | |||||||
364 | =begin classdoc | ||||||
365 | |||||||
366 | Return this object's configuration information. | ||||||
367 | |||||||
368 | @returnlist a list of the latitude/longitude bounding box coordinates (in degrees), | ||||||
369 | the Mercator distance bounding box coordinates (in meters), and the final | ||||||
370 | width and height (in pixels) of the associated image, i.e., | ||||||
371 | |
||||||
372 | ($minlat, $minlong, $maxlat, $maxlong, | ||||||
373 | $minmerclong, $minmerclat, $maxmerclong, $maxmerclat, | ||||||
374 | $width, $height) | ||||||
375 | |||||||
376 | |||||||
377 | =end classdoc | ||||||
378 | |||||||
379 | =cut | ||||||
380 | |||||||
381 | sub config { | ||||||
382 | return $_[0]->{mercator}->config(); | ||||||
383 | } | ||||||
384 | |||||||
385 | =pod | ||||||
386 | |||||||
387 | =begin classdoc | ||||||
388 | |||||||
389 | Extract a submap from this object's basemap. | ||||||
390 | Given latitude/longitude bounding box coordinates, | ||||||
391 | creates a new GD::Map::Mercator object with data and | ||||||
392 | image contained by the specified bounding box. The extracted | ||||||
393 | map may optionally be scaled. | ||||||
394 |
|
||||||
395 | Note that any saved coordinates | ||||||
396 | will be lost unless a scale operation is applied to regenerate | ||||||
397 | them. | ||||||
398 | |||||||
399 | @param $minlat minimum latitude of bounding box | ||||||
400 | @param $minlong minimum longitude of bounding box | ||||||
401 | @param $maxlat maximum latitude of bounding box | ||||||
402 | @param $maxlong maximum longitude of bounding box | ||||||
403 | @optional $scale any scaling to be applied to the submap | ||||||
404 | |||||||
405 | @returns a new GD::Map::Mercator object | ||||||
406 | |||||||
407 | =end classdoc | ||||||
408 | |||||||
409 | =cut | ||||||
410 | |||||||
411 | sub extract { | ||||||
412 | my ($self, $minlat, $minlong, $maxlat, $maxlong, $scale) = @_; | ||||||
413 | |||||||
414 | my @coords = $self->{mercator}->config(); | ||||||
415 | |||||||
416 | $@ = 'Specified region outside the bounds of this map.', | ||||||
417 | return undef | ||||||
418 | if ($minlat < $coords[0]) || ($maxlat > $coords[2]) || | ||||||
419 | ($minlong < $coords[1]) || ($maxlong > $coords[3]); | ||||||
420 | |||||||
421 | my ($xmin, $ymin, $xminmerc, $yminmerc) = $self->{mercator}->project($minlat, $minlong); | ||||||
422 | my ($xmax, $ymax, $xmaxmerc, $ymaxmerc) = $self->{mercator}->project($maxlat, $maxlong); | ||||||
423 | my ($width, $height) = (($xmax - $xmin + 1), ($ymax - $ymin + 1)); | ||||||
424 | |||||||
425 | my $class = ref $self; | ||||||
426 | if ($scale) { | ||||||
427 | # | ||||||
428 | # just create a new map from scratch that scales the selected region | ||||||
429 | # | ||||||
430 | $width *= $scale; | ||||||
431 | $height *= $scale; | ||||||
432 | |||||||
433 | return ${class}->new( | ||||||
434 | data_path => $self->{data_path}, | ||||||
435 | min_lat => $minlat, | ||||||
436 | min_long => $minlong, | ||||||
437 | max_lat => $maxlat, | ||||||
438 | max_long => $maxlong, | ||||||
439 | width => $width, | ||||||
440 | height => $height, | ||||||
441 | verbose => $self->{verbose}, | ||||||
442 | thickness => $self->{thickness}, | ||||||
443 | foreground => $self->{foreground}, | ||||||
444 | background => $self->{background}, | ||||||
445 | keep => $self->{keep}, | ||||||
446 | omit => $self->{omit}, | ||||||
447 | save_coords => $self->{save_coords}, | ||||||
448 | ); | ||||||
449 | } | ||||||
450 | |||||||
451 | my $newmap = { | ||||||
452 | data_path => $self->{data_path}, | ||||||
453 | min_lat => $minlat, | ||||||
454 | min_long => $minlong, | ||||||
455 | max_lat => $maxlat, | ||||||
456 | max_long => $maxlong, | ||||||
457 | width => $width, | ||||||
458 | height => $height, | ||||||
459 | verbose => $self->{verbose}, | ||||||
460 | thickness => $self->{thickness}, | ||||||
461 | foreground => $self->{foreground}, | ||||||
462 | background => $self->{background}, | ||||||
463 | keep => $self->{keep}, | ||||||
464 | omit => $self->{omit}, | ||||||
465 | save_coords => $self->{save_coords}, | ||||||
466 | }; | ||||||
467 | $newmap->{mercator} = GD::Map::Mercator::Projector->new( | ||||||
468 | $minlat, $minlong, $maxlat, $maxlong, $width, $height, $self->{verbose}); | ||||||
469 | ($width, $height) = $newmap->{mercator}->dimensions(); | ||||||
470 | my ($fg, $bg) = @$self{qw(foreground background)}; | ||||||
471 | my $img = $newmap->{image} = GD::Image->new($width, $height); | ||||||
472 | $newmap->{fg} = ref $fg ? $img->colorAllocate(@$fg) : | ||||||
473 | ($fg=~/^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) | ||||||
474 | ? $img->colorAllocate(hex($1), hex($2), hex($3)) | ||||||
475 | : $img->colorAllocate(@{$colors{$fg}}); | ||||||
476 | $newmap->{bg} = ref $bg ? $img->colorAllocate(@$bg) : | ||||||
477 | ($bg=~/^#([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i) | ||||||
478 | ? $img->colorAllocate(hex($1), hex($2), hex($3)) | ||||||
479 | : $img->colorAllocate(@{$colors{$bg}}); | ||||||
480 | $img->filledRectangle(1,1,$width,$height,$newmap->{bg}); | ||||||
481 | $img->setThickness($newmap->{thickness}); | ||||||
482 | $newmap->{image}->copy($self->{image}, 0, 0, $xmax, $ymax, $width, $height); | ||||||
483 | |||||||
484 | return bless $newmap, $class; | ||||||
485 | } | ||||||
486 | |||||||
487 | =pod | ||||||
488 | |||||||
489 | =begin classdoc | ||||||
490 | |||||||
491 | Create a new scaled map from this object's basemap. | ||||||
492 | Causes a recalculation/rerendering of the map, in order | ||||||
493 | to properly render details on zoom-in scaling, or smooth | ||||||
494 | details on zom-out. | ||||||
495 | |||||||
496 | @param $scale scaling factor; the width and height of | ||||||
497 | this object's image are multiplied by this factor | ||||||
498 | to generate the new GD::Map::Mercator object | ||||||
499 | |||||||
500 | @returns a new GD::Map::Mercator object | ||||||
501 | |||||||
502 | =end classdoc | ||||||
503 | |||||||
504 | =cut | ||||||
505 | |||||||
506 | sub scale { | ||||||
507 | my ($self, $scale) = @_; | ||||||
508 | |||||||
509 | my $class = ref $self; | ||||||
510 | my @coords = $self->{mercator}->config(); | ||||||
511 | |||||||
512 | return ${class}->new( | ||||||
513 | data_path => $self->{data_path}, | ||||||
514 | min_lat => $coords[0], | ||||||
515 | min_long => $coords[1], | ||||||
516 | max_lat => $coords[2], | ||||||
517 | max_long => $coords[3], | ||||||
518 | width => $coords[-2] * $scale, | ||||||
519 | height => $coords[-1] * $scale, | ||||||
520 | silent => !$self->{verbose}, | ||||||
521 | thickness => $self->{thickness}, | ||||||
522 | foreground => $self->{foreground}, | ||||||
523 | background => $self->{background}, | ||||||
524 | keep => $self->{keep}, | ||||||
525 | omit => $self->{omit}, | ||||||
526 | save_coords => $self->{save_coords}, | ||||||
527 | ); | ||||||
528 | } | ||||||
529 | |||||||
530 | =pod | ||||||
531 | |||||||
532 | =begin classdoc | ||||||
533 | |||||||
534 | Return the pixel and Mercator distance coordinates for the | ||||||
535 | input latitude/longitude coordinates. Note that the input | ||||||
536 | coordinates do not need to | ||||||
537 | be within the bounding box of this object's map. | ||||||
538 |
|
||||||
539 | Multiple sets of input coordinates may be supplied, in which | ||||||
540 | case multiple coordinate results will be returned (4 output values | ||||||
541 | per input coordinate pair). | ||||||
542 | |||||||
543 | @param $latitude latitude of coordinate to project (in degrees) | ||||||
544 | @param $longitude longitude of coordinate to project (in degrees) | ||||||
545 | |||||||
546 | @returnlist a list of the (X,Y) pixel coordinate and the | ||||||
547 | (X,Y) Mercator distances (in meters) for each set of input | ||||||
548 | GIS coordinates. | ||||||
549 | |||||||
550 | =end classdoc | ||||||
551 | |||||||
552 | =cut | ||||||
553 | |||||||
554 | sub project { | ||||||
555 | my $self = shift; | ||||||
556 | return $self->{mercator}->project(@_); | ||||||
557 | } | ||||||
558 | |||||||
559 | =pod | ||||||
560 | |||||||
561 | =begin classdoc | ||||||
562 | |||||||
563 | Return the latitude/longitude and Mercator distance coordinates | ||||||
564 | for the input pixel coordinates. Note that the input coordinates do not need to | ||||||
565 | be within the bounding box of this object's map. | ||||||
566 |
|
||||||
567 | Multiple sets of input coordinates may be supplied, in which | ||||||
568 | case multiple coordinate results will be returned (4 output values | ||||||
569 | per input coordinate pair). | ||||||
570 | |||||||
571 | @param $x horizontal pixel coordinate to translate | ||||||
572 | @param $y vertical pixel coordinate to translate | ||||||
573 | |||||||
574 | @returnlist a list of the latitude/longitude coordinate (in degrees) and the | ||||||
575 | (X,Y) Mercator distances (in meters) for each set of input pixel coordinates. | ||||||
576 | |||||||
577 | =end classdoc | ||||||
578 | |||||||
579 | =cut | ||||||
580 | |||||||
581 | sub translate { | ||||||
582 | my $self = shift; | ||||||
583 | return $self->{mercator}->translate(@_); | ||||||
584 | } | ||||||
585 | |||||||
586 | =pod | ||||||
587 | |||||||
588 | =begin classdoc | ||||||
589 | |||||||
590 | Return this object's GD::Image object. | ||||||
591 | |||||||
592 | @return the GD::Image object for this object's map image | ||||||
593 | |||||||
594 | =end classdoc | ||||||
595 | |||||||
596 | =cut | ||||||
597 | |||||||
598 | sub image { return $_[0]->{image}; } | ||||||
599 | |||||||
600 | =pod | ||||||
601 | |||||||
602 | =begin classdoc | ||||||
603 | |||||||
604 | Write this object's image, configuration, and optional | ||||||
605 | coordinates map information | ||||||
606 | to the specified file. Useful for saving extracted or | ||||||
607 | scaled maps derived from basemaps. | ||||||
608 | |||||||
609 | @param $outpath a filepath specification, optionally | ||||||
610 | including the image filename with an image type | ||||||
611 | suffix of 'png', 'gif', 'jpg', or 'jpeg'. | ||||||
612 | |||||||
613 | @return undef on failure, with an error messge in $@ ; |
||||||
614 | this object on success | ||||||
615 | |||||||
616 | =end classdoc | ||||||
617 | |||||||
618 | =cut | ||||||
619 | |||||||
620 | sub save { | ||||||
621 | my ($self, $path) = @_; | ||||||
622 | |||||||
623 | my $fmt = (substr($path, -4, 1) eq '.') ? substr($path, -3) | ||||||
624 | : (substr($path, -5, 1) eq '.') ? substr($path, -4) | ||||||
625 | : undef; | ||||||
626 | my ($confpath, $imgpath, $coordpath) = ($path, $path, $path); | ||||||
627 | |||||||
628 | if ($fmt) { | ||||||
629 | $@ = "Invalid or unsupported image format $fmt", | ||||||
630 | return undef | ||||||
631 | unless $valid_fmt{$fmt} && $self->{image}->can($valid_fmt{$fmt}); | ||||||
632 | $confpath=~s/$fmt$/conf/; | ||||||
633 | $coordpath=~s/$fmt$/coords/; | ||||||
634 | } | ||||||
635 | else { | ||||||
636 | $confpath .= '.conf'; | ||||||
637 | $coordpath .= '.coords'; | ||||||
638 | $imgpath .= '.png'; | ||||||
639 | $fmt = 'png'; | ||||||
640 | } | ||||||
641 | |||||||
642 | die "Cannot open $imgpath: $!" unless open OUTF, ">$imgpath"; | ||||||
643 | binmode OUTF; | ||||||
644 | print "Writing $imgpath\n" | ||||||
645 | if $self->{verbose}; | ||||||
646 | print OUTF ($fmt eq 'png') ? $self->{image}->png | ||||||
647 | : ($fmt eq 'gif') ? $self->{image}->gif | ||||||
648 | : $self->{image}->jpeg; | ||||||
649 | close OUTF; | ||||||
650 | |||||||
651 | die "Cannot open $confpath: $!" | ||||||
652 | unless open OUTF, ">$confpath"; | ||||||
653 | # | ||||||
654 | # save as simple CSV, no D::D madness | ||||||
655 | # | ||||||
656 | print OUTF join(',', $self->{mercator}->config()), "\n"; | ||||||
657 | close OUTF; | ||||||
658 | |||||||
659 | if ($self->{save_coords}) { | ||||||
660 | die "Cannot open $coordpath: $!" | ||||||
661 | unless open OUTF, ">$coordpath"; | ||||||
662 | print OUTF $self->{_coords}; | ||||||
663 | close OUTF; | ||||||
664 | } | ||||||
665 | return $self; | ||||||
666 | } | ||||||
667 | |||||||
668 | =pod | ||||||
669 | |||||||
670 | =begin classdoc | ||||||
671 | |||||||
672 | Return the actual width and height of this object's map image. | ||||||
673 | Note that the returned values may be different than the originally | ||||||
674 | requested dimensions, due to scaling for Mercator projection. | ||||||
675 | |||||||
676 | @returnlist the actual width and height (in pixels) of this object's map image | ||||||
677 | |||||||
678 | =end classdoc | ||||||
679 | |||||||
680 | =cut | ||||||
681 | |||||||
682 | sub dimensions { | ||||||
683 | return $_[0]->{mercator}->dimensions(); | ||||||
684 | } | ||||||
685 | |||||||
686 | ############################ | ||||||
687 | # PRIVATE FUNCTIONS | ||||||
688 | ############################ | ||||||
689 | |||||||
690 | # | ||||||
691 | # actually, this might be considered a pucliy overloaded | ||||||
692 | # function; its the callback from the Mercator::Projector | ||||||
693 | # object to render a segment | ||||||
694 | # | ||||||
695 | # NOTE: need to apply some compress here: reduce adjacent coords | ||||||
696 | # to a single pair of coords of equivalent line segment | ||||||
697 | # | ||||||
698 | sub add_segment { | ||||||
699 | my ($self, $seg, $dataset, $segno) = @_; | ||||||
700 | |||||||
701 | print "\rDrawing segment $segno " | ||||||
702 | if $self->{verbose}; | ||||||
703 | |||||||
704 | if ($self->{save_coords}) { | ||||||
705 | my $coords = $self->{_coords} || ''; | ||||||
706 | my $area = 0; | ||||||
707 | my $i = 0; | ||||||
708 | while ($i < @$seg) { | ||||||
709 | $i += 2 | ||||||
710 | while ($i < @$seg) && (!defined $seg->[$i]); | ||||||
711 | last unless ($i < @$seg); | ||||||
712 | |||||||
713 | $coords .= "Dataset $dataset Segment $segno Area $area: "; | ||||||
714 | $coords .= join(',', $seg->[$i++], $seg->[$i++], '') | ||||||
715 | while ($i < @$seg) && (defined $seg->[$i]); | ||||||
716 | |||||||
717 | $coords .= "\n"; | ||||||
718 | $area++; | ||||||
719 | } | ||||||
720 | $self->{_coords} = $coords; | ||||||
721 | } | ||||||
722 | |||||||
723 | my $fg = $self->{fg}; | ||||||
724 | my $img = $self->{image}; | ||||||
725 | my $polyline = GD::Polyline->new(); | ||||||
726 | my ($lx, $ly) = ($seg->[0], $seg->[1]); | ||||||
727 | my $i = 2; | ||||||
728 | ($lx, $ly) = ($seg->[$i++], $seg->[$i++]) | ||||||
729 | unless defined $lx; | ||||||
730 | $polyline->addPt($lx, $ly); | ||||||
731 | my $ptcnt = 0; | ||||||
732 | while ($i < @$seg) { | ||||||
733 | my ($x, $y) = ($seg->[$i++], $seg->[$i++]); | ||||||
734 | $polyline->addPt($x, $y), | ||||||
735 | $ptcnt++, | ||||||
736 | ($lx, $ly) = ($x, $y), | ||||||
737 | next | ||||||
738 | if defined $x; | ||||||
739 | # | ||||||
740 | # if an undef marker, draw current line | ||||||
741 | # | ||||||
742 | $img->polyline($polyline, $fg) | ||||||
743 | if ($ptcnt > 2); | ||||||
744 | $polyline = GD::Polyline->new(); | ||||||
745 | ($lx, $ly) = (undef, undef); | ||||||
746 | $ptcnt = 0; | ||||||
747 | next; | ||||||
748 | } | ||||||
749 | # | ||||||
750 | # draw any remaining line | ||||||
751 | # | ||||||
752 | $img->polyline($polyline, $fg) | ||||||
753 | if ($ptcnt > 1); | ||||||
754 | return 1; | ||||||
755 | } | ||||||
756 | |||||||
757 | sub _load_basemap { | ||||||
758 | my $self = shift; | ||||||
759 | |||||||
760 | my $pat = $valid_fmt{$self->{imgfmt}}; | ||||||
761 | |||||||
762 | my $conf = "$self->{basemap_loc}.conf"; | ||||||
763 | my $imgfile = "$self->{basemap_loc}.$self->{imgfmt}"; | ||||||
764 | die "Unsupported image format $self->{imgfmt}; check your GD configuration." | ||||||
765 | unless GD::Image->can($pat); | ||||||
766 | |||||||
767 | die "$conf not found." | ||||||
768 | unless (-s $conf); | ||||||
769 | |||||||
770 | die "$imgfile not found." | ||||||
771 | unless (-s $imgfile); | ||||||
772 | |||||||
773 | die "Cannot open $conf: $!" | ||||||
774 | unless open F, $conf; | ||||||
775 | my $data = |
||||||
776 | close F; | ||||||
777 | chomp $data; | ||||||
778 | my @mapdata = split /,/, $data; | ||||||
779 | |||||||
780 | die "Invalid map data file" | ||||||
781 | unless (scalar @mapdata == 10); | ||||||
782 | # | ||||||
783 | # only install the lat/long and pixel coords, skip the mercator distances | ||||||
784 | # | ||||||
785 | $self->{mercator} = GD::Map::Mercator::Projector->new( | ||||||
786 | @mapdata[0..3], @mapdata[8..9], $self->{verbose}); | ||||||
787 | |||||||
788 | my $fd; | ||||||
789 | die "Cannot open $imgfile: $!" | ||||||
790 | unless open $fd, $imgfile; | ||||||
791 | $self->{image} = | ||||||
792 | ($self->{imgfmt} eq 'gif') ? GD::Image->newFromGif($fd) | ||||||
793 | : ($self->{imgfmt} eq 'png') ? GD::Image->newFromPng($fd) | ||||||
794 | : GD::Image->newFromJpeg($fd); | ||||||
795 | close $fd; | ||||||
796 | |||||||
797 | return $self; | ||||||
798 | } | ||||||
799 | |||||||
800 | 1; | ||||||
801 | |||||||
802 | |||||||
803 | package GD::Map::Mercator::Projector; | ||||||
804 | |||||||
805 | =pod | ||||||
806 | |||||||
807 | =begin hidden | ||||||
808 | |||||||
809 | Translates latitude, longitude datapoints to pixel coordinates | ||||||
810 | using Mercator Projection. | ||||||
811 | |||||||
812 | (Yes, I know Mercator is bad. Really bad. Criminal, even. | ||||||
813 | But its nice for rasterization. And its good enough for Google, so | ||||||
814 | its good enough for me.) | ||||||
815 | |||||||
816 | To convert to Mercator, we first computes | ||||||
817 | scales based on min/max lat/long pts. | ||||||
818 | |||||||
819 | Longitude is linear, except for some fiddling to deal with | ||||||
820 | crossing boundry from positive to negative. | ||||||
821 | |||||||
822 | Latitude requires some trig: | ||||||
823 | |||||||
824 | y = log(tan(lat) + sec(lat)) = log(sin(lat)/cos(lat) + 1/cos(lat)) | ||||||
825 | = log((sin(lat) + 1)/cos(lat)); | ||||||
826 | |||||||
827 | (all in radians, of course) | ||||||
828 | |||||||
829 | (We should really use UTM...) | ||||||
830 | |||||||
831 | =end hidden | ||||||
832 | |||||||
833 | =cut | ||||||
834 | |||||||
835 | use POSIX; | ||||||
836 | use Geo::Mercator; | ||||||
837 | |||||||
838 | use strict; | ||||||
839 | use warnings; | ||||||
840 | |||||||
841 | sub new { | ||||||
842 | my ($class, $minlat, $minlong, $maxlat, $maxlong, $width, $height, $verbose) = @_; | ||||||
843 | |||||||
844 | die "Bad min/max longitude" | ||||||
845 | if ($minlong < -180) || ($minlong > 180) || | ||||||
846 | ($maxlong < -180) || ($maxlong > 180) || | ||||||
847 | (($maxlong < $minlong) && | ||||||
848 | ((($maxlong < 0) && ($minlong < 0)) || | ||||||
849 | (($maxlong > 0) && ($minlong > 0)))); | ||||||
850 | die "Bad min/max latitude" | ||||||
851 | if ($minlat > $maxlat) || ($minlat < -90) || ($minlat > 90) || | ||||||
852 | ($maxlat < -90) || ($maxlat > 90); | ||||||
853 | |||||||
854 | my ($xmin, $ymin) = mercate($minlat, $minlong); | ||||||
855 | my ($xmax, $ymax) = mercate($maxlat, $maxlong); | ||||||
856 | my $longadj = (($xmin > 0) && ($xmax < 0)); | ||||||
857 | |||||||
858 | my $hscale = $width/($xmax - $xmin); | ||||||
859 | my $vscale = $height/($ymax - $ymin); | ||||||
860 | # | ||||||
861 | # adjust the image dimensions to match best scaling | ||||||
862 | # | ||||||
863 | my $scale = ($hscale < $vscale) ? $hscale : $vscale; | ||||||
864 | $height = _round(($ymax - $ymin) * $scale); | ||||||
865 | $width = _round(($xmax - $xmin) * $scale); | ||||||
866 | |||||||
867 | my ($minmerclong, $minmerclat) = mercate($minlat, $minlong); | ||||||
868 | my ($maxmerclong, $maxmerclat) = mercate($maxlat, $maxlong); | ||||||
869 | |||||||
870 | return bless { | ||||||
871 | minlat => $minlat, | ||||||
872 | minlong => $minlong, | ||||||
873 | maxlat => $maxlat, | ||||||
874 | maxlong => $maxlong, | ||||||
875 | minmerclat => $minmerclat, | ||||||
876 | minmerclong => $minmerclong, | ||||||
877 | maxmerclat => $maxmerclat, | ||||||
878 | maxmerclong => $maxmerclong, | ||||||
879 | xmin => $xmin, | ||||||
880 | ymin => $ymin, | ||||||
881 | xmax => $xmax, | ||||||
882 | ymax => $ymax, | ||||||
883 | scale => $scale, | ||||||
884 | longadj => $longadj, | ||||||
885 | width => $width, | ||||||
886 | height => $height, | ||||||
887 | verbose => $verbose}, $class; | ||||||
888 | } | ||||||
889 | |||||||
890 | sub config { | ||||||
891 | my $self = shift; | ||||||
892 | return (@$self{qw(minlat minlong maxlat maxlong | ||||||
893 | minmerclong minmerclat maxmerclong maxmerclat width height)}); | ||||||
894 | } | ||||||
895 | |||||||
896 | sub dimensions { return ($_[0]->{width}, $_[0]->{height}); } | ||||||
897 | # | ||||||
898 | # return pixel coord from input lat/long | ||||||
899 | # | ||||||
900 | sub project { | ||||||
901 | my $self = shift; | ||||||
902 | # | ||||||
903 | # note: we assume the inputs are in a valid range, but not | ||||||
904 | # neccesarily inside our bounding box | ||||||
905 | # | ||||||
906 | my @result = (); | ||||||
907 | while (@_) { | ||||||
908 | my ($x, $y) = mercate(shift, shift); | ||||||
909 | push @result, (($y <= $self->{ymax}) && | ||||||
910 | ($y >= $self->{ymin}) && | ||||||
911 | ($x >= $self->{xmin}) && | ||||||
912 | ($x <= $self->{xmax})) | ||||||
913 | ? (_round(($x - $self->{xmin}) * $self->{scale}), | ||||||
914 | # lat goes bottom to top, pixels top to bottom | ||||||
915 | _round($self->{height} - (($y - $self->{ymin}) * $self->{scale}))) | ||||||
916 | : (); | ||||||
917 | } | ||||||
918 | return @result; | ||||||
919 | } | ||||||
920 | # | ||||||
921 | # return lat/long and mercator distances for input pixel coords | ||||||
922 | # | ||||||
923 | sub translate { | ||||||
924 | my $self = shift; | ||||||
925 | # | ||||||
926 | # upconvert to meters before demercating | ||||||
927 | # | ||||||
928 | my @result = (); | ||||||
929 | my ($xmin, $ymin, $scale, $longadj, $height) = | ||||||
930 | @$self{qw(xmin ymin scale longadj height)}; | ||||||
931 | while (@_) { | ||||||
932 | my ($x, $y) = (shift, $height - shift); | ||||||
933 | $x /= $scale; | ||||||
934 | $x += $xmin; | ||||||
935 | $y /= $scale; | ||||||
936 | $y += $ymin; | ||||||
937 | push @result, demercate($x, $y), $y, $x; | ||||||
938 | } | ||||||
939 | return @result; | ||||||
940 | } | ||||||
941 | |||||||
942 | sub _round { | ||||||
943 | return (ceil($_[0]) - $_[0]) < ($_[0] - floor($_[0])) | ||||||
944 | ? ceil($_[0]) | ||||||
945 | : floor($_[0]); | ||||||
946 | } | ||||||
947 | |||||||
948 | sub filter { | ||||||
949 | my ($self, $fd, $container, $dataset, $keepers) = @_; | ||||||
950 | |||||||
951 | my ($xmin, $ymin, $xmax, $ymax, $scale, $longadj, $width, $height) = | ||||||
952 | @$self{qw(xmin ymin xmax ymax scale longadj width height)}; | ||||||
953 | my ($n, $len, $record, $segno, $rank, $pts); | ||||||
954 | my @coords = (); | ||||||
955 | my @mercs = (); | ||||||
956 | my @keeppx = (); | ||||||
957 | # | ||||||
958 | # convert keep region coords to pixel coords | ||||||
959 | # | ||||||
960 | if ($keepers) { | ||||||
961 | my $i = 0; | ||||||
962 | my ($keepx, $keepy); | ||||||
963 | while ($i < @$keepers) { | ||||||
964 | ($keepx, $keepy) = mercate($keepers->[$i++], $keepers->[$i++]); | ||||||
965 | push @keeppx, | ||||||
966 | (_round(($keepx - $xmin) * $scale), | ||||||
967 | _round($height - (($keepy - $ymin) * $scale))); | ||||||
968 | } | ||||||
969 | } | ||||||
970 | while ($n = read($fd, $len, 4)) { | ||||||
971 | $len = unpack('L', $len); | ||||||
972 | $n = read($fd, $record, $len); | ||||||
973 | ($segno, $rank, $pts) = unpack('LLL', substr($record, 0, 12)); | ||||||
974 | $pts *= 2; | ||||||
975 | # | ||||||
976 | # sometimes Perl doesn't believe me the 1st time! | ||||||
977 | # | ||||||
978 | @mercs = unpack("d$pts", substr($record, 12)); | ||||||
979 | my $retry = 3; | ||||||
980 | $retry--, | ||||||
981 | @mercs = unpack("d$pts", substr($record, 12)) | ||||||
982 | while ($retry && (@mercs != $pts)); | ||||||
983 | die "No coords read!!!!" unless scalar @mercs == $pts; | ||||||
984 | print "\n*** Had to reload segment $segno ", 3 - $retry, " times!\n" | ||||||
985 | if $self->{verbose} && ($retry < 3); | ||||||
986 | |||||||
987 | my $inside = 0; | ||||||
988 | my ($lx, $ly) = | ||||||
989 | (_round(($mercs[0] - $xmin) * $scale), | ||||||
990 | _round($height - (($mercs[1] - $ymin) * $scale))); | ||||||
991 | my $i = 2; | ||||||
992 | while ($i < @mercs) { | ||||||
993 | my ($x, $y) = | ||||||
994 | (_round(($mercs[$i++] - $xmin) * $scale), | ||||||
995 | _round($height - (($mercs[$i++] - $ymin) * $scale))); | ||||||
996 | # | ||||||
997 | # scaling causes many pts to overlap, so skip them | ||||||
998 | # !!!NOTE need to optimze for pts on the same line segment | ||||||
999 | # (ie, no change in x, xor no change in y) | ||||||
1000 | # | ||||||
1001 | next | ||||||
1002 | if ($x == $lx) && ($y == $ly); | ||||||
1003 | |||||||
1004 | if (($y <= $height) && ($y >= 0) && ($x >= 0) && ($x <= $width) && | ||||||
1005 | ((!$keepers) || _keep(\@keeppx, $x, $y))) { | ||||||
1006 | # | ||||||
1007 | # if prior point outside, add it w/ a undef marker | ||||||
1008 | # probably need to compute clipping intersection | ||||||
1009 | # | ||||||
1010 | push @coords, undef, undef, $lx, $ly | ||||||
1011 | unless (($ly <= $height) && ($ly >= 0) && ($lx >= 0) && ($lx <= $width)); | ||||||
1012 | push @coords, $x, $y; | ||||||
1013 | $inside++; | ||||||
1014 | } | ||||||
1015 | ($lx, $ly) = ($x, $y); | ||||||
1016 | } | ||||||
1017 | if ($inside) { | ||||||
1018 | $container->add_segment(\@coords, $dataset, $segno) | ||||||
1019 | } | ||||||
1020 | elsif ($self->{verbose}) { | ||||||
1021 | print "\r*** Skipping segment $segno \r"; | ||||||
1022 | } | ||||||
1023 | @mercs = (); | ||||||
1024 | @coords = (); | ||||||
1025 | } | ||||||
1026 | print "\n" if $self->{verbose}; | ||||||
1027 | return $self; | ||||||
1028 | } | ||||||
1029 | |||||||
1030 | sub _keep { | ||||||
1031 | my ($keeppx, $x, $y) = @_; | ||||||
1032 | my $i = 0; | ||||||
1033 | $i += 4 | ||||||
1034 | while ($i < @$keeppx) && | ||||||
1035 | (($y > $keeppx->[$i+3]) || ($y < $keeppx->[1]) || | ||||||
1036 | ($x < $keeppx->[$i]) || ($x > $keeppx->[$i+2])); | ||||||
1037 | return ($i != @$keeppx); | ||||||
1038 | } | ||||||
1039 | |||||||
1040 | 1; | ||||||
1041 | |||||||
1042 | __END__ |