| 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; |