ym4r_gm 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ module Ym4r
2
+ module GmPlugin
3
+ #Small map control. Report to the Google Maps API documentation for details.
4
+ class GSmallMapControl
5
+ include MappingObject
6
+ def create
7
+ "new GSmallMapControl()"
8
+ end
9
+ end
10
+ #Large Map control. Report to the Google Maps API documentation for details.
11
+ class GLargeMapControl
12
+ include MappingObject
13
+ def create
14
+ "new GLargeMapControl()"
15
+ end
16
+ end
17
+ #Small Zoom control. Report to the Google Maps API documentation for details.
18
+ class GSmallZoomControl
19
+ include MappingObject
20
+ def create
21
+ "new GSmallZoomControl()"
22
+ end
23
+ end
24
+ #Scale control. Report to the Google Maps API documentation for details.
25
+ class GScaleControl
26
+ include MappingObject
27
+ def create
28
+ "new GScaleControl()"
29
+ end
30
+ end
31
+ #Map type control. Report to the Google Maps API documentation for details.
32
+ class GMapTypeControl
33
+ include MappingObject
34
+ def create
35
+ "new GMapTypeControl()"
36
+ end
37
+ end
38
+ #Map type control. Report to the Google Maps API documentation for details.
39
+ class GHierarchicalMapTypeControl
40
+ include MappingObject
41
+ def create
42
+ "new GHierarchicalMapTypeControl()"
43
+ end
44
+ end
45
+ #Overview map control. Report to the Google Maps API documentation for details.
46
+ class GOverviewMapControl
47
+ include MappingObject
48
+ def initialize(size = nil)
49
+ @size = size
50
+ end
51
+ def create
52
+ "new GOverviewMapControl(#{@size ? @size.create : ''})"
53
+ end
54
+ end
55
+
56
+ # Local Search control. Report to the Google Maps API documentation for details.
57
+ # The first argument of the constructor is one of the following: :top_right, :top_left, :bottom_right, :bottom_left.
58
+ # The second and third arguments of the constructor are the offset width and height respectively in pixels.
59
+ # The fourth argument is a javascript hash of valid Google local search control options
60
+ # (ex. {suppressZoomToBounds : true, resultList : google.maps.LocalSearch.RESULT_LIST_INLINE,
61
+ # suppressInitialResultSelection : true, searchFormHint : 'Local Search powered by Google',
62
+ # linkTarget : GSearch.LINK_TARGET_BLANK})
63
+ class GLocalSearchControl < Struct.new(:anchor, :offset_width, :offset_height, :options)
64
+ include MappingObject
65
+ def create
66
+ if offset_width.nil?
67
+ ow = 10
68
+ else
69
+ ow = offset_width
70
+ end
71
+ if offset_height.nil?
72
+ oh = 20
73
+ else
74
+ oh = offset_height
75
+ end
76
+ js_anchor = if anchor == :top_right
77
+ "G_ANCHOR_TOP_RIGHT"
78
+ elsif anchor == :top_left
79
+ "G_ANCHOR_TOP_LEFT"
80
+ elsif anchor == :bottom_right
81
+ "G_ANCHOR_BOTTOM_RIGHT"
82
+ else
83
+ "G_ANCHOR_BOTTOM_LEFT"
84
+ end
85
+ "new google.maps.LocalSearch(options), new GControlPosition(#{js_anchor}, new GSize(#{ow},#{oh}))"
86
+ end
87
+ end
88
+
89
+ #An object representing a position of a control.
90
+ #The first argument of the constructor is one of the following : :top_right, :top_left, :bottom_right, :bottom_left.
91
+ class GControlPosition < Struct.new(:anchor,:offset)
92
+ include MappingObject
93
+ def create
94
+ js_anchor = if anchor == :top_right
95
+ "G_ANCHOR_TOP_RIGHT"
96
+ elsif anchor == :top_left
97
+ "G_ANCHOR_TOP_LEFT"
98
+ elsif anchor == :bottom_right
99
+ "G_ANCHOR_BOTTOM_RIGHT"
100
+ else
101
+ "G_ANCHOR_BOTTOM_LEFT"
102
+ end
103
+ "new GControlPosition(#{js_anchor},#{offset})"
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,395 @@
1
+ #--
2
+ #
3
+ # Utility for creating Google Maps Encoded GPolylines
4
+ #
5
+ # License: You may distribute this code under the same terms as Ruby itself
6
+ #
7
+ # Author: Joel Rosenberg
8
+ # Modified for use in YM4R by Ken-ichi Ueda
9
+ #
10
+ # ( Drawing from the official example pages as well as Mark McClure's work )
11
+ #
12
+ # == Example
13
+ #
14
+ # data = [
15
+ # [ 37.4419, -122.1419],
16
+ # [ 37.4519, -122.1519],
17
+ # [ 37.4619, -122.1819],
18
+ # ]
19
+ #
20
+ # encoder = GMapPolylineEncoder.new()
21
+ # result = encoder.encode( data )
22
+ #
23
+ # javascript << " var myLine = new GPolyline.fromEncoded({\n"
24
+ # javascript << " color: \"#FF0000\",\n"
25
+ # javascript << " weight: 10,\n"
26
+ # javascript << " opacity: 0.5,\n"
27
+ # javascript << " zoomFactor: #{result[:zoomFactor]},\n"
28
+ # javascript << " numLevels: #{result[:numLevels]},\n"
29
+ # javascript << " points: \"#{result[:points]}\",\n"
30
+ # javascript << " levels: \"#{result[:levels]}\"\n"
31
+ # javascript << " });"
32
+ #
33
+ # == Methods
34
+ #
35
+ # Constructor args (all optional):
36
+ # :numLevels (default 18)
37
+ # :zoomFactor (default 2)
38
+ # :reduce: Reduce points (default true)
39
+ # :escape: Escape backslashes (default true)
40
+ #
41
+ # encode( points ) method
42
+ # points (required): array of longitude, latitude pairs
43
+ #
44
+ # returns hash with keys :points, :levels, :zoomFactor, :numLevels
45
+ #
46
+ # == Background
47
+ #
48
+ # Description: http://www.google.com/apis/maps/documentation/#Encoded_Polylines
49
+ # API: http://www.google.com/apis/maps/documentation/reference.html#GPolyline
50
+ # Hints: http://www.google.com/apis/maps/documentation/polylinealgorithm.html
51
+ #
52
+ # Example Javascript for instantiating an encoded polyline:
53
+ # var encodedPolyline = new GPolyline.fromEncoded({
54
+ # color: "#FF0000",
55
+ # weight: 10,
56
+ # points: "yzocFzynhVq}@n}@o}@nzD",
57
+ # levels: "BBB",
58
+ # zoomFactor: 32,
59
+ # numLevels: 4
60
+ # });
61
+ #
62
+ # == Changes
63
+ #
64
+ # 06.29.2007 - Release 0.1
65
+ # Profiling showed that distance() accounted for 50% of the time when
66
+ # processing McClure's British coast data. By moving the distance
67
+ # calculation into encode(), we can cache a few of the calculations
68
+ # (magnitude) and eliminate the overhead of the function call. This
69
+ # reduced the time to encode by ~ 30%
70
+ #
71
+ # 06.21.2007 Implementing the Doublas-Peucker algorithm for removing superflous
72
+ # points as per Mark McClure's design:
73
+ # http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/
74
+ #
75
+ # 10.14.2006 Cleaned up (and finally grasped) zoom levels
76
+ #
77
+ # 09.2006 First port of the official example's javascript. Ignoring zoom
78
+ # levels for now, showing points at all zoom levels
79
+ #
80
+ #++
81
+
82
+ module Ym4r
83
+ module GmPlugin
84
+ class GMapPolylineEncoder
85
+ attr_accessor :reduce, :escape #zoomFactor and numLevels need side effects
86
+ attr_reader :zoomFactor, :numLevels
87
+
88
+ # The minimum distance from the line that a point must exceed to avoid
89
+ # elimination under the DP Algorithm.
90
+ @@dp_threshold = 0.00001
91
+
92
+ def initialize(options = {})
93
+ # There are no required parameters
94
+
95
+ # Nice defaults
96
+ @numLevels = options.has_key?(:numLevels) ? options[:numLevels] : 18
97
+ @zoomFactor = options.has_key?(:zoomFactor) ? options[:zoomFactor] : 2
98
+
99
+ # Calculate the distance thresholds for each zoom level
100
+ calculate_zoom_breaks()
101
+
102
+ # By default we'll simplify the polyline unless told otherwise
103
+ @reduce = ! options.has_key?(:reduce) ? true : options[:reduce]
104
+
105
+ # Escape by default; most people are using this in a web context
106
+ @escape = ! options.has_key?(:escape) ? true : options[:escape]
107
+
108
+ end
109
+
110
+ def numLevels=( new_num_levels )
111
+ @numLevels = new_num_levels
112
+ # We need to recalculate our zoom breaks
113
+ calculate_zoom_breaks()
114
+ end
115
+
116
+ def zoomFactor=( new_zoom_factor )
117
+ @zoomFactor = new_zoom_factor
118
+ # We need to recalculate our zoom breaks
119
+ calculate_zoom_breaks()
120
+ end
121
+
122
+ def encode( points )
123
+
124
+ #
125
+ # This is an implementation of the Douglas-Peucker algorithm for simplifying
126
+ # a line. You can thing of it as an elimination of points that do not
127
+ # deviate enough from a vector. That threshold for point elimination is in
128
+ # @@dp_threshold. See
129
+ #
130
+ # http://everything2.com/index.pl?node_id=859282
131
+ #
132
+ # for an explanation of the algorithm
133
+ #
134
+
135
+ max_dist = 0 # Greatest distance we measured during the run
136
+ stack = []
137
+ distances = Array.new(points.size)
138
+
139
+ if(points.length > 2)
140
+ stack << [0, points.size-1]
141
+
142
+ while(stack.length > 0)
143
+ current_line = stack.pop()
144
+ p1_idx = current_line[0]
145
+ pn_idx = current_line[1]
146
+ pb_dist = 0
147
+ pb_idx = nil
148
+
149
+ x1 = points[p1_idx][0]
150
+ y1 = points[p1_idx][1]
151
+ x2 = points[pn_idx][0]
152
+ y2 = points[pn_idx][1]
153
+
154
+ # Caching the line's magnitude for performance
155
+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
156
+ magnitude_squared = magnitude ** 2
157
+
158
+ # Find the farthest point and its distance from the line between our pair
159
+ for i in (p1_idx+1)..(pn_idx-1)
160
+
161
+ # Refactoring distance computation inline for performance
162
+ #current_distance = compute_distance(points[i], points[p1_idx], points[pn_idx])
163
+
164
+ #
165
+ # This uses Euclidian geometry. It shouldn't be that big of a deal since
166
+ # we're using it as a rough comparison for line elimination and zoom
167
+ # calculation.
168
+ #
169
+ # TODO: Implement Haversine functions which would probably bring this to
170
+ # a snail's pace (ehhhh)
171
+ #
172
+
173
+ px = points[i][0]
174
+ py = points[i][1]
175
+
176
+ current_distance = nil
177
+
178
+ if( magnitude == 0 )
179
+ # The line is really just a point
180
+ current_distance = Math.sqrt((x2-px)**2 + (y2-py)**2)
181
+ else
182
+
183
+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / magnitude_squared
184
+
185
+ if( u <= 0 || u > 1 )
186
+ # The point is closest to an endpoint. Find out which one
187
+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
188
+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
189
+ if( ix > iy )
190
+ current_distance = iy
191
+ else
192
+ current_distance = ix
193
+ end
194
+ else
195
+ # The perpendicular point intersects the line
196
+ ix = x1 + u * (x2 - x1)
197
+ iy = y1 + u * (y2 - y1)
198
+ current_distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
199
+ end
200
+ end
201
+
202
+ # See if this distance is the greatest for this segment so far
203
+ if(current_distance > pb_dist)
204
+ pb_dist = current_distance
205
+ pb_idx = i
206
+ end
207
+ end
208
+
209
+ # See if this is the greatest distance for all points
210
+ if(pb_dist > max_dist)
211
+ max_dist = pb_dist
212
+ end
213
+
214
+ if(pb_dist > @@dp_threshold)
215
+ # Our point, Pb, that had the greatest distance from the line, is also
216
+ # greater than our threshold. Process again using Pb as a new
217
+ # start/end point. Record this distance - we'll use it later when
218
+ # creating zoom values
219
+ distances[pb_idx] = pb_dist
220
+ stack << [p1_idx, pb_idx]
221
+ stack << [pb_idx, pn_idx]
222
+ end
223
+
224
+ end
225
+ end
226
+
227
+ # Force line endpoints to be included (sloppy, but faster than checking for
228
+ # endpoints in encode_points())
229
+ distances[0] = max_dist
230
+ distances[distances.length-1] = max_dist
231
+
232
+ # Create Base64 encoded strings for our points and zoom levels
233
+ points_enc = encode_points( points, distances)
234
+ levels_enc = encode_levels( points, distances, max_dist)
235
+
236
+ # Make points_enc an escaped string if desired.
237
+ # We should escape the levels too, in case google pulls a switcheroo
238
+ @escape && points_enc && points_enc.gsub!( /\\/, '\\\\\\\\' )
239
+
240
+
241
+ # Returning a hash. Yes, I am a Perl programmer
242
+ return {
243
+ :points => points_enc,
244
+ :levels => levels_enc,
245
+ :zoomFactor => @zoomFactor,
246
+ :numLevels => @numLevels,
247
+ }
248
+
249
+ end
250
+
251
+ private
252
+
253
+ def calculate_zoom_breaks()
254
+ # Calculate the distance thresholds for each zoom level
255
+ @zoom_level_breaks = Array.new(@numLevels);
256
+
257
+ for i in 0..(@numLevels-1)
258
+ @zoom_level_breaks[i] = @@dp_threshold * (@zoomFactor ** ( @numLevels-i-1));
259
+ end
260
+
261
+ return
262
+ end
263
+
264
+ def encode_points( points, distances )
265
+ encoded = ""
266
+
267
+ plat = 0
268
+ plon = 0
269
+
270
+ #points.each do |point| # Gah, need the distances.
271
+ for i in 0..(points.size() - 1)
272
+ if(! @reduce || distances[i] != nil )
273
+ point = points[i]
274
+ late5 = (point[0] * 1e5).floor();
275
+ lone5 = (point[1] * 1e5).floor();
276
+
277
+ dlat = late5 - plat
278
+ dlon = lone5 - plon
279
+
280
+ plat = late5;
281
+ plon = lone5;
282
+
283
+ # I used to need this for some reason
284
+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlat)).to_s
285
+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlon)).to_s
286
+ encoded << encodeSignedNumber(dlat).to_s
287
+ encoded << encodeSignedNumber(dlon).to_s
288
+ end
289
+ end
290
+
291
+ return encoded
292
+
293
+ end
294
+
295
+ def encode_levels( points, distances, max_dist )
296
+
297
+ encoded = "";
298
+
299
+ # Force startpoint
300
+ encoded << encodeNumber(@numLevels - 1)
301
+
302
+ if( points.size() > 2 )
303
+ for i in 1..(points.size() - 2)
304
+ distance = distances[i]
305
+ if( ! @reduce || distance != nil)
306
+ computed_level = 0
307
+
308
+ while (distance < @zoom_level_breaks[computed_level]) do
309
+ computed_level += 1
310
+ end
311
+
312
+ encoded << encodeNumber( @numLevels - computed_level - 1 )
313
+ end
314
+ end
315
+ end
316
+
317
+ # Force endpoint
318
+ encoded << encodeNumber(@numLevels - 1)
319
+
320
+ return encoded;
321
+
322
+ end
323
+
324
+ def compute_distance( point, lineStart, lineEnd )
325
+
326
+ #
327
+ # Note: This has been refactored to encode() inline for performance and
328
+ # computation caching
329
+ #
330
+
331
+ px = point[0]
332
+ py = point[1]
333
+ x1 = lineStart[0]
334
+ y1 = lineStart[1]
335
+ x2 = lineEnd[0]
336
+ y2 = lineEnd[1]
337
+
338
+ distance = nil
339
+
340
+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
341
+
342
+ if( magnitude == 0 )
343
+ return Math.sqrt((x2-px)**2 + (y2-py)**2)
344
+ end
345
+
346
+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / (magnitude**2)
347
+
348
+ if( u <= 0 || u > 1 )
349
+ # The point is closest to an endpoint. Find out which
350
+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
351
+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
352
+ if( ix > iy )
353
+ distance = iy
354
+ else
355
+ distance = ix
356
+ end
357
+ else
358
+ # The perpendicular point intersects the line
359
+ ix = x1 + u * (x2 - x1)
360
+ iy = y1 + u * (y2 - y1)
361
+ distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
362
+ end
363
+
364
+ return distance
365
+ end
366
+
367
+ def encodeSignedNumber(num)
368
+ # Based on the official google example
369
+
370
+ sgn_num = num << 1
371
+
372
+ if( num < 0 )
373
+ sgn_num = ~(sgn_num)
374
+ end
375
+
376
+ return encodeNumber(sgn_num)
377
+ end
378
+
379
+ def encodeNumber(num)
380
+ # Based on the official google example
381
+
382
+ encoded = "";
383
+
384
+ while (num >= 0x20) do
385
+ encoded << ((0x20 | (num & 0x1f)) + 63).chr;
386
+ num = num >> 5;
387
+ end
388
+
389
+ encoded << (num + 63).chr;
390
+ return encoded;
391
+ end
392
+
393
+ end
394
+ end
395
+ end