ym4r_gm 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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