line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
1
|
|
|
1
|
|
2290
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
54
|
|
2
|
|
|
|
|
|
|
# $Id: ModestMaps.pm,v 1.63 2008/08/03 17:08:39 asc Exp $ |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Net::Flickr::Geo::ModestMaps; |
5
|
1
|
|
|
1
|
|
5
|
use base qw(Net::Flickr::Geo); |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
86
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
$Net::Flickr::Geo::ModestMaps::VERSION = '0.72'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Net::Flickr::Geo::ModestMaps - tools for working with geotagged Flickr photos and Modest Maps |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my %opts = (); |
16
|
|
|
|
|
|
|
getopts('c:s:', \%opts); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# |
19
|
|
|
|
|
|
|
# Defaults |
20
|
|
|
|
|
|
|
# |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
my $cfg = Config::Simple->new($opts{'c'}); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# |
25
|
|
|
|
|
|
|
# Atkinson dithering is hawt but takes a really long |
26
|
|
|
|
|
|
|
# time... |
27
|
|
|
|
|
|
|
# |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$cfg->param("modestmaps.filter", "atkinson"); |
30
|
|
|
|
|
|
|
$cfg->param("modestmaps.timeout", (45 * 60)); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# |
33
|
|
|
|
|
|
|
# Let's say all but one of your photos are in the center of |
34
|
|
|
|
|
|
|
# Paris and the last one is at the airport. If you try to render |
35
|
|
|
|
|
|
|
# a 'poster style' (that is all the tiles for the bounding box |
36
|
|
|
|
|
|
|
# containing those points at street level) map you will make |
37
|
|
|
|
|
|
|
# your computer cry... |
38
|
|
|
|
|
|
|
# |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$cfg->param("pinwin.skip_photos", [506934069]); |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# |
43
|
|
|
|
|
|
|
# I CAN HAS MAPZ? |
44
|
|
|
|
|
|
|
# |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
my $fl = Net::Flickr::Geo::ModestMaps->new($cfg); |
47
|
|
|
|
|
|
|
$fl->log()->add(Log::Dispatch::Screen->new('name' => 'scr', min_level => 'info')); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
my $map_data = $fl->mk_poster_map_for_photoset($opts{'s'}); |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# |
52
|
|
|
|
|
|
|
# returns stuff like : |
53
|
|
|
|
|
|
|
# |
54
|
|
|
|
|
|
|
# { |
55
|
|
|
|
|
|
|
# 'url' => 'http://127.0.0.1:9999/?provider=YAHOO_AERIAL&marker=yadda yadda yadda', |
56
|
|
|
|
|
|
|
# 'image-height' => '8528', |
57
|
|
|
|
|
|
|
# 'marker-484080715' => '5076,5606,4919,5072,500,375', |
58
|
|
|
|
|
|
|
# 'marker-506435771' => '5256,4768,5099,542,500,375', |
59
|
|
|
|
|
|
|
# 'path' => '/tmp/dkl0o7uxjY.jpg', |
60
|
|
|
|
|
|
|
# 'image-width' => '6656', |
61
|
|
|
|
|
|
|
# } |
62
|
|
|
|
|
|
|
# |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
my $results = $fl->upload_poster_map($map_data->{'path'}); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# |
67
|
|
|
|
|
|
|
# returns stuff like : |
68
|
|
|
|
|
|
|
# |
69
|
|
|
|
|
|
|
# [ |
70
|
|
|
|
|
|
|
# ['/tmp/GGsf4552h.jpg', '99999992'], |
71
|
|
|
|
|
|
|
# ['/tmp/kosfGgsfdh.jpg', '99999254'], |
72
|
|
|
|
|
|
|
# ['/tmp/h354jF590.jpg', '999984643'], |
73
|
|
|
|
|
|
|
# [ and so on... ] |
74
|
|
|
|
|
|
|
# ]; |
75
|
|
|
|
|
|
|
# |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=head1 DESCRIPTION |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Tools for working with geotagged Flickr photos and the Modest Maps ws-pinwin HTTP service. |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=cut |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
=head1 OPTIONS |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
Options are passed to Net::Flickr::Backup using a Config::Simple object or |
87
|
|
|
|
|
|
|
a valid Config::Simple config file. Options are grouped by "block". |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=head2 flickr |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=over 4 |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=item * B |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
String. I |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
A valid Flickr API key. |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=item * B |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
String. I |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
A valid Flickr Auth API secret key. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
=item * B |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
String. I |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
A valid Flickr Auth API token. |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
The B defines which XML/XPath handler to use to process API responses. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=over 4 |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=item * B |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Use XML::LibXML. |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=item * B |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Use XML::XPath. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=back |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=back |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=head2 pinwin |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=item * B |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
The height of the background map on which the pinwin/thumbnail will be |
132
|
|
|
|
|
|
|
placed. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Default is 1024. |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
=item * B |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
The width of the background map on which the pinwin/thumbnail will be |
139
|
|
|
|
|
|
|
placed. |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
Default is 1024. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=item * B |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
Boolean. |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
Automatically upload newly create map images to Flickr. Photos will be tagged with the following machine tags : |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=over 4 |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=item * B |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
Where I is the photo that has been added to the map image. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=item * B |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=back |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
Default is false. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=item * B |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
Boolean. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Mark pinwin uploads to Flickr as viewable by anyone. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
Default is false. |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=item * B |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Boolean. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Mark pinwin uploads to Flickr as viewable only by friends. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Default is false. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
=item * B |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
Boolean. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
Mark pinwin uploads to Flickr as viewable only by family. |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
Default is false. |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=item * B |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
String. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
The string label for the photo size to display, as defined by the flickr.photos.getSizes |
190
|
|
|
|
|
|
|
API method : |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
http://www.flickr.com/services/api/flickr.photos.getSizes.html |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
Default is I |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=item * B |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
Int. |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
By default, the object will try to map the (Flickr) accuracy to the corresponding |
201
|
|
|
|
|
|
|
zoom level of the Modest Maps provider you have chosen. If this option is defined |
202
|
|
|
|
|
|
|
then it will be used as the zoom level regardless of what Flickr says. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=item * B |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
Int. |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
Used by the I (and by extension I) object methods to |
209
|
|
|
|
|
|
|
define the width of each slice taken from a poster map. |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
Default is 1771 |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=item * B |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
Int. |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
Used by the I (and by extension I) object methods to |
218
|
|
|
|
|
|
|
define the height of each slice taken from a poster map. |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
Default is 1239 |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=item * B |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Int (or array reference of ints) |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
Used by I related object methods, a list of photos to exclude from the list |
227
|
|
|
|
|
|
|
returned by the Flickr API. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=item * B |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
String (or array reference of strings) |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
Used by I related object methods, a list of tags that all photos must B have if |
234
|
|
|
|
|
|
|
they are to be included in the final output. |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=item * B |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
String (or array reference of strings) |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Used by I related object methods, a list of tags that all photos must have if |
241
|
|
|
|
|
|
|
they are to be included in the final output. |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head2 modestmaps |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
=over 4 |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=item * B |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
The URL to a server running the ws-pinwin.py HTTP interface to the |
250
|
|
|
|
|
|
|
ModestMaps tile-creation service. |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
This requires Modest Maps 1.0 release or higher. |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=item * B |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
A map provider and tile format for generating map images. |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
As of this writing, current providers are : |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=over 4 |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=item * B |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=item * B |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=item * B |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=item * B |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=item * B |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=item * B |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=item * B |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
=item * B |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
=item * B |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=back |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=item * B |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
Used only when creating poster maps, the method parameter defines how the underlying |
285
|
|
|
|
|
|
|
map is generated. Valid options are : |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
=over 4 |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
=item * B |
290
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
Render map tiles at a suitable zoom level in order to fit the bounding |
292
|
|
|
|
|
|
|
box (for all the images in a photoset) in an image with specific dimensions |
293
|
|
|
|
|
|
|
(I and I). |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
=item * B |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
Render all the map tiles necessary to display the bounding box (for all the |
298
|
|
|
|
|
|
|
images in a photoset) at a specific zoom level. |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=back |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
Default is bbox. |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=item * B |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
If true then extra white space will be added the underlying image in order to |
307
|
|
|
|
|
|
|
fit any markers that may extend beyond the original dimensions of the map. |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
Boolean. |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Default is true. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=item * B |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
Used only when creating poster maps, the adjust parameter tells the modest maps server |
316
|
|
|
|
|
|
|
to extend bbox passed by I kilometers. This is mostly for esthetics so that there is |
317
|
|
|
|
|
|
|
a little extra map love near pinwin located at the borders of a map. |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
Boolean. |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
Default is .25 |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
=item * B |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
Tell the Modest Maps server to filter the rendered map image before applying an markers. |
326
|
|
|
|
|
|
|
Valid options are : |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=over 4 |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
=item * B |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
Apply the Atkinson dithering filter to the map image. |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
This is brutally slow. Especially for poster maps. That's life. |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=back |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=item * B |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
Int. |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
The number of seconds the object's HTTP handler will wait when requesting data from the |
343
|
|
|
|
|
|
|
Modest Maps server. |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
Default is 300 seconds. |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=back |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
=cut |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
use Net::ModestMaps; |
352
|
|
|
|
|
|
|
use Data::Dumper; |
353
|
|
|
|
|
|
|
use FileHandle; |
354
|
|
|
|
|
|
|
use GD; |
355
|
|
|
|
|
|
|
use Imager; |
356
|
|
|
|
|
|
|
use URI; |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
# for clustermaps |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
use List::Util qw(min max); |
361
|
|
|
|
|
|
|
use Date::Calc qw (Today Add_Delta_Days Delta_Days); |
362
|
|
|
|
|
|
|
use Geo::Geotude; |
363
|
|
|
|
|
|
|
use Geo::Distance; |
364
|
|
|
|
|
|
|
use LWP::Simple; |
365
|
|
|
|
|
|
|
use POSIX qw (ceil floor); |
366
|
|
|
|
|
|
|
use Image::Size qw(imgsize); |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
=head1 PACKAGE METHODS |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=cut |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
=head2 __PACKAGE__->new($cfg) |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
Returns a I object. |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
=cut |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
# Defined in Net::Flickr::API |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 PINWIN MAPS |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=cut |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=head2 $obj->mk_pinwin_map_for_photo($photo_id) |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
Fetch a map using the Modest Maps ws-pinwin API for a geotagged Flickr photo |
387
|
|
|
|
|
|
|
and place a "pinwin" style thumbnail of the photo over the map's marker. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
Returns an array of arrays (kind of pointless really, but at least consistent). |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
The first element of the (second-level) array will be the path to the newly created map |
392
|
|
|
|
|
|
|
image. If uploads are enabled the newly created Flickr photo ID will be |
393
|
|
|
|
|
|
|
passed as the second element. |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=cut |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# Defined in Net::Flickr::Geo |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=head2 $obj->mk_pinwin_maps_for_photoset($photoset_id) |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
For each geotagged photo in a set, fetch a map using the Modest Maps |
402
|
|
|
|
|
|
|
ws-pinwin API for a geotagged Flickr photo and place a "pinwin" style |
403
|
|
|
|
|
|
|
thumbnail of the photo over the map's marker. |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
If uploads are enabled then each map for a given photo will be |
406
|
|
|
|
|
|
|
added such that it appears before the photo it references. |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Returns an array of arrays. |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
The first element of each (second-level) array reference will be the path to the newly |
411
|
|
|
|
|
|
|
created map image. If uploads are enabled the newly created Flickr photo |
412
|
|
|
|
|
|
|
ID will be passed as the second element. |
413
|
|
|
|
|
|
|
|
414
|
|
|
|
|
|
|
=cut |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
# Defined in Net::Flickr::Geo |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
=head1 POSTER MAPS |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head2 $obj->mk_poster_map_for_photoset($set_id) |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
For each geotagged photo in a set, plot the latitude and longitude and |
423
|
|
|
|
|
|
|
create a bounding box for the collection. Then fetch a map for that box |
424
|
|
|
|
|
|
|
using the Modest Maps ws-pinwin API for a geotagged Flickr photo and place |
425
|
|
|
|
|
|
|
a "pinwin" style thumbnail for each photo in the set. |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
Automatic uploads are not available for this method since the resultant |
428
|
|
|
|
|
|
|
images will almost always be too big. |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Returns a hash reference containing the URL that was used to request the |
431
|
|
|
|
|
|
|
map image, the path to the data that was sent back as well as all of the |
432
|
|
|
|
|
|
|
Modest Maps specific headers sent back. |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
=cut |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub mk_poster_map_for_photoset { |
437
|
|
|
|
|
|
|
my $self = shift; |
438
|
|
|
|
|
|
|
my $set_id = shift; |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
my $ph_size = $self->divine_option("pinwin.photo_size", "Medium"); |
441
|
|
|
|
|
|
|
my $provider = $self->divine_option("modestmaps.provider"); |
442
|
|
|
|
|
|
|
my $method = $self->divine_option("modestmaps.method", "bbox"); |
443
|
|
|
|
|
|
|
my $bleed = $self->divine_option("modestmaps.bleed", 1); |
444
|
|
|
|
|
|
|
my $adjust = $self->divine_option("modestmaps.adjust", .25); |
445
|
|
|
|
|
|
|
my $filter = $self->divine_option("modestmaps.filter", ); |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
my $upload = $self->divine_option("pinwin.upload", 0); |
448
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
# |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
my $photos = $self->collect_photos_for_set($set_id); |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
if (! $photos){ |
454
|
|
|
|
|
|
|
return undef; |
455
|
|
|
|
|
|
|
} |
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
my $ne_lat = undef; |
458
|
|
|
|
|
|
|
my $ne_lon = undef; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
my $sw_lat = undef; |
461
|
|
|
|
|
|
|
my $sw_lon = undef; |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
my %urls = (); |
464
|
|
|
|
|
|
|
my @markers = (); |
465
|
|
|
|
|
|
|
my @poly = (); |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
foreach my $ph (@$photos){ |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
my $id = $ph->getAttribute("id"); |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
my $ph_url = $self->flickr_photo_url($ph); |
472
|
|
|
|
|
|
|
$urls{$id} = $ph_url; |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
my $sz = $self->api_call({'method' => 'flickr.photos.getSizes', |
475
|
|
|
|
|
|
|
'args' => {'photo_id' => $id,}}); |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
my $sm = ($sz->findnodes("/rsp/sizes/size[\@label='$ph_size']"))[0]; |
478
|
|
|
|
|
|
|
my $w = $sm->getAttribute("width"); |
479
|
|
|
|
|
|
|
my $h = $sm->getAttribute("height"); |
480
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
my $lat = $ph->getAttribute("latitude"); |
482
|
|
|
|
|
|
|
my $lon = $ph->getAttribute("longitude"); |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
push @poly, "$lat,$lon"; |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
$sw_lat = min($sw_lat, $lat); |
487
|
|
|
|
|
|
|
$sw_lon = min($sw_lon, $lon); |
488
|
|
|
|
|
|
|
$ne_lat = max($ne_lat, $lat); |
489
|
|
|
|
|
|
|
$ne_lon = max($ne_lon, $lon); |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
my %args = ( |
492
|
|
|
|
|
|
|
'uid' => $id, |
493
|
|
|
|
|
|
|
'lat' => $lat, |
494
|
|
|
|
|
|
|
'lon' => $lon, |
495
|
|
|
|
|
|
|
'width' => $w, |
496
|
|
|
|
|
|
|
'height' => $h, |
497
|
|
|
|
|
|
|
); |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
my $marker = Net::Flickr::Geo::ModestMaps::Marker->new(%args); |
500
|
|
|
|
|
|
|
push @markers, $marker; |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
my $bbox = "$sw_lat,$sw_lon,$ne_lat,$ne_lon"; |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
# |
506
|
|
|
|
|
|
|
# fetch the actual map |
507
|
|
|
|
|
|
|
# |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
my $markers_prepped = Net::Flickr::Geo::ModestMaps::MarkerSet->prepare(\@markers); |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
my %mm_args = ( |
512
|
|
|
|
|
|
|
'provider' => $provider, |
513
|
|
|
|
|
|
|
'method' => $method, |
514
|
|
|
|
|
|
|
'bleed' => $bleed, |
515
|
|
|
|
|
|
|
'adjust' => $adjust, |
516
|
|
|
|
|
|
|
'bbox' => $bbox, |
517
|
|
|
|
|
|
|
'marker' => $markers_prepped, |
518
|
|
|
|
|
|
|
); |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
if ($method eq "extent"){ |
521
|
|
|
|
|
|
|
$mm_args{'width'} = $self->divine_option("pinwin.map_width", 1024); |
522
|
|
|
|
|
|
|
$mm_args{'height'} = $self->divine_option("pinwin.map_height", 1024); |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
else { |
526
|
|
|
|
|
|
|
$mm_args{'zoom'} = $self->divine_option("modestmaps.zoom", 17); |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
if ($filter){ |
530
|
|
|
|
|
|
|
$mm_args{'filter'} = $filter; |
531
|
|
|
|
|
|
|
} |
532
|
|
|
|
|
|
|
|
533
|
|
|
|
|
|
|
if (my $convex = $self->divine_option("modestmaps.convex")){ |
534
|
|
|
|
|
|
|
$mm_args{'convex'} = $convex; |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
$self->log()->info(Dumper(\%mm_args)); |
538
|
|
|
|
|
|
|
|
539
|
|
|
|
|
|
|
my $map_data = $self->fetch_modestmap_image(\%mm_args); |
540
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
if (! $map_data){ |
542
|
|
|
|
|
|
|
return undef; |
543
|
|
|
|
|
|
|
} |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# |
546
|
|
|
|
|
|
|
# place the markers (please to refactor me...) |
547
|
|
|
|
|
|
|
# |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
my @images = (); |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
foreach my $prop (%$map_data){ |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
if ($prop =~ /^marker-(.*)$/){ |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
my $id = $1; |
556
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
my $ph_url = $urls{$id}; |
558
|
|
|
|
|
|
|
my $ph_img = $self->mk_tempfile(".jpg"); |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
if (! $self->simple_get($ph_url, $ph_img)){ |
561
|
|
|
|
|
|
|
next; |
562
|
|
|
|
|
|
|
} |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
my @pw_details = split(",", $map_data->{$prop}); |
565
|
|
|
|
|
|
|
my $pw_x = $pw_details[2]; |
566
|
|
|
|
|
|
|
my $pw_y = $pw_details[3]; |
567
|
|
|
|
|
|
|
my $pw_w = $pw_details[4]; |
568
|
|
|
|
|
|
|
my $pw_h = $pw_details[5]; |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
push @images, [$ph_img, $pw_x, $pw_y, $pw_w, $pw_h]; |
571
|
|
|
|
|
|
|
} |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
my $out = $self->place_marker_images($map_data->{'path'}, \@images); |
575
|
|
|
|
|
|
|
$map_data->{'path'} = $out; |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
return $map_data; |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
=head2 $obj->upload_poster_map($poster_map) |
581
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
Take a file created by the I and chop it up |
583
|
|
|
|
|
|
|
in "postcard-sized" pieces and upload each to Flickr. |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
Returns an array of arrays. |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
The first element of the (second-level) array will be the path to the newly created map |
588
|
|
|
|
|
|
|
image. If uploads are enabled the newly created Flickr photo ID will be |
589
|
|
|
|
|
|
|
passed as the second element. |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
=cut |
592
|
|
|
|
|
|
|
|
593
|
|
|
|
|
|
|
sub upload_poster_map { |
594
|
|
|
|
|
|
|
my $self = shift; |
595
|
|
|
|
|
|
|
my $map = shift; |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
my $slices = $self->crop_poster_map($map); |
598
|
|
|
|
|
|
|
my @res = shift; |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
foreach my $img (@$slices){ |
601
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
my %args = ('photo' => $img); |
603
|
|
|
|
|
|
|
my $id = $self->upload_image(\%args); |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
push @res, [$img, $id]; |
606
|
|
|
|
|
|
|
unlink($img); |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
return \@res; |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
|
612
|
|
|
|
|
|
|
=head2 $obj->crop_poster_map($poster_map) |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
Take a file created by the I and chop it up |
615
|
|
|
|
|
|
|
in "postcard-sized" pieces. |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
The height and width of each piece are defined by the I and |
618
|
|
|
|
|
|
|
I config options. |
619
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
Any image whose cropping creates a file smaller than either dimension will |
621
|
|
|
|
|
|
|
be padded with extra (white) space. |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
Returns a list of files. |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
=cut |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
sub crop_poster_map { |
628
|
|
|
|
|
|
|
my $self = shift; |
629
|
|
|
|
|
|
|
my $map = shift; |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
my $crop_width = $self->divine_option("pinwin.crop_width", 1771); |
632
|
|
|
|
|
|
|
my $crop_height = $self->divine_option("pinwin.crop_width", 1239); |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
my $offset_x = 0; |
635
|
|
|
|
|
|
|
my $offset_y = 0; |
636
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
my @slices = (); |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
my $im = Imager->new(); |
640
|
|
|
|
|
|
|
$im->read('file' => $map); |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
my $map_h = $im->getheight(); |
643
|
|
|
|
|
|
|
my $map_w = $im->getwidth(); |
644
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
while ($offset_x < $map_w) { |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
while ($offset_y < $map_h) { |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
my $x = $offset_x; |
650
|
|
|
|
|
|
|
my $y = $offset_y; |
651
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
my $slice = $im->crop('left' => $x, 'top' => $y, 'width' => $crop_width, 'height' => $crop_height); |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
my $h = $slice->getheight(); |
655
|
|
|
|
|
|
|
my $w = $slice->getwidth(); |
656
|
|
|
|
|
|
|
|
657
|
|
|
|
|
|
|
if (($h < $crop_height) || ($w < $crop_width)){ |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
my $canvas = Imager->new('xsize' => $crop_width, 'ysize' => $crop_height); |
660
|
|
|
|
|
|
|
$canvas->box('color' => 'white', 'xmin' => 0, 'ymin' => 0, 'xmax' => $crop_width, 'ymax' => $crop_height, 'filled' => 1); |
661
|
|
|
|
|
|
|
$canvas->paste('img' => $slice, 'left' => 0, 'top' => 0); |
662
|
|
|
|
|
|
|
push @slices, $canvas; |
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
else { |
666
|
|
|
|
|
|
|
push @slices, $slice; |
667
|
|
|
|
|
|
|
} |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
$offset_y += $crop_height; |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
$offset_x += $crop_width; |
673
|
|
|
|
|
|
|
$offset_y = 0; |
674
|
|
|
|
|
|
|
} |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
my @files = map { |
677
|
|
|
|
|
|
|
$self->write_jpeg($im); |
678
|
|
|
|
|
|
|
} @slices; |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
return \@files; |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
=head1 CLUSTER MAPS |
684
|
|
|
|
|
|
|
|
685
|
|
|
|
|
|
|
=head2 $obj->mk_cluster_maps_for_photo($photo_id) |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
Like poster maps, cluster maps plot many photos in multiple pinwins on a single |
688
|
|
|
|
|
|
|
background map image. Unlike poster maps, where you explicitly list all the photos |
689
|
|
|
|
|
|
|
to display (by specifying a photo set) cluster maps renders a single photo as its |
690
|
|
|
|
|
|
|
principal focus with all the photos with in an (n) kilometer radius of the subject |
691
|
|
|
|
|
|
|
photo. |
692
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
Multiple photos sharing the same latitude and longitude are also clustered together |
694
|
|
|
|
|
|
|
and rendered in a single pinwin, whose size and shape is relative to the square |
695
|
|
|
|
|
|
|
root of the number of total photos. This helps, in densely photographed areas, to |
696
|
|
|
|
|
|
|
prevent cascading pinwins from rocketing off the map canvas trying to find a suitably |
697
|
|
|
|
|
|
|
empty space to avoid overlapping other nearby pinwins. |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
As of this writing, all the surrounding photos are rendered using their 75x75 pixel |
700
|
|
|
|
|
|
|
square thumbnail though this will be a user-configurable option in future releases. |
701
|
|
|
|
|
|
|
The principal photo size can still be set by assigning the I config |
702
|
|
|
|
|
|
|
variable (the default is I). |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
Cluster maps are not a general purpose interface to the Flickr I |
705
|
|
|
|
|
|
|
method (yet) although there are some flags to limit search results to the principal |
706
|
|
|
|
|
|
|
photo's owner or by one or more copyright licenses. |
707
|
|
|
|
|
|
|
|
708
|
|
|
|
|
|
|
All (except B, discussed below) the usual config options may be set for |
709
|
|
|
|
|
|
|
cluster maps. In addition, you may also define the following options : |
710
|
|
|
|
|
|
|
|
711
|
|
|
|
|
|
|
=over 4 |
712
|
|
|
|
|
|
|
|
713
|
|
|
|
|
|
|
=item * B |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
Float. |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
The number of kilometers from I<$photo_id>'s lat/lon in which to perform a |
718
|
|
|
|
|
|
|
radial query for other geotagged photos. |
719
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
Default is I<1> |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
=item * B |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
Int. |
725
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
The number of days on either side of I<$photo_id>'s "date taken" value with which |
727
|
|
|
|
|
|
|
to limit the scope of the query. |
728
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
Default is I<0> |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=item * B |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
Boolean. |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
Limit all queries to include only photos uploaded by I<$photo_id>'s owner. |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
Default is I |
738
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
=item * B |
740
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
Boolean. |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
Typically used when setting the I to ensure that nearby photos |
744
|
|
|
|
|
|
|
uploaded by I<$photo_id>s owner are included. If true, this will cause the code |
745
|
|
|
|
|
|
|
to execute the same search twice. The second query will remove any licensing |
746
|
|
|
|
|
|
|
restrictions and enforce that only photos owned by I<$photo_id>'s owner be |
747
|
|
|
|
|
|
|
returned. The two result sets will then be merged and sorted by distance from |
748
|
|
|
|
|
|
|
the center point. |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
(Ignored if the I is true.) |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
Default is I |
753
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=item * B |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
String. |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
A comma-separated list of Flickr license IDs to limit the list of photos returned |
759
|
|
|
|
|
|
|
by the I API method. |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
Default is none. |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
=item * B |
764
|
|
|
|
|
|
|
|
765
|
|
|
|
|
|
|
String. |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
Post search, ensure that all the photos have a minimum set of geo permissions. |
768
|
|
|
|
|
|
|
|
769
|
|
|
|
|
|
|
Valid options are : "public", "contact", "friend", "family", "friend or family" |
770
|
|
|
|
|
|
|
and "all". |
771
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
Default is I |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
=item * B |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
Int. |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
Although the clustering of photos sharing the same latitude and longitude helps |
779
|
|
|
|
|
|
|
cut down on number of pinwins the Modest Maps needs to figure out how to layout |
780
|
|
|
|
|
|
|
on the background map, there is still an upper limit after which it (Modest Maps) |
781
|
|
|
|
|
|
|
will simply give up. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
The exact number is a little hard to say as it is usually a function of how closely |
784
|
|
|
|
|
|
|
grouped any number of pinwins (clustered or not) are to each other. Anecdotally, |
785
|
|
|
|
|
|
|
anything less than 100 is fine; less than 200 is a toss up; anything after that |
786
|
|
|
|
|
|
|
usually wakes the baby. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
Default is I<100> |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
=item * B |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
Int. |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
All of the clusters are grouped by their lat/lon position rounded off to three |
795
|
|
|
|
|
|
|
decimal points. You can change this option to set the maximum number of photos |
796
|
|
|
|
|
|
|
that can be contained in a single group. |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
Default is half the value of the I parameter. |
799
|
|
|
|
|
|
|
|
800
|
|
|
|
|
|
|
=back |
801
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
If either the B or B is exceeded then another |
803
|
|
|
|
|
|
|
search query is initiated, where the radial days offset from I<$photo_id>'s taken |
804
|
|
|
|
|
|
|
date is reduced by 10%. If no offset value was set by the user then an initial |
805
|
|
|
|
|
|
|
value of 365 is set (meaning that if there are still too many photos after the |
806
|
|
|
|
|
|
|
second query it will be reset to 328 days and so on.) |
807
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
Finally, all cluster maps assume the Modest Maps B method. The bounding box |
809
|
|
|
|
|
|
|
itself is calcluated using the photos further from the center and is adjusted (in |
810
|
|
|
|
|
|
|
size) relative to distance between the south-west and north-east corners. |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
If the distance is less that 1km, the bounding box will be expanded by .25km; If |
813
|
|
|
|
|
|
|
the distance is less than 1.5km, the bounding box will be expanded by .1km; If the |
814
|
|
|
|
|
|
|
bounding is less than 2km, the bounding box will be expanded by .1km. |
815
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
Returns a hash reference containing the URL that was used to request the |
817
|
|
|
|
|
|
|
map image, the path to the data that was sent back as well as all of the |
818
|
|
|
|
|
|
|
Modest Maps specific headers sent back. |
819
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
Attribution for the photos is returned in a hash refernce whose key is labeled |
821
|
|
|
|
|
|
|
I and whose contents are a series of nested hashes mapping marker |
822
|
|
|
|
|
|
|
IDs to owners and a list of photos for that marker. For example : |
823
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
$response = { |
825
|
|
|
|
|
|
|
# lots of other stuff |
826
|
|
|
|
|
|
|
'marker-2561168539' => '1455,4189,1427,2065,75,75', |
827
|
|
|
|
|
|
|
'attribution' => { |
828
|
|
|
|
|
|
|
'2366199422' => { |
829
|
|
|
|
|
|
|
'foobar' => ['http://www.flickr.com/photos/foobar/999999'], |
830
|
|
|
|
|
|
|
} |
831
|
|
|
|
|
|
|
} |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
=cut |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
sub mk_cluster_map_for_photo { |
837
|
|
|
|
|
|
|
my $self = shift; |
838
|
|
|
|
|
|
|
my $photo_id = shift; |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
my ($ph, $ph_marker, $queries) = $self->mk_cluster_map_for_photo_base($photo_id); |
841
|
|
|
|
|
|
|
my ($markers, $bbox); |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
# really ? |
844
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
if (scalar(@$queries) == 1){ |
846
|
|
|
|
|
|
|
($markers, $bbox) = $self->search_for_cluster_map($queries->[0]); |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
|
849
|
|
|
|
|
|
|
else { |
850
|
|
|
|
|
|
|
($markers, $bbox) = $self->blended_search_for_cluster_map($queries); |
851
|
|
|
|
|
|
|
} |
852
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
# |
854
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
unshift @{$markers}, $ph_marker; |
856
|
|
|
|
|
|
|
return $self->create_cluster_map($markers, $bbox); |
857
|
|
|
|
|
|
|
} |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
=head2 $obj->mk_historical_cluster_map_for_photo($photo_id) |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
Historical cluster maps are similar to plain old cluster in nature, but not in |
862
|
|
|
|
|
|
|
execution. Rather than doing a single query and showing whatever happens to be |
863
|
|
|
|
|
|
|
closest to I<$photo_id> historical cluster maps rely on the code making two |
864
|
|
|
|
|
|
|
calls to the photos.search each explicitly constrained by a date range. |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
The first query will ask for photos within (n) days of when the photo was taken; |
867
|
|
|
|
|
|
|
the second query will ask for photos within (n) days of today. |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
The two result sets are then smushed together, sorted by distance to I<$photo_id> |
870
|
|
|
|
|
|
|
and clustered in to groups. If the number of photos, or grouped photos, is too |
871
|
|
|
|
|
|
|
high then each date range is reduced (the value of offset days is multiplied by 90% |
872
|
|
|
|
|
|
|
and rounded down) and the process is repeated until everything fits. |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
Or something breaks. |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
Then we make a map! |
877
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
All the same rules and options that apply for plain old cluster maps are valid |
879
|
|
|
|
|
|
|
for historical cluster maps. |
880
|
|
|
|
|
|
|
|
881
|
|
|
|
|
|
|
=cut |
882
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
sub mk_historical_cluster_map_for_photo { |
884
|
|
|
|
|
|
|
my $self = shift; |
885
|
|
|
|
|
|
|
my $photo_id = shift; |
886
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
my $offset = $self->divine_option("clustermap.offset", 0); |
888
|
|
|
|
|
|
|
my $today = $self->today(); |
889
|
|
|
|
|
|
|
my ($before_now, $after_now) = $self->calculate_delta_days($today, $offset); |
890
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
my ($ph, $ph_marker, $queries) = $self->mk_cluster_map_for_photo_base($photo_id); |
892
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
# tmp array so we don't get stuck in an infinite |
894
|
|
|
|
|
|
|
# loop always creating new queries to modify... |
895
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
my @new_queries = (); |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
foreach my $query_then (@$queries){ |
899
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
my %query_now = map { |
901
|
|
|
|
|
|
|
$_ => $query_then->{$_}; |
902
|
|
|
|
|
|
|
} keys %{$query_then}; |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
$query_now{'min_taken_date'} = $before_now; |
905
|
|
|
|
|
|
|
$query_now{'max_taken_date'} = $after_now; |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
push @new_queries, \%query_now; |
908
|
|
|
|
|
|
|
} |
909
|
|
|
|
|
|
|
|
910
|
|
|
|
|
|
|
map { push @$queries, $_ } @new_queries; |
911
|
|
|
|
|
|
|
|
912
|
|
|
|
|
|
|
my ($markers, $bbox) = $self->blended_search_for_cluster_map($queries); |
913
|
|
|
|
|
|
|
unshift @{$markers}, $ph_marker; |
914
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
return $self->create_cluster_map($markers, $bbox); |
916
|
|
|
|
|
|
|
} |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
# shhh... |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
sub mk_cluster_maps_for_photoset { |
921
|
|
|
|
|
|
|
my $self = shift; |
922
|
|
|
|
|
|
|
my $set_id = shift; |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
my $upload = $self->divine_option('pinwin.upload', 0); |
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
# |
927
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
my $photos = $self->collect_photos_for_set($set_id); |
929
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
if (! $photos){ |
931
|
|
|
|
|
|
|
return undef; |
932
|
|
|
|
|
|
|
} |
933
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
my @maps = (); |
935
|
|
|
|
|
|
|
my @set = (); |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
foreach my $ph (@$photos){ |
938
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
my $uid = $ph->getAttribute("id"); |
940
|
|
|
|
|
|
|
my $map = $self->mk_cluster_map_for_photo($uid); |
941
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
if (! $map){ |
943
|
|
|
|
|
|
|
$self->log()->error("failed to generate cluster map for photo #$uid"); |
944
|
|
|
|
|
|
|
next; |
945
|
|
|
|
|
|
|
} |
946
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
my @local_res = ($map->{'path'}); |
948
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
if ($upload){ |
950
|
|
|
|
|
|
|
my $id = $self->upload_map($ph, $map->{'path'}); |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
push @local_res, $id; |
953
|
|
|
|
|
|
|
push @set, $id; |
954
|
|
|
|
|
|
|
push @set, $ph->getAttribute("id"); |
955
|
|
|
|
|
|
|
} |
956
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
push @maps, \@local_res; |
958
|
|
|
|
|
|
|
} |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
if (($upload) && (scalar(@set))) { |
961
|
|
|
|
|
|
|
$self->api_call({'method' => 'flickr.photosets.editPhotos', |
962
|
|
|
|
|
|
|
'args' => {'photoset_id' => $set_id, |
963
|
|
|
|
|
|
|
'primary_photo_id' => $set[0], |
964
|
|
|
|
|
|
|
'photo_ids' => join(",", @set)}}); |
965
|
|
|
|
|
|
|
} |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
return \@maps; |
968
|
|
|
|
|
|
|
} |
969
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
# |
971
|
|
|
|
|
|
|
# not so public |
972
|
|
|
|
|
|
|
# |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
sub create_cluster_map { |
975
|
|
|
|
|
|
|
my $self = shift; |
976
|
|
|
|
|
|
|
my $markers = shift; |
977
|
|
|
|
|
|
|
my $bbox = shift; |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
my $mm_args = $self->prepare_modestmaps_args_for_cluster_map($markers, $bbox); |
980
|
|
|
|
|
|
|
my $urls = $self->gather_urls_for_cluster_map($markers); |
981
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
my $map_data = $self->create_pinwin_map($mm_args, $urls); |
983
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
if (! $map_data){ |
985
|
|
|
|
|
|
|
return undef; |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
# |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
$map_data->{'attribution'} = $self->collect_attributions($markers); |
991
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
$self->log()->debug(Dumper($map_data)); |
993
|
|
|
|
|
|
|
return $map_data; |
994
|
|
|
|
|
|
|
} |
995
|
|
|
|
|
|
|
|
996
|
|
|
|
|
|
|
sub create_pinwin_map { |
997
|
|
|
|
|
|
|
my $self = shift; |
998
|
|
|
|
|
|
|
my $mm_args = shift; |
999
|
|
|
|
|
|
|
my $img_urls = shift; |
1000
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
$self->log()->debug(Dumper($mm_args)); |
1002
|
|
|
|
|
|
|
$self->log()->debug(Dumper($img_urls)); |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
my $map_data = $self->fetch_modestmap_image($mm_args); |
1005
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
if (! $map_data){ |
1007
|
|
|
|
|
|
|
return undef; |
1008
|
|
|
|
|
|
|
} |
1009
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
my $out = $self->place_map_images($map_data, $img_urls); |
1011
|
|
|
|
|
|
|
$map_data->{'path'} = $out; |
1012
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
return $map_data; |
1014
|
|
|
|
|
|
|
} |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
sub collect_attributions { |
1017
|
|
|
|
|
|
|
my $self = shift; |
1018
|
|
|
|
|
|
|
my $markers = shift; |
1019
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
my %attribution = (); |
1021
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
foreach my $mrk (@$markers){ |
1023
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
if (! exists($mrk->{'attribution'})){ |
1025
|
|
|
|
|
|
|
next; |
1026
|
|
|
|
|
|
|
} |
1027
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
my $uid = $mrk->{'uid'}; |
1029
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
if (ref($mrk->{'attribution'}) ne "ARRAY"){ |
1031
|
|
|
|
|
|
|
$attribution{$uid}->{$mrk->{'attribution'}->{'owner'}} = $mrk->{'attribution'}->{'url'}; |
1032
|
|
|
|
|
|
|
} |
1033
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
else { |
1035
|
|
|
|
|
|
|
map { |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
my $owner = $_->{'owner'}; |
1038
|
|
|
|
|
|
|
my $url = $_->{'url'}; |
1039
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
$attribution{$uid}->{$owner} ||= []; |
1041
|
|
|
|
|
|
|
push @{$attribution{$uid}->{$owner}}, $url; |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
} @{$mrk->{'attribution'}}; |
1044
|
|
|
|
|
|
|
} |
1045
|
|
|
|
|
|
|
} |
1046
|
|
|
|
|
|
|
|
1047
|
|
|
|
|
|
|
return \%attribution; |
1048
|
|
|
|
|
|
|
} |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
sub fetch_map_image { |
1051
|
|
|
|
|
|
|
my $self = shift; |
1052
|
|
|
|
|
|
|
my $ph = shift; |
1053
|
|
|
|
|
|
|
my $thumb_data = shift; |
1054
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
# please refactor me... |
1056
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
my $lat = $self->get_geo_property($ph, "latitude"); |
1058
|
|
|
|
|
|
|
my $lon = $self->get_geo_property($ph, "longitude"); |
1059
|
|
|
|
|
|
|
my $acc = $self->get_geo_property($ph, "accuracy"); |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
if ((! $lat) || (! $lon)){ |
1062
|
|
|
|
|
|
|
return undef; |
1063
|
|
|
|
|
|
|
} |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
# |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
my $zoom = $self->flickr_accuracy_to_zoom($acc); |
1068
|
|
|
|
|
|
|
$self->log()->info("zoom to $zoom ($acc)"); |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
# |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
my $out = $self->mk_tempfile(".png"); |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
my $provider = $self->divine_option("modestmaps.provider"); |
1075
|
|
|
|
|
|
|
my $bleed = $self->divine_option("modestmaps.bleed"); |
1076
|
|
|
|
|
|
|
my $filter = $self->divine_option("modestmaps.filter"); |
1077
|
|
|
|
|
|
|
$zoom = $self->divine_option("modestmaps.zoom", $zoom); |
1078
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
# |
1080
|
|
|
|
|
|
|
|
1081
|
|
|
|
|
|
|
my %args = ( |
1082
|
|
|
|
|
|
|
'uid' => 'thumbnail', |
1083
|
|
|
|
|
|
|
'lat' => $lat, |
1084
|
|
|
|
|
|
|
'lon' => $lon, |
1085
|
|
|
|
|
|
|
'width' => $thumb_data->{'width'}, |
1086
|
|
|
|
|
|
|
'height' => $thumb_data->{'height'}, |
1087
|
|
|
|
|
|
|
); |
1088
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
my $marker = Net::Flickr::Geo::ModestMaps::Marker->new(%args); |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
my $height = $self->divine_option("pinwin.map_height", 1024); |
1092
|
|
|
|
|
|
|
my $width = $self->divine_option("pinwin.map_width", 1024); |
1093
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
my %mm_args = ( |
1095
|
|
|
|
|
|
|
'provider' => $provider, |
1096
|
|
|
|
|
|
|
'latitude' => $lat, |
1097
|
|
|
|
|
|
|
'longitude' => $lon, |
1098
|
|
|
|
|
|
|
'zoom' => $zoom, |
1099
|
|
|
|
|
|
|
'method' => 'center', |
1100
|
|
|
|
|
|
|
'height' => $height, |
1101
|
|
|
|
|
|
|
'width' => $width, |
1102
|
|
|
|
|
|
|
'bleed' => $bleed, |
1103
|
|
|
|
|
|
|
'marker' => $marker, |
1104
|
|
|
|
|
|
|
); |
1105
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
if ($filter){ |
1107
|
|
|
|
|
|
|
$mm_args{'filter'} = $filter; |
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
# $self->log()->info(Dumper(\%mm_args)); |
1111
|
|
|
|
|
|
|
return $self->fetch_modestmap_image(\%mm_args, $out); |
1112
|
|
|
|
|
|
|
} |
1113
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
sub flickr_accuracy_to_zoom { |
1115
|
|
|
|
|
|
|
my $self = shift; |
1116
|
|
|
|
|
|
|
my $acc = shift; |
1117
|
|
|
|
|
|
|
|
1118
|
|
|
|
|
|
|
my $provider = $self->divine_option("modestmaps.provider"); |
1119
|
|
|
|
|
|
|
$provider =~ /^([^_]+)_/; |
1120
|
|
|
|
|
|
|
my $short = lc($1); |
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
if ($short eq 'yahoo'){ |
1123
|
|
|
|
|
|
|
return $acc; |
1124
|
|
|
|
|
|
|
} |
1125
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
else { |
1127
|
|
|
|
|
|
|
return $acc + 1; |
1128
|
|
|
|
|
|
|
} |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
} |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
sub fetch_modestmap_image { |
1133
|
|
|
|
|
|
|
my $self = shift; |
1134
|
|
|
|
|
|
|
my $args = shift; |
1135
|
|
|
|
|
|
|
my $out = shift; |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
$out ||= $self->mk_tempfile(".jpg"); |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
my $timeout = $self->divine_option("modestmaps.timeout", (5 * 60)); |
1140
|
|
|
|
|
|
|
my $remote = $self->divine_option("modestmaps.server"); |
1141
|
|
|
|
|
|
|
|
1142
|
|
|
|
|
|
|
my $mm = Net::ModestMaps->new(); |
1143
|
|
|
|
|
|
|
$mm->host($remote); |
1144
|
|
|
|
|
|
|
$mm->timeout($timeout); |
1145
|
|
|
|
|
|
|
$mm->ensure_max_header_lines($args->{'marker'}); |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
my $data = $mm->draw($args, $out); |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
if (my $err = $data->{'error'}){ |
1150
|
|
|
|
|
|
|
$self->log()->info("modestmaps error : $err->{'code'}, $err->{'message'}"); |
1151
|
|
|
|
|
|
|
return undef; |
1152
|
|
|
|
|
|
|
} |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
$self->log()->info("received modest map image and stored in $out"); |
1155
|
|
|
|
|
|
|
return $data; |
1156
|
|
|
|
|
|
|
} |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
sub modify_map { |
1159
|
|
|
|
|
|
|
my $self = shift; |
1160
|
|
|
|
|
|
|
my $ph = shift; |
1161
|
|
|
|
|
|
|
my $map_data = shift; |
1162
|
|
|
|
|
|
|
my $thumb_data = shift; |
1163
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
my @pw_details = split(",", $map_data->{'marker-thumbnail'}); |
1165
|
|
|
|
|
|
|
my $pw_x = $pw_details[2]; |
1166
|
|
|
|
|
|
|
my $pw_y = $pw_details[3]; |
1167
|
|
|
|
|
|
|
my $pw_w = $pw_details[4]; |
1168
|
|
|
|
|
|
|
my $pw_h = $pw_details[5]; |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
my @images = ([$thumb_data->{path}, $pw_x, $pw_y, $pw_w, $pw_h]); |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
return $self->place_marker_images($map_data->{'path'}, \@images); |
1173
|
|
|
|
|
|
|
} |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
sub place_marker_images { |
1176
|
|
|
|
|
|
|
my $self = shift; |
1177
|
|
|
|
|
|
|
my $map_img = shift; |
1178
|
|
|
|
|
|
|
my $markers = shift; |
1179
|
|
|
|
|
|
|
|
1180
|
|
|
|
|
|
|
# use GD instead of Imager because the latter has |
1181
|
|
|
|
|
|
|
# a habit of rendeing the actual thumbnails all wrong... |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
# ensure the truecolor luv to prevent nasty dithering |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
my $truecolor = 1; |
1186
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
my $im = GD::Image->newFromPng($map_img, $truecolor); |
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
foreach my $data (@$markers){ |
1190
|
|
|
|
|
|
|
my ($mrk_img, $x, $y, $w, $h) = @$data; |
1191
|
|
|
|
|
|
|
my $ph = GD::Image->newFromJpeg($mrk_img, $truecolor); |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
eval { |
1194
|
|
|
|
|
|
|
$im->copy($ph, $x, $y, 0, 0, $w, $h); |
1195
|
|
|
|
|
|
|
}; |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
if ($@){ |
1198
|
|
|
|
|
|
|
$self->log()->error("picture made GD cry, skipping. $@"); |
1199
|
|
|
|
|
|
|
} |
1200
|
|
|
|
|
|
|
|
1201
|
|
|
|
|
|
|
unlink($mrk_img); |
1202
|
|
|
|
|
|
|
} |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
unlink($map_img); |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
return $self->write_jpeg($im); |
1207
|
|
|
|
|
|
|
} |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
sub place_map_images { |
1210
|
|
|
|
|
|
|
my $self = shift; |
1211
|
|
|
|
|
|
|
my $map_data = shift; |
1212
|
|
|
|
|
|
|
my $urls = shift; |
1213
|
|
|
|
|
|
|
|
1214
|
|
|
|
|
|
|
my @images = (); |
1215
|
|
|
|
|
|
|
|
1216
|
|
|
|
|
|
|
foreach my $prop (%$map_data){ |
1217
|
|
|
|
|
|
|
|
1218
|
|
|
|
|
|
|
if ($prop =~ /^marker-(.*)$/){ |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
my $id = $1; |
1221
|
|
|
|
|
|
|
|
1222
|
|
|
|
|
|
|
my $ph_url = $urls->{$id}; |
1223
|
|
|
|
|
|
|
my $ph_img = $self->mk_tempfile(".jpg"); |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
$self->log()->info("fetch $ph_url"); |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
if (! $self->simple_get($ph_url, $ph_img)){ |
1228
|
|
|
|
|
|
|
$self->log()->error("failed to retrieve $ph_url, $!"); |
1229
|
|
|
|
|
|
|
next; |
1230
|
|
|
|
|
|
|
} |
1231
|
|
|
|
|
|
|
|
1232
|
|
|
|
|
|
|
my @pw_details = split(",", $map_data->{$prop}); |
1233
|
|
|
|
|
|
|
my $pw_x = $pw_details[2]; |
1234
|
|
|
|
|
|
|
my $pw_y = $pw_details[3]; |
1235
|
|
|
|
|
|
|
my $pw_w = $pw_details[4]; |
1236
|
|
|
|
|
|
|
my $pw_h = $pw_details[5]; |
1237
|
|
|
|
|
|
|
|
1238
|
|
|
|
|
|
|
push @images, [$ph_img, $pw_x, $pw_y, $pw_w, $pw_h]; |
1239
|
|
|
|
|
|
|
} |
1240
|
|
|
|
|
|
|
} |
1241
|
|
|
|
|
|
|
|
1242
|
|
|
|
|
|
|
return $self->place_marker_images($map_data->{'path'}, \@images); |
1243
|
|
|
|
|
|
|
} |
1244
|
|
|
|
|
|
|
|
1245
|
|
|
|
|
|
|
# |
1246
|
|
|
|
|
|
|
# search |
1247
|
|
|
|
|
|
|
# |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
sub collect_blended_search { |
1250
|
|
|
|
|
|
|
my $self = shift; |
1251
|
|
|
|
|
|
|
my $queries = shift; |
1252
|
|
|
|
|
|
|
|
1253
|
|
|
|
|
|
|
my $perms = $self->divine_option("clustermap.geo_perms", "all"); |
1254
|
|
|
|
|
|
|
|
1255
|
|
|
|
|
|
|
my %unsorted = (); |
1256
|
|
|
|
|
|
|
my @possible = (); |
1257
|
|
|
|
|
|
|
|
1258
|
|
|
|
|
|
|
my %seen = (); |
1259
|
|
|
|
|
|
|
|
1260
|
|
|
|
|
|
|
foreach my $q (@$queries){ |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
my %local_q = (); |
1263
|
|
|
|
|
|
|
|
1264
|
|
|
|
|
|
|
foreach my $k (keys %{$q}){ |
1265
|
|
|
|
|
|
|
if ($k =~ /^__/){ |
1266
|
|
|
|
|
|
|
next; |
1267
|
|
|
|
|
|
|
} |
1268
|
|
|
|
|
|
|
|
1269
|
|
|
|
|
|
|
$local_q{$k} = $q->{$k}; |
1270
|
|
|
|
|
|
|
} |
1271
|
|
|
|
|
|
|
|
1272
|
|
|
|
|
|
|
my $search = $self->api_call({'method' => 'flickr.photos.search', 'args' => \%local_q}); |
1273
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
foreach my $ph ($search->findnodes("/rsp/photos/photo")){ |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
my $uid = $ph->getAttribute("id"); |
1277
|
|
|
|
|
|
|
|
1278
|
|
|
|
|
|
|
if (exists($seen{$uid})){ |
1279
|
|
|
|
|
|
|
next; |
1280
|
|
|
|
|
|
|
} |
1281
|
|
|
|
|
|
|
|
1282
|
|
|
|
|
|
|
$seen{$uid} ++; |
1283
|
|
|
|
|
|
|
|
1284
|
|
|
|
|
|
|
if (exists($q->{'__exclude'})){ |
1285
|
|
|
|
|
|
|
if (grep /$uid/, @{$q->{'__exclude'}}){ |
1286
|
|
|
|
|
|
|
$self->log()->info("exclude photo #$uid from blended search"); |
1287
|
|
|
|
|
|
|
next; |
1288
|
|
|
|
|
|
|
} |
1289
|
|
|
|
|
|
|
} |
1290
|
|
|
|
|
|
|
|
1291
|
|
|
|
|
|
|
if (! $self->ensure_geo_perms($uid, $perms)){ |
1292
|
|
|
|
|
|
|
next; |
1293
|
|
|
|
|
|
|
} |
1294
|
|
|
|
|
|
|
|
1295
|
|
|
|
|
|
|
my $geo = Geo::Distance->new(); |
1296
|
|
|
|
|
|
|
my $dist = $geo->distance("kilometer", $q->{'lon'}, $q->{'lat'}, $ph->getAttribute("longitude"), $ph->getAttribute("latitude")); |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
$unsorted{$dist} ||= []; |
1299
|
|
|
|
|
|
|
push @{$unsorted{$dist}}, $ph; |
1300
|
|
|
|
|
|
|
} |
1301
|
|
|
|
|
|
|
} |
1302
|
|
|
|
|
|
|
|
1303
|
|
|
|
|
|
|
foreach my $dist (sort {$a <=> $b} keys %unsorted){ |
1304
|
|
|
|
|
|
|
map { push @possible, $_ } @{$unsorted{$dist}}; |
1305
|
|
|
|
|
|
|
} |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
return \@possible; |
1308
|
|
|
|
|
|
|
} |
1309
|
|
|
|
|
|
|
|
1310
|
|
|
|
|
|
|
# |
1311
|
|
|
|
|
|
|
# marker methods |
1312
|
|
|
|
|
|
|
# |
1313
|
|
|
|
|
|
|
|
1314
|
|
|
|
|
|
|
sub gather_urls_for_cluster_map { |
1315
|
|
|
|
|
|
|
my $self = shift; |
1316
|
|
|
|
|
|
|
my $markers = shift; |
1317
|
|
|
|
|
|
|
|
1318
|
|
|
|
|
|
|
my %urls = map { |
1319
|
|
|
|
|
|
|
$_->{'uid'} => $_->{'url'}; |
1320
|
|
|
|
|
|
|
} @$markers; |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
return \%urls; |
1323
|
|
|
|
|
|
|
} |
1324
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
# |
1326
|
|
|
|
|
|
|
# cluster methods (photo) |
1327
|
|
|
|
|
|
|
# |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
sub mk_cluster_map_for_photo_base { |
1330
|
|
|
|
|
|
|
my $self = shift; |
1331
|
|
|
|
|
|
|
my $photo_id = shift; |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
my $ph_size = $self->divine_option("pinwin.photo_size", "Medium"); |
1334
|
|
|
|
|
|
|
my $r = $self->divine_option("clustermap.radius", 1); |
1335
|
|
|
|
|
|
|
my $offset = $self->divine_option("clustermap.offset", 0); |
1336
|
|
|
|
|
|
|
my $own = $self->divine_option("clustermap.only_photo_owner", 1); |
1337
|
|
|
|
|
|
|
my $force_own = $self->divine_option("clustermap.force_photo_owner", 0); |
1338
|
|
|
|
|
|
|
my $license = $self->divine_option("clustermap.photo_license", "*"); |
1339
|
|
|
|
|
|
|
|
1340
|
|
|
|
|
|
|
# |
1341
|
|
|
|
|
|
|
|
1342
|
|
|
|
|
|
|
my $ph = $self->api_call({'method' => 'flickr.photos.getInfo', 'args' => {'photo_id' => $photo_id}}); |
1343
|
|
|
|
|
|
|
$ph = ($ph->findnodes("/rsp/photo"))[0]; |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
my $owner = $ph->findvalue("owner/\@nsid"); |
1346
|
|
|
|
|
|
|
|
1347
|
|
|
|
|
|
|
my $lat = $self->get_geo_property($ph, "latitude"); |
1348
|
|
|
|
|
|
|
my $lon = $self->get_geo_property($ph, "longitude"); |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
my $sizes = $self->api_call({'method' => 'flickr.photos.getSizes', 'args' => {'photo_id' => $photo_id}}); |
1351
|
|
|
|
|
|
|
|
1352
|
|
|
|
|
|
|
my $h = $sizes->findvalue("/rsp/sizes/size[\@label='Medium']/\@height"); |
1353
|
|
|
|
|
|
|
my $w = $sizes->findvalue("/rsp/sizes/size[\@label='Medium']/\@width"); |
1354
|
|
|
|
|
|
|
|
1355
|
|
|
|
|
|
|
my $url = sprintf("http://farm%d.static.flickr.com/%d/%s_%s.jpg", |
1356
|
|
|
|
|
|
|
$ph->getAttribute("farm"), |
1357
|
|
|
|
|
|
|
$ph->getAttribute("server"), |
1358
|
|
|
|
|
|
|
$ph->getAttribute("id"), |
1359
|
|
|
|
|
|
|
$ph->getAttribute("secret")); |
1360
|
|
|
|
|
|
|
|
1361
|
|
|
|
|
|
|
my %args = ( |
1362
|
|
|
|
|
|
|
'uid' => $photo_id, |
1363
|
|
|
|
|
|
|
'lat' => $lat, |
1364
|
|
|
|
|
|
|
'lon' => $lon, |
1365
|
|
|
|
|
|
|
'width' => $w, |
1366
|
|
|
|
|
|
|
'height' => $h, |
1367
|
|
|
|
|
|
|
'url' => $url, |
1368
|
|
|
|
|
|
|
); |
1369
|
|
|
|
|
|
|
|
1370
|
|
|
|
|
|
|
if ($license){ |
1371
|
|
|
|
|
|
|
my $username = $ph->findvalue("owner/\@username"); |
1372
|
|
|
|
|
|
|
my $ph_url = $ph->findvalue("urls/url[\@type='photopage']"); |
1373
|
|
|
|
|
|
|
$args{'attribution'} = {'owner' => $username, 'url' => $ph_url}; |
1374
|
|
|
|
|
|
|
} |
1375
|
|
|
|
|
|
|
|
1376
|
|
|
|
|
|
|
my $ph_marker = Net::Flickr::Geo::ModestMaps::Marker->new(%args); |
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
# |
1379
|
|
|
|
|
|
|
# Basic search criteria |
1380
|
|
|
|
|
|
|
# |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
my %query = ( |
1383
|
|
|
|
|
|
|
'lat' => $lat, |
1384
|
|
|
|
|
|
|
'lon' => $lon, |
1385
|
|
|
|
|
|
|
'radius' => $r, |
1386
|
|
|
|
|
|
|
'extras' => 'geo,date_taken', |
1387
|
|
|
|
|
|
|
'__exclude' => [$ph->getAttribute('id')], |
1388
|
|
|
|
|
|
|
); |
1389
|
|
|
|
|
|
|
|
1390
|
|
|
|
|
|
|
if ($license ne "*"){ |
1391
|
|
|
|
|
|
|
$query{'license'} = $license; |
1392
|
|
|
|
|
|
|
$query{'extras'} .= ",owner_name"; |
1393
|
|
|
|
|
|
|
} |
1394
|
|
|
|
|
|
|
|
1395
|
|
|
|
|
|
|
if ($own){ |
1396
|
|
|
|
|
|
|
$query{'user_id'} = $owner; |
1397
|
|
|
|
|
|
|
} |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
if ($offset){ |
1400
|
|
|
|
|
|
|
my $dt = $ph->findvalue("dates/\@taken"); |
1401
|
|
|
|
|
|
|
my ($before, $after) = $self->calculate_delta_days($dt, $offset); |
1402
|
|
|
|
|
|
|
|
1403
|
|
|
|
|
|
|
$query{'min_taken_date'} = $before; |
1404
|
|
|
|
|
|
|
$query{'max_taken_date'} = $after; |
1405
|
|
|
|
|
|
|
} |
1406
|
|
|
|
|
|
|
|
1407
|
|
|
|
|
|
|
# |
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
my @queries = (\%query); |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
# |
1412
|
|
|
|
|
|
|
|
1413
|
|
|
|
|
|
|
if ((! $own) && ($force_own) && (exists($query{'license'}))){ |
1414
|
|
|
|
|
|
|
|
1415
|
|
|
|
|
|
|
$self->log()->info("forcing photo owner photos, requires a blended search"); |
1416
|
|
|
|
|
|
|
|
1417
|
|
|
|
|
|
|
my %query_me = map { |
1418
|
|
|
|
|
|
|
$_ => $query{$_}; |
1419
|
|
|
|
|
|
|
} keys %query; |
1420
|
|
|
|
|
|
|
|
1421
|
|
|
|
|
|
|
delete($query_me{'license'}); |
1422
|
|
|
|
|
|
|
$query_me{'user_id'} = $ph->findvalue("owner/\@nsid"); |
1423
|
|
|
|
|
|
|
|
1424
|
|
|
|
|
|
|
push @queries, \%query_me; |
1425
|
|
|
|
|
|
|
} |
1426
|
|
|
|
|
|
|
|
1427
|
|
|
|
|
|
|
# |
1428
|
|
|
|
|
|
|
|
1429
|
|
|
|
|
|
|
return ($ph, $ph_marker, \@queries); |
1430
|
|
|
|
|
|
|
} |
1431
|
|
|
|
|
|
|
|
1432
|
|
|
|
|
|
|
# |
1433
|
|
|
|
|
|
|
# cluster methods (markers) |
1434
|
|
|
|
|
|
|
# |
1435
|
|
|
|
|
|
|
|
1436
|
|
|
|
|
|
|
sub markers_for_clusters { |
1437
|
|
|
|
|
|
|
my $self = shift; |
1438
|
|
|
|
|
|
|
my $clusters = shift; |
1439
|
|
|
|
|
|
|
|
1440
|
|
|
|
|
|
|
my @markers = (); |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
foreach my $key (keys %{$clusters}){ |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
if (scalar(@{$clusters->{$key}}) == 1){ |
1445
|
|
|
|
|
|
|
push @markers, $clusters->{$key}->[0]; |
1446
|
|
|
|
|
|
|
next; |
1447
|
|
|
|
|
|
|
} |
1448
|
|
|
|
|
|
|
|
1449
|
|
|
|
|
|
|
my @images = (); |
1450
|
|
|
|
|
|
|
my @attribution = (); |
1451
|
|
|
|
|
|
|
|
1452
|
|
|
|
|
|
|
my ($uid, $lat, $lon); |
1453
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
foreach my $mrk (@{$clusters->{$key}}){ |
1455
|
|
|
|
|
|
|
|
1456
|
|
|
|
|
|
|
if (! exists($mrk->{'url'})){ |
1457
|
|
|
|
|
|
|
next; |
1458
|
|
|
|
|
|
|
} |
1459
|
|
|
|
|
|
|
|
1460
|
|
|
|
|
|
|
$uid = $mrk->{'uid'}; |
1461
|
|
|
|
|
|
|
$lat = $mrk->{'lat'}; |
1462
|
|
|
|
|
|
|
$lon = $mrk->{'lon'}; |
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
push @images, $mrk->{'url'}; |
1465
|
|
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
if (exists($mrk->{'attribution'})){ |
1467
|
|
|
|
|
|
|
push @attribution, $mrk->{'attribution'}; |
1468
|
|
|
|
|
|
|
} |
1469
|
|
|
|
|
|
|
} |
1470
|
|
|
|
|
|
|
|
1471
|
|
|
|
|
|
|
# reassign $w,$h |
1472
|
|
|
|
|
|
|
|
1473
|
|
|
|
|
|
|
my $stacked = $self->stack_images(\@images); |
1474
|
|
|
|
|
|
|
my ($w, $h) = imgsize($stacked); |
1475
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
my $url = "file://" . $stacked; |
1477
|
|
|
|
|
|
|
|
1478
|
|
|
|
|
|
|
my %args = ('uid' => $uid, |
1479
|
|
|
|
|
|
|
'lat' => $lat, |
1480
|
|
|
|
|
|
|
'lon' => $lon, |
1481
|
|
|
|
|
|
|
'width' => $w, |
1482
|
|
|
|
|
|
|
'height' => $h, |
1483
|
|
|
|
|
|
|
'url' => $url, |
1484
|
|
|
|
|
|
|
'attribution' => \@attribution, |
1485
|
|
|
|
|
|
|
); |
1486
|
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
my $marker = Net::Flickr::Geo::ModestMaps::Marker->new(%args); |
1488
|
|
|
|
|
|
|
push @markers, $marker; |
1489
|
|
|
|
|
|
|
} |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
return \@markers; |
1492
|
|
|
|
|
|
|
} |
1493
|
|
|
|
|
|
|
|
1494
|
|
|
|
|
|
|
# |
1495
|
|
|
|
|
|
|
# cluster methods (search) |
1496
|
|
|
|
|
|
|
# |
1497
|
|
|
|
|
|
|
|
1498
|
|
|
|
|
|
|
sub search_for_cluster_map { |
1499
|
|
|
|
|
|
|
my $self = shift; |
1500
|
|
|
|
|
|
|
my $query = shift; |
1501
|
|
|
|
|
|
|
return $self->blended_search_for_cluster_map([$query]); |
1502
|
|
|
|
|
|
|
} |
1503
|
|
|
|
|
|
|
|
1504
|
|
|
|
|
|
|
sub blended_search_for_cluster_map { |
1505
|
|
|
|
|
|
|
my $self = shift; |
1506
|
|
|
|
|
|
|
my $queries = shift; |
1507
|
|
|
|
|
|
|
|
1508
|
|
|
|
|
|
|
my $max_photos = $self->divine_option("clustermap.max_photos", 100); |
1509
|
|
|
|
|
|
|
my $max_grouped = $self->divine_option("clustermap.max_photos_per_group", int($max_photos / 2)); |
1510
|
|
|
|
|
|
|
my $offset = $self->divine_option("clustermap.offset", 0); |
1511
|
|
|
|
|
|
|
|
1512
|
|
|
|
|
|
|
my $photos = undef; |
1513
|
|
|
|
|
|
|
my $clusters = undef; |
1514
|
|
|
|
|
|
|
my $groups = undef; |
1515
|
|
|
|
|
|
|
my $bbox = undef; |
1516
|
|
|
|
|
|
|
|
1517
|
|
|
|
|
|
|
my $ok = 0; |
1518
|
|
|
|
|
|
|
|
1519
|
|
|
|
|
|
|
my @pts = (); |
1520
|
|
|
|
|
|
|
|
1521
|
|
|
|
|
|
|
foreach my $q (@$queries){ |
1522
|
|
|
|
|
|
|
push @pts, [$q->{'lat'}, $q->{'lon'}]; |
1523
|
|
|
|
|
|
|
} |
1524
|
|
|
|
|
|
|
|
1525
|
|
|
|
|
|
|
while (! $ok){ |
1526
|
|
|
|
|
|
|
|
1527
|
|
|
|
|
|
|
$self->log()->debug("new blended search"); |
1528
|
|
|
|
|
|
|
$photos = $self->collect_blended_search($queries); |
1529
|
|
|
|
|
|
|
|
1530
|
|
|
|
|
|
|
my $cnt_photos = scalar(@$photos); |
1531
|
|
|
|
|
|
|
my $cnt_groups = 0; |
1532
|
|
|
|
|
|
|
my $cnt_clusters = 0; |
1533
|
|
|
|
|
|
|
my $cnt_grouped = 0; |
1534
|
|
|
|
|
|
|
|
1535
|
|
|
|
|
|
|
my $local_ok = ($cnt_photos <= $max_photos) ? 1 : 0; |
1536
|
|
|
|
|
|
|
|
1537
|
|
|
|
|
|
|
$self->log()->info("search returns $cnt_photos photos (max : $max_photos)"); |
1538
|
|
|
|
|
|
|
|
1539
|
|
|
|
|
|
|
if ($local_ok){ |
1540
|
|
|
|
|
|
|
($clusters, $groups, $bbox) = $self->cluster_blended_search($photos, \@pts); |
1541
|
|
|
|
|
|
|
|
1542
|
|
|
|
|
|
|
$cnt_groups = scalar(keys %$groups); |
1543
|
|
|
|
|
|
|
$cnt_clusters = scalar(keys %$clusters); |
1544
|
|
|
|
|
|
|
|
1545
|
|
|
|
|
|
|
$cnt_grouped = map { max($cnt_grouped, $_) } values %$groups; |
1546
|
|
|
|
|
|
|
|
1547
|
|
|
|
|
|
|
$self->log()->info("search returned $cnt_clusters clustered photos, across $cnt_groups groups (max photos/group : $cnt_grouped)"); |
1548
|
|
|
|
|
|
|
$local_ok = ($cnt_grouped <= $max_grouped) ? 1 : 0; |
1549
|
|
|
|
|
|
|
} |
1550
|
|
|
|
|
|
|
|
1551
|
|
|
|
|
|
|
if ($local_ok){ |
1552
|
|
|
|
|
|
|
$ok = 1; |
1553
|
|
|
|
|
|
|
last; |
1554
|
|
|
|
|
|
|
} |
1555
|
|
|
|
|
|
|
|
1556
|
|
|
|
|
|
|
# reset |
1557
|
|
|
|
|
|
|
|
1558
|
|
|
|
|
|
|
$self->log()->info("too many photos adjusting offset and radius so as not to make modestmaps cry"); |
1559
|
|
|
|
|
|
|
|
1560
|
|
|
|
|
|
|
$offset = ($offset) ? floor($offset * .9) : 365; |
1561
|
|
|
|
|
|
|
|
1562
|
|
|
|
|
|
|
if ($offset <= 0){ |
1563
|
|
|
|
|
|
|
$self->log()->error("offset equals zero, eyes turning black"); |
1564
|
|
|
|
|
|
|
return undef; |
1565
|
|
|
|
|
|
|
} |
1566
|
|
|
|
|
|
|
|
1567
|
|
|
|
|
|
|
foreach my $q (@{$queries}){ |
1568
|
|
|
|
|
|
|
|
1569
|
|
|
|
|
|
|
if ((! $q->{'min_taken_date'}) || (! $q->{'max_taken_date'})){ |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
my $today = $self->today(); |
1572
|
|
|
|
|
|
|
my ($min, $max) = $self->calculate_delta_days($today, $offset); |
1573
|
|
|
|
|
|
|
|
1574
|
|
|
|
|
|
|
$q->{'min_taken_date'} = $min; |
1575
|
|
|
|
|
|
|
$q->{'max_taken_date'} = $max; |
1576
|
|
|
|
|
|
|
} |
1577
|
|
|
|
|
|
|
|
1578
|
|
|
|
|
|
|
else { |
1579
|
|
|
|
|
|
|
|
1580
|
|
|
|
|
|
|
$q->{'min_taken_date'} =~ /(\d{4})-(\d{2})-(\d{2})/; |
1581
|
|
|
|
|
|
|
my ($y1, $m1, $d1) = ($1, $2, $3); |
1582
|
|
|
|
|
|
|
|
1583
|
|
|
|
|
|
|
$q->{'max_taken_date'} =~ /(\d{4})-(\d{2})-(\d{2})/; |
1584
|
|
|
|
|
|
|
my ($y2, $m2, $d2) = ($1, $2, $3); |
1585
|
|
|
|
|
|
|
|
1586
|
|
|
|
|
|
|
my $delta = Delta_Days($y1, $m1, $d1, $y2, $m2, $d2); |
1587
|
|
|
|
|
|
|
my @new = Add_Delta_Days($y1, $m1, $d1, int($delta / 2)); |
1588
|
|
|
|
|
|
|
|
1589
|
|
|
|
|
|
|
my $start = sprintf("%04d-%02d-%02d", @new); |
1590
|
|
|
|
|
|
|
$self->log()->info("reset start date to $start with an offset of $offset days"); |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
my ($min, $max) = $self->calculate_delta_days($start, $offset); |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
$self->log()->info("reset min taken date from $q->{'min_taken_date'} to $min"); |
1595
|
|
|
|
|
|
|
$self->log()->info("reset min taken date from $q->{'max_taken_date'} to $max"); |
1596
|
|
|
|
|
|
|
|
1597
|
|
|
|
|
|
|
$q->{'min_taken_date'} = $min; |
1598
|
|
|
|
|
|
|
$q->{'max_taken_date'} = $max; |
1599
|
|
|
|
|
|
|
} |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
# |
1602
|
|
|
|
|
|
|
|
1603
|
|
|
|
|
|
|
$q->{'per_page'} ||= $self->divine_option("clustermap.max_photos", 100); |
1604
|
|
|
|
|
|
|
$q->{'per_page'} = ceil($q->{'per_page'} * .9); |
1605
|
|
|
|
|
|
|
} |
1606
|
|
|
|
|
|
|
} |
1607
|
|
|
|
|
|
|
|
1608
|
|
|
|
|
|
|
# |
1609
|
|
|
|
|
|
|
|
1610
|
|
|
|
|
|
|
my $markers = $self->markers_for_clusters($clusters); |
1611
|
|
|
|
|
|
|
|
1612
|
|
|
|
|
|
|
return ($markers, $bbox); |
1613
|
|
|
|
|
|
|
} |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
sub cluster_blended_search { |
1616
|
|
|
|
|
|
|
my $self = shift; |
1617
|
|
|
|
|
|
|
my $photos = shift; |
1618
|
|
|
|
|
|
|
my $pts = shift; |
1619
|
|
|
|
|
|
|
|
1620
|
|
|
|
|
|
|
my %clusters = (); |
1621
|
|
|
|
|
|
|
my %groups = (); |
1622
|
|
|
|
|
|
|
my %bbox = (); |
1623
|
|
|
|
|
|
|
|
1624
|
|
|
|
|
|
|
if (defined($pts)){ |
1625
|
|
|
|
|
|
|
foreach my $c (@$pts){ |
1626
|
|
|
|
|
|
|
$bbox{'sw_lat'} = (exists($bbox{'sw_lat'})) ? min($bbox{'sw_lat'}, $c->[0]) : $c->[0]; |
1627
|
|
|
|
|
|
|
$bbox{'sw_lon'} = (exists($bbox{'sw_lon'})) ? min($bbox{'sw_lon'}, $c->[1]) : $c->[1]; |
1628
|
|
|
|
|
|
|
$bbox{'ne_lat'} = (exists($bbox{'ne_lat'})) ? max($bbox{'ne_lat'}, $c->[0]) : $c->[0]; |
1629
|
|
|
|
|
|
|
$bbox{'ne_lon'} = (exists($bbox{'ne_lon'})) ? max($bbox{'ne_lon'}, $c->[1]) : $c->[1]; |
1630
|
|
|
|
|
|
|
} |
1631
|
|
|
|
|
|
|
} |
1632
|
|
|
|
|
|
|
|
1633
|
|
|
|
|
|
|
foreach my $ph (@$photos){ |
1634
|
|
|
|
|
|
|
|
1635
|
|
|
|
|
|
|
my $uid = $ph->getAttribute("id"); |
1636
|
|
|
|
|
|
|
|
1637
|
|
|
|
|
|
|
my $lat = $self->get_geo_property($ph, "latitude"); |
1638
|
|
|
|
|
|
|
my $lon = $self->get_geo_property($ph, "longitude"); |
1639
|
|
|
|
|
|
|
my $cluster_key = $self->geotude($lat, $lon); |
1640
|
|
|
|
|
|
|
|
1641
|
|
|
|
|
|
|
# to do : allow for other sizes and center crop... |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
my $url = sprintf("http://farm%d.static.flickr.com/%d/%s_%s_s.jpg", |
1644
|
|
|
|
|
|
|
$ph->getAttribute("farm"), |
1645
|
|
|
|
|
|
|
$ph->getAttribute("server"), |
1646
|
|
|
|
|
|
|
$ph->getAttribute("id"), |
1647
|
|
|
|
|
|
|
$ph->getAttribute("secret")); |
1648
|
|
|
|
|
|
|
|
1649
|
|
|
|
|
|
|
my %args = ( |
1650
|
|
|
|
|
|
|
'uid' => $uid, |
1651
|
|
|
|
|
|
|
'lat' => $lat, |
1652
|
|
|
|
|
|
|
'lon' => $lon, |
1653
|
|
|
|
|
|
|
'width' => 75, |
1654
|
|
|
|
|
|
|
'height' => 75, |
1655
|
|
|
|
|
|
|
'url' => $url, |
1656
|
|
|
|
|
|
|
); |
1657
|
|
|
|
|
|
|
|
1658
|
|
|
|
|
|
|
# attribution |
1659
|
|
|
|
|
|
|
|
1660
|
|
|
|
|
|
|
if (my $owner = $ph->getAttribute("ownername")){ |
1661
|
|
|
|
|
|
|
|
1662
|
|
|
|
|
|
|
my $page = sprintf("http://www.flickr.com/photos/%s/%s", |
1663
|
|
|
|
|
|
|
$ph->getAttribute("owner"), |
1664
|
|
|
|
|
|
|
$uid); |
1665
|
|
|
|
|
|
|
|
1666
|
|
|
|
|
|
|
$args{'attribution'} = {'owner' => $owner, 'url' => $page}; |
1667
|
|
|
|
|
|
|
} |
1668
|
|
|
|
|
|
|
|
1669
|
|
|
|
|
|
|
my $marker = Net::Flickr::Geo::ModestMaps::Marker->new(%args); |
1670
|
|
|
|
|
|
|
|
1671
|
|
|
|
|
|
|
$clusters{$cluster_key} ||= []; |
1672
|
|
|
|
|
|
|
push @{$clusters{$cluster_key}}, $marker; |
1673
|
|
|
|
|
|
|
|
1674
|
|
|
|
|
|
|
$bbox{'sw_lat'} = (exists($bbox{'sw_lat'})) ? min($bbox{'sw_lat'}, $lat) : $lat; |
1675
|
|
|
|
|
|
|
$bbox{'sw_lon'} = (exists($bbox{'sw_lon'})) ? min($bbox{'sw_lon'}, $lon) : $lon; |
1676
|
|
|
|
|
|
|
$bbox{'ne_lat'} = (exists($bbox{'ne_lat'})) ? max($bbox{'ne_lat'}, $lat) : $lat; |
1677
|
|
|
|
|
|
|
$bbox{'ne_lon'} = (exists($bbox{'ne_lon'})) ? max($bbox{'ne_lon'}, $lon) : $lon; |
1678
|
|
|
|
|
|
|
|
1679
|
|
|
|
|
|
|
# to do : check to see how closely together |
1680
|
|
|
|
|
|
|
# stuff is clustered; weight counts below accordingly |
1681
|
|
|
|
|
|
|
|
1682
|
|
|
|
|
|
|
my $rnd_lat = sprintf("%.2f", $lat); |
1683
|
|
|
|
|
|
|
my $rnd_lon = sprintf("%.2f", $lon); |
1684
|
|
|
|
|
|
|
my $group_key = $self->geotude($rnd_lat, $rnd_lon); |
1685
|
|
|
|
|
|
|
|
1686
|
|
|
|
|
|
|
$groups{$group_key} ++; |
1687
|
|
|
|
|
|
|
} |
1688
|
|
|
|
|
|
|
|
1689
|
|
|
|
|
|
|
return \%clusters, \%groups, \%bbox; |
1690
|
|
|
|
|
|
|
} |
1691
|
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
# |
1693
|
|
|
|
|
|
|
# cluster methods (other) |
1694
|
|
|
|
|
|
|
# |
1695
|
|
|
|
|
|
|
|
1696
|
|
|
|
|
|
|
sub prepare_modestmaps_args_for_cluster_map { |
1697
|
|
|
|
|
|
|
my $self = shift; |
1698
|
|
|
|
|
|
|
my $markers = shift; |
1699
|
|
|
|
|
|
|
my $bbox = shift; |
1700
|
|
|
|
|
|
|
|
1701
|
|
|
|
|
|
|
my $provider = $self->divine_option("modestmaps.provider"); |
1702
|
|
|
|
|
|
|
my $bleed = $self->divine_option("modestmaps.bleed", 1); |
1703
|
|
|
|
|
|
|
my $adjust = $self->divine_option("modestmaps.adjust", .25); |
1704
|
|
|
|
|
|
|
my $filter = $self->divine_option("modestmaps.filter", ); |
1705
|
|
|
|
|
|
|
my $zoom = $self->divine_option("modestmaps.zoom", 17); |
1706
|
|
|
|
|
|
|
|
1707
|
|
|
|
|
|
|
my $markers_prepped = Net::Flickr::Geo::ModestMaps::MarkerSet->prepare($markers); |
1708
|
|
|
|
|
|
|
|
1709
|
|
|
|
|
|
|
my %mm_args = ( |
1710
|
|
|
|
|
|
|
'provider' => $provider, |
1711
|
|
|
|
|
|
|
'method' => 'bbox', |
1712
|
|
|
|
|
|
|
'bleed' => $bleed, |
1713
|
|
|
|
|
|
|
'adjust' => $adjust, |
1714
|
|
|
|
|
|
|
'marker' => $markers_prepped, |
1715
|
|
|
|
|
|
|
'zoom' => $zoom, |
1716
|
|
|
|
|
|
|
'bbox' => "$bbox->{'sw_lat'},$bbox->{'sw_lon'},$bbox->{'ne_lat'},$bbox->{'ne_lon'}", |
1717
|
|
|
|
|
|
|
); |
1718
|
|
|
|
|
|
|
|
1719
|
|
|
|
|
|
|
my $dist_avg = $self->calculate_average_distance($bbox); |
1720
|
|
|
|
|
|
|
|
1721
|
|
|
|
|
|
|
my $readjust = 0; |
1722
|
|
|
|
|
|
|
|
1723
|
|
|
|
|
|
|
if ($dist_avg < 1){ |
1724
|
|
|
|
|
|
|
$readjust = .25; |
1725
|
|
|
|
|
|
|
} |
1726
|
|
|
|
|
|
|
|
1727
|
|
|
|
|
|
|
elsif ($dist_avg < 1.5){ |
1728
|
|
|
|
|
|
|
$readjust = .15 |
1729
|
|
|
|
|
|
|
} |
1730
|
|
|
|
|
|
|
|
1731
|
|
|
|
|
|
|
elsif ($dist_avg < 2){ |
1732
|
|
|
|
|
|
|
$readjust = .1; |
1733
|
|
|
|
|
|
|
} |
1734
|
|
|
|
|
|
|
|
1735
|
|
|
|
|
|
|
else { } |
1736
|
|
|
|
|
|
|
|
1737
|
|
|
|
|
|
|
if (($readjust) && ($readjust > $mm_args{'adjust'})){ |
1738
|
|
|
|
|
|
|
$self->log()->info("autosetting modestmaps adjust parameter to $readjust"); |
1739
|
|
|
|
|
|
|
$mm_args{'adjust'} = $readjust; |
1740
|
|
|
|
|
|
|
} |
1741
|
|
|
|
|
|
|
|
1742
|
|
|
|
|
|
|
if ($filter){ |
1743
|
|
|
|
|
|
|
$mm_args{'filter'} = $filter; |
1744
|
|
|
|
|
|
|
} |
1745
|
|
|
|
|
|
|
|
1746
|
|
|
|
|
|
|
return \%mm_args; |
1747
|
|
|
|
|
|
|
} |
1748
|
|
|
|
|
|
|
|
1749
|
|
|
|
|
|
|
# |
1750
|
|
|
|
|
|
|
# geo |
1751
|
|
|
|
|
|
|
# |
1752
|
|
|
|
|
|
|
|
1753
|
|
|
|
|
|
|
sub geotude { |
1754
|
|
|
|
|
|
|
my $self = shift; |
1755
|
|
|
|
|
|
|
my $lat = shift; |
1756
|
|
|
|
|
|
|
my $lon = shift; |
1757
|
|
|
|
|
|
|
|
1758
|
|
|
|
|
|
|
my $geo = Geo::Geotude->new('latitude' => $lat, 'longitude' => $lon); |
1759
|
|
|
|
|
|
|
return $geo->geotude(); |
1760
|
|
|
|
|
|
|
} |
1761
|
|
|
|
|
|
|
|
1762
|
|
|
|
|
|
|
sub calculate_average_distance { |
1763
|
|
|
|
|
|
|
my $self = shift; |
1764
|
|
|
|
|
|
|
my $bbox = shift; |
1765
|
|
|
|
|
|
|
|
1766
|
|
|
|
|
|
|
my $geo = Geo::Distance->new(); |
1767
|
|
|
|
|
|
|
|
1768
|
|
|
|
|
|
|
my $dist_x = $geo->distance("kilometer", $bbox->{'sw_lon'}, $bbox->{'sw_lat'}, $bbox->{'sw_lon'}, $bbox->{'ne_lat'}); |
1769
|
|
|
|
|
|
|
my $dist_y = $geo->distance("kilometer", $bbox->{'sw_lon'}, $bbox->{'sw_lat'}, $bbox->{'sw_lon'}, $bbox->{'ne_lat'}); |
1770
|
|
|
|
|
|
|
|
1771
|
|
|
|
|
|
|
my $dist_avg = ($dist_x + $dist_y) / 2; |
1772
|
|
|
|
|
|
|
|
1773
|
|
|
|
|
|
|
$self->log()->info("distance between sw and ne corners is $dist_x km and $dist_y km"); |
1774
|
|
|
|
|
|
|
$self->log()->info("average distance is $dist_avg km"); |
1775
|
|
|
|
|
|
|
|
1776
|
|
|
|
|
|
|
return $dist_avg; |
1777
|
|
|
|
|
|
|
} |
1778
|
|
|
|
|
|
|
|
1779
|
|
|
|
|
|
|
# |
1780
|
|
|
|
|
|
|
# images |
1781
|
|
|
|
|
|
|
# |
1782
|
|
|
|
|
|
|
|
1783
|
|
|
|
|
|
|
sub stack_images { |
1784
|
|
|
|
|
|
|
my $self = shift; |
1785
|
|
|
|
|
|
|
my $images = shift; |
1786
|
|
|
|
|
|
|
|
1787
|
|
|
|
|
|
|
my $count = scalar(@$images); |
1788
|
|
|
|
|
|
|
my $per_row = ceil(sqrt($count)); |
1789
|
|
|
|
|
|
|
my $rows = ceil($count/$per_row); |
1790
|
|
|
|
|
|
|
|
1791
|
|
|
|
|
|
|
my $other_rows = $rows - 1; |
1792
|
|
|
|
|
|
|
my $last_row = $count - ($other_rows * $per_row); |
1793
|
|
|
|
|
|
|
|
1794
|
|
|
|
|
|
|
if ($last_row == $other_rows){ |
1795
|
|
|
|
|
|
|
$per_row += 1; |
1796
|
|
|
|
|
|
|
$rows -= 1; |
1797
|
|
|
|
|
|
|
} |
1798
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
$self->log()->info("stacking $count images $per_row per row for a total of $rows rows"); |
1800
|
|
|
|
|
|
|
|
1801
|
|
|
|
|
|
|
my $spacer_px = 10; |
1802
|
|
|
|
|
|
|
my $spacers_w = $per_row - 1; |
1803
|
|
|
|
|
|
|
my $spacers_h = $rows - 1; |
1804
|
|
|
|
|
|
|
|
1805
|
|
|
|
|
|
|
my $cnv_w = ($per_row * 75) + ($spacers_w * $spacer_px); |
1806
|
|
|
|
|
|
|
my $cnv_h = ($rows * 75) + ($spacers_h * $spacer_px); |
1807
|
|
|
|
|
|
|
|
1808
|
|
|
|
|
|
|
$self->log()->debug("stacking canvas is $cnv_w x $cnv_h pixels"); |
1809
|
|
|
|
|
|
|
|
1810
|
|
|
|
|
|
|
my $truecolor = 1; |
1811
|
|
|
|
|
|
|
GD::Image->trueColor($truecolor); |
1812
|
|
|
|
|
|
|
|
1813
|
|
|
|
|
|
|
my $im = new GD::Image($cnv_w, $cnv_h); |
1814
|
|
|
|
|
|
|
my $wh = $im->colorAllocate(255, 255, 255); |
1815
|
|
|
|
|
|
|
|
1816
|
|
|
|
|
|
|
$im->filledRectangle(0, 0, $cnv_w, $cnv_h, $wh); |
1817
|
|
|
|
|
|
|
|
1818
|
|
|
|
|
|
|
my $across = 1; |
1819
|
|
|
|
|
|
|
my $down = 1; |
1820
|
|
|
|
|
|
|
|
1821
|
|
|
|
|
|
|
foreach my $url (@$images){ |
1822
|
|
|
|
|
|
|
|
1823
|
|
|
|
|
|
|
my $tmp = $self->mk_tempfile(".jpg"); |
1824
|
|
|
|
|
|
|
|
1825
|
|
|
|
|
|
|
if (! getstore($url, $tmp)){ |
1826
|
|
|
|
|
|
|
$self->log()->error("failed to retrieve $url for stacking, $!"); |
1827
|
|
|
|
|
|
|
next; |
1828
|
|
|
|
|
|
|
} |
1829
|
|
|
|
|
|
|
|
1830
|
|
|
|
|
|
|
my $ph = GD::Image->newFromJpeg($tmp, $truecolor); |
1831
|
|
|
|
|
|
|
|
1832
|
|
|
|
|
|
|
if (! $ph){ |
1833
|
|
|
|
|
|
|
$self->log()->error("failed to create image from $tmp, $!"); |
1834
|
|
|
|
|
|
|
next; |
1835
|
|
|
|
|
|
|
} |
1836
|
|
|
|
|
|
|
|
1837
|
|
|
|
|
|
|
my $copy_x = ($spacer_px * ($across - 1)) + (75 * ($across - 1)); |
1838
|
|
|
|
|
|
|
my $copy_y = ($spacer_px * ($down - 1)) + (75 * ($down - 1)); |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
$self->log()->debug("copy image at $copy_x ($across accross) and $copy_y ($down down)"); |
1841
|
|
|
|
|
|
|
|
1842
|
|
|
|
|
|
|
eval { |
1843
|
|
|
|
|
|
|
$im->copy($ph, $copy_x, $copy_y, 0, 0, 75, 75); |
1844
|
|
|
|
|
|
|
}; |
1845
|
|
|
|
|
|
|
|
1846
|
|
|
|
|
|
|
if ($@){ |
1847
|
|
|
|
|
|
|
$self->log()->error("picture made GD cry, skipping. $@"); |
1848
|
|
|
|
|
|
|
} |
1849
|
|
|
|
|
|
|
|
1850
|
|
|
|
|
|
|
unlink($tmp); |
1851
|
|
|
|
|
|
|
|
1852
|
|
|
|
|
|
|
if ($across == $per_row){ |
1853
|
|
|
|
|
|
|
$across = 1; |
1854
|
|
|
|
|
|
|
$down += 1; |
1855
|
|
|
|
|
|
|
} |
1856
|
|
|
|
|
|
|
|
1857
|
|
|
|
|
|
|
else { |
1858
|
|
|
|
|
|
|
$across += 1 |
1859
|
|
|
|
|
|
|
} |
1860
|
|
|
|
|
|
|
} |
1861
|
|
|
|
|
|
|
|
1862
|
|
|
|
|
|
|
return $self->write_jpeg($im); |
1863
|
|
|
|
|
|
|
} |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
sub write_jpeg { |
1866
|
|
|
|
|
|
|
my $self = shift; |
1867
|
|
|
|
|
|
|
my $im = shift; |
1868
|
|
|
|
|
|
|
my $out = shift; |
1869
|
|
|
|
|
|
|
|
1870
|
|
|
|
|
|
|
if (! defined($out)){ |
1871
|
|
|
|
|
|
|
$out = $self->mk_tempfile(".jpg"); |
1872
|
|
|
|
|
|
|
} |
1873
|
|
|
|
|
|
|
|
1874
|
|
|
|
|
|
|
my $fh = FileHandle->new(">$out"); |
1875
|
|
|
|
|
|
|
|
1876
|
|
|
|
|
|
|
binmode($fh); |
1877
|
|
|
|
|
|
|
$fh->print($im->jpeg(100)); |
1878
|
|
|
|
|
|
|
$fh->close(); |
1879
|
|
|
|
|
|
|
|
1880
|
|
|
|
|
|
|
return $out; |
1881
|
|
|
|
|
|
|
} |
1882
|
|
|
|
|
|
|
|
1883
|
|
|
|
|
|
|
# |
1884
|
|
|
|
|
|
|
# datetime |
1885
|
|
|
|
|
|
|
# |
1886
|
|
|
|
|
|
|
|
1887
|
|
|
|
|
|
|
sub calculate_delta_days { |
1888
|
|
|
|
|
|
|
my $self = shift; |
1889
|
|
|
|
|
|
|
my $dt = shift; |
1890
|
|
|
|
|
|
|
my $offset = shift; |
1891
|
|
|
|
|
|
|
|
1892
|
|
|
|
|
|
|
$dt =~ /^(\d{4})-(\d{2})-(\d{2})/; |
1893
|
|
|
|
|
|
|
|
1894
|
|
|
|
|
|
|
my $yyyy = $1; |
1895
|
|
|
|
|
|
|
my $mm = $2; |
1896
|
|
|
|
|
|
|
my $dd = $3; |
1897
|
|
|
|
|
|
|
|
1898
|
|
|
|
|
|
|
my $before = sprintf("%04d-%02d-%02d 00:00:00", Add_Delta_Days($yyyy, $mm, $dd, -$offset)); |
1899
|
|
|
|
|
|
|
my $after = sprintf("%04d-%02d-%02d 23:59:59", Add_Delta_Days($yyyy, $mm, $dd, $offset)); |
1900
|
|
|
|
|
|
|
|
1901
|
|
|
|
|
|
|
return ($before, $after); |
1902
|
|
|
|
|
|
|
} |
1903
|
|
|
|
|
|
|
|
1904
|
|
|
|
|
|
|
sub today { |
1905
|
|
|
|
|
|
|
my $self = shift; |
1906
|
|
|
|
|
|
|
return sprintf("%04d-%02d-%02d", Today()); |
1907
|
|
|
|
|
|
|
} |
1908
|
|
|
|
|
|
|
|
1909
|
|
|
|
|
|
|
# |
1910
|
|
|
|
|
|
|
# hey ! look over there !! |
1911
|
|
|
|
|
|
|
# |
1912
|
|
|
|
|
|
|
|
1913
|
|
|
|
|
|
|
package Net::Flickr::Geo::ModestMaps::MarkerSet; |
1914
|
|
|
|
|
|
|
|
1915
|
|
|
|
|
|
|
sub prepare { |
1916
|
|
|
|
|
|
|
my $pkg = shift; |
1917
|
|
|
|
|
|
|
my $markers = shift; |
1918
|
|
|
|
|
|
|
|
1919
|
|
|
|
|
|
|
if (ref($markers) ne "ARRAY"){ |
1920
|
|
|
|
|
|
|
return "$markers"; |
1921
|
|
|
|
|
|
|
} |
1922
|
|
|
|
|
|
|
|
1923
|
|
|
|
|
|
|
my @prep = map { "$_" } @$markers; |
1924
|
|
|
|
|
|
|
return \@prep; |
1925
|
|
|
|
|
|
|
} |
1926
|
|
|
|
|
|
|
|
1927
|
|
|
|
|
|
|
package Net::Flickr::Geo::ModestMaps::Marker; |
1928
|
|
|
|
|
|
|
|
1929
|
|
|
|
|
|
|
use overload q("") => sub { |
1930
|
|
|
|
|
|
|
my $self = shift; |
1931
|
|
|
|
|
|
|
|
1932
|
|
|
|
|
|
|
my @parts = map { |
1933
|
|
|
|
|
|
|
$self->{$_} |
1934
|
|
|
|
|
|
|
} qw(uid lat lon width height); |
1935
|
|
|
|
|
|
|
|
1936
|
|
|
|
|
|
|
return join(",", @parts); |
1937
|
|
|
|
|
|
|
}; |
1938
|
|
|
|
|
|
|
|
1939
|
|
|
|
|
|
|
sub new { |
1940
|
|
|
|
|
|
|
my $pkg = shift; |
1941
|
|
|
|
|
|
|
my %self = @_; |
1942
|
|
|
|
|
|
|
return bless \%self, $pkg; |
1943
|
|
|
|
|
|
|
} |
1944
|
|
|
|
|
|
|
|
1945
|
|
|
|
|
|
|
=head1 VERSION |
1946
|
|
|
|
|
|
|
|
1947
|
|
|
|
|
|
|
0.72 |
1948
|
|
|
|
|
|
|
|
1949
|
|
|
|
|
|
|
=head1 DATE |
1950
|
|
|
|
|
|
|
|
1951
|
|
|
|
|
|
|
$Date: 2008/08/03 17:08:39 $ |
1952
|
|
|
|
|
|
|
|
1953
|
|
|
|
|
|
|
=head1 AUTHOR |
1954
|
|
|
|
|
|
|
|
1955
|
|
|
|
|
|
|
Aaron Straup Cope Eascope@cpan.orgE |
1956
|
|
|
|
|
|
|
|
1957
|
|
|
|
|
|
|
=head1 EXAMPLES |
1958
|
|
|
|
|
|
|
|
1959
|
|
|
|
|
|
|
L |
1960
|
|
|
|
|
|
|
|
1961
|
|
|
|
|
|
|
=head1 REQUIREMENTS |
1962
|
|
|
|
|
|
|
|
1963
|
|
|
|
|
|
|
Modest Maps 1.0 or higher. |
1964
|
|
|
|
|
|
|
|
1965
|
|
|
|
|
|
|
L |
1966
|
|
|
|
|
|
|
|
1967
|
|
|
|
|
|
|
=head1 NOTES |
1968
|
|
|
|
|
|
|
|
1969
|
|
|
|
|
|
|
All uploads to Flickr are marked with a content-type of "other". |
1970
|
|
|
|
|
|
|
|
1971
|
|
|
|
|
|
|
=head1 SEE ALSO |
1972
|
|
|
|
|
|
|
|
1973
|
|
|
|
|
|
|
L |
1974
|
|
|
|
|
|
|
|
1975
|
|
|
|
|
|
|
L |
1976
|
|
|
|
|
|
|
|
1977
|
|
|
|
|
|
|
L |
1978
|
|
|
|
|
|
|
|
1979
|
|
|
|
|
|
|
L |
1980
|
|
|
|
|
|
|
|
1981
|
|
|
|
|
|
|
L |
1982
|
|
|
|
|
|
|
|
1983
|
|
|
|
|
|
|
=head1 BUGS |
1984
|
|
|
|
|
|
|
|
1985
|
|
|
|
|
|
|
Sure, why not. |
1986
|
|
|
|
|
|
|
|
1987
|
|
|
|
|
|
|
Please report all bugs via L |
1988
|
|
|
|
|
|
|
|
1989
|
|
|
|
|
|
|
=head1 LICENSE |
1990
|
|
|
|
|
|
|
|
1991
|
|
|
|
|
|
|
Copyright (c) 2007-2008 Aaron Straup Cope. All Rights Reserved. |
1992
|
|
|
|
|
|
|
|
1993
|
|
|
|
|
|
|
This is free software. You may redistribute it and/or |
1994
|
|
|
|
|
|
|
modify it under the same terms as Perl itself. |
1995
|
|
|
|
|
|
|
|
1996
|
|
|
|
|
|
|
=cut |
1997
|
|
|
|
|
|
|
|
1998
|
|
|
|
|
|
|
return 1; |