ym4r 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,5 +1,5 @@
1
1
  =YM4R
2
- This is YM4R 0.3.1. The goal of YM4R (which naturally means Yellow Maps For Ruby...) is to ease the use of the Google Maps (including the Geocoding API) and Yahoo! Maps API's (including the Geocoding, Map Image, Traffic and Local Search API's) from Ruby and Rails.
2
+ This is YM4R 0.3.2. The goal of YM4R (Yellow Maps For Ruby) is to ease the use of the Google Maps (including the Geocoding API) and Yahoo! Maps API's (including the Geocoding, Map Image, Traffic and Local Search API's) from Ruby and Rails.
3
3
 
4
4
  ==Operations
5
5
  ===Google Maps
@@ -58,7 +58,7 @@ What is sent to the browser will be the fllowing JavaScript code:
58
58
  A new type of GOverlay is available, called GMarkerGroup. To use it you would have to include in your HTML template the JavaScript file markerGroup.js found in the <tt>lib/ym4r/google_maps/javascript</tt> directory of the distribution.
59
59
 
60
60
  It is useful in 2 situations:
61
- - Display and undisplay a group of markers without referencing all of them. You just declare your marker group globally and call +activate+ and +deactivate+ on this group in response, for example, to clicks on links on your page..
61
+ - Display and undisplay a group of markers without referencing all of them. You just declare your marker group globally and call +activate+ and +deactivate+ on this group in response, for example, to clicks on links on your page.
62
62
  - Keeping an index of markers, for example, in order to show one of these markers in reponse to a click on a link (the way Google Local does with the results of the search).
63
63
 
64
64
  Here is how you would use it from your Ruby code:
@@ -76,7 +76,32 @@ Then in your template, you could have that:
76
76
  <%= @map.to_div %>
77
77
  When you click on one of the links, the corresponding marker has its info window displayed.
78
78
 
79
- You can call +activate+ and +deactivate+ to display or undisplay a group of markers. You can add new markers with +addMarker(marker,id)+. Again if you don't care about referencing the marker, you don't need to pass an id. If the marker group is active, the newly added marker will be displayed immediately. Otherwise it will be displayed the next time the group is activated. Finally, since it is an overlay, the group will be removed when calling clearOverlays on the GMap object.
79
+ You can call +activate+ and +deactivate+ to display or undisplay a group of markers. You can add new markers with <tt>addMarker(marker,id)</tt>. Again if you don't care about referencing the marker, you don't need to pass an id. If the marker group is active, the newly added marker will be displayed immediately. Otherwise it will be displayed the next time the group is activated. Finally, since it is an overlay, the group will be removed when calling clearOverlays on the GMap object.
80
+
81
+ ====Clusterer
82
+ A Clusterer is a type of overlay that contains a potentially very large group of markers. It is engineered so markers close to each other and undistinguishable from each other at some level of zoom, appear as only one marker on the map at that level of zoom. Therefore it is possible to have a very large number of markers on the map at the same time and not having the map crawl to a halt in order to display them. It has been slightly modified from Jef Poskanzer's original Clusterer2 code (see http://www.acme.com/javascript/ for the original version). The major difference with that version is that, in YM4R, the clusterer is a GOverlay and can therefore be added to the map with <tt>map.addOverlay(clusterer)</tt> and is cleared along with the rest of overlays when <tt>map.clearOverlays()</tt> is called.
83
+
84
+ <b>In order to use a clusterer, you should include in your template page the clusterer.js file that can be found in the <tt>lib/ym4r/gogle_maps/javascript/</tt> directory of the YM4R distribution.</b>
85
+
86
+ To create a clusterer, you should first create an array of all the GMarkers that you want to include in the clusterer (you can still add more after the clusterer has been added to the map though). When GMarkers close together are grouped in one cluster (represented by another marker on the map) and the user clicks on the cluster marker, a list of markers in the cluster is displayed in the info windo with a description: By default it is equal to the +title+ of the markers (which is also displayed as a tooltip when hovering on the marker with the mouse). If you don't want that, you can also pass to the GMarker constructor a key-value pair with key <tt>:description</tt> to have a description different from the title. For example, here is how you would create a clusterer:
87
+ markers = [GMarker.new([37.8,-90.4819],:info_window => "Hello",:title => "HOYOYO"),
88
+ GMarker.new([37.844,-90.47819],:info_window => "Namaste",:description => "Chopoto" , :title => "Ay"),
89
+ GMarker.new([37.83,-90.456619],:info_window => "Bonjour",:title => "Third"),
90
+ ]
91
+ clusterer = Clusterer.new(markers,:max_visible_markers => 2)
92
+ Of course, with only 3 markers, the whole clusterer thing is totally useless. Usually, using the clusterer starts being interesting with hundreds of markers. The options to pass the clusterer are:
93
+ - <tt>:max_visible_markers</tt>: Below this number of markers, no clustering is performed. Defaults to 150.
94
+ - <tt>:grid_size</tt>: The clusterer divides the visible area into a grid of this size. Defaults to 5.
95
+ - <tt>:min_markers_per_cluster</tt>: Below this number of markers a cluster is not formed. Defaults to 5.
96
+ - <tt>:max_lines_per_info_box</tt>: Number of lines to display in the info window displayed when a cluster is clicked on by the user. Defaults to 10.
97
+ - <tt>:icon</tt>: Icon to be used to mark a cluster. Defaults to G_DEFAULT_ICON (the classic red marker).
98
+
99
+ Then to add the clusterer to the map, you proceed as with a classic overlay:
100
+ map.addOverlay(clusterer)
101
+
102
+ To add new markers in RJS, you should do this:
103
+ page << clusterer.add_marker(marker,description)
104
+ In this case, the <tt>:description</tt> passed to the GMarker contructor will not be taken into account. Only the +description+ passed in the call to +add_marker+ will.
80
105
 
81
106
  ====Adding new map types
82
107
  It is now possible to easily add new map types, on top of the already existing ones, like G_SATELLITE_MAP or G_NORMAL_MAP. The imagery for these new map types can come from layers of the standard map types or can be taken either from a WMS server or from pretiled images on a server (that can be generated with a tool that comes with the library).
@@ -96,9 +121,26 @@ Here is how to define a pretiled layer:
96
121
  layer = PreTiledLayer.new("http://localhost:3000/tiles",
97
122
  {'prefix' => "Map C 2006", 'copyright_texts' => ["Open Atlas"]},
98
123
  13..13,0.7,"gif")
99
- I tell the PreTiledLayer constructor where I can find the tiles, setup the Copyright string, the valid zooms for the map, the opacity and the format. Tiles must have standardized names: <tt>tile_#{zoom}_#{x_tile}_#{y_tile}.#{format}</tt> (here the format is "gif"). You can use some tools (see below) to generate tiles in this format either from local maps or from WMS servers (useful to create tiles from geographic data files without having to run a map server or to cache images from slow servers).
124
+ I tell the PreTiledLayer constructor where I can find the tiles, setup the Copyright string, the valid zooms for the map, the opacity and the format. Tiles must have standardized names: <tt>tile_#{zoom}_#{x_tile}_#{y_tile}.#{format}</tt> (here the format is "gif"). You can use some tools (see below) to generate tiles in this format either from local maps or from WMS servers (useful to create tiles from geographic data files without having to run a map server or to cache images from slow servers).
100
125
 
101
- You can add such a layer to a new map type the following way:
126
+ Instead of having the tiles requested directly, you can also decide to have an action on the server which takes care of it. You can used the class PreTiledLayerFromAction for this. In this case, the first argument of the constructor is an url of an action. The arguments +x+, +y+ and +z+ will be passed to it.
127
+ layer = PreTiledLayerFromAction.new(url_for(:action => :get_tile),
128
+ {'prefix' => "Map C 2006", 'copyright_texts' => ["Open Atlas"]},
129
+ 13..14,0.7,"png")
130
+ The other constructor arguments have the same meaning as PreTiledLayer. Here is an uninteresting example of action that serves tiles:
131
+ def get_tile
132
+ x = @params[:x]
133
+ y = @params[:y]
134
+ z = @params[:z]
135
+ begin
136
+ send_file "#{RAILS_ROOT}/public/tiles/tile_#{z}_#{x}_#{y}.png" ,
137
+ :type => 'image/png', :disposition => 'inline'
138
+ rescue Exception
139
+ render :nothing => true
140
+ end
141
+ end
142
+
143
+ You can add a layer to a new map type the following way:
102
144
  map_type = GMapType.new(layer,"My WMS")
103
145
  This is actually the simplest configuration possible. Your map type has only one data layer and is called "My WMS". You can add more that one layer: Either one that you have created yourself or existing ones. For example:
104
146
  map_type = GMapType.new([GMapType::G_SATELLITE_MAP.get_tile_layers[0],layer,GMapType::G_HYBRID_MAP.get_tile_layers[1]],
@@ -202,19 +244,22 @@ Here is an example of request:
202
244
  Preliminary support for this API has been added. It works along the same lines as with the Google Maps API but it is not very polished currently.
203
245
 
204
246
  ==Changes since last version
205
- - Addition of a utility class to use the Google Maps API
247
+ - Addition of Clusterer class
248
+ - Addition of a PreTiledLayerFromAction class
206
249
 
207
250
  ==TODO
208
- - Add support for easy manipulation of external Google Maps-related libraries: Advanced tooltip manipulation, GeoRSS, Clusterer...
209
- - Add support to show and hide groups of overlays easily
251
+ - Add support for easy manipulation of external Google Maps-related libraries: Advanced tooltip manipulation, GeoRSS, KML,...
210
252
  - Documentation! Documentation! Documentation!
211
253
  - Tutorials
212
254
 
213
255
  ==Disclaimer
214
256
  This software is not endorsed in any way by Yahoo! or Google.
215
257
 
258
+ ==Acknowledgement
259
+ YM4R bundles JavaScript libraries from John Deck (WMS layers on Google Maps) and Jef Poskanzer (Clusterer on Google Maps).
260
+
216
261
  ==License
217
- YM4R is released under the MIT license.
262
+ YM4R is released under the MIT license. The <tt>clusterer.js</tt> file is redistributed with a different license (but still compatible with the MIT license). Check the top of the file in <tt>lib/ym4r/google_maps/javascript</tt> to know more.
218
263
 
219
264
  ==Support
220
265
  Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut+ym4r@gmail.com.
@@ -0,0 +1,444 @@
1
+ // Clusterer.js - marker clustering routines for Google Maps apps
2
+ //
3
+ // The original version of this code is available at:
4
+ // http://www.acme.com/javascript/
5
+ //
6
+ // Copyright � 2005,2006 by Jef Poskanzer <jef@mail.acme.com>.
7
+ // All rights reserved.
8
+ //
9
+ // Modified for inclusion into the YM4R library in accordance with the
10
+ // following license:
11
+ //
12
+ // Redistribution and use in source and binary forms, with or without
13
+ // modification, are permitted provided that the following conditions
14
+ // are met:
15
+ // 1. Redistributions of source code must retain the above copyright
16
+ // notice, this list of conditions and the following disclaimer.
17
+ // 2. Redistributions in binary form must reproduce the above copyright
18
+ // notice, this list of conditions and the following disclaimer in the
19
+ // documentation and/or other materials provided with the distribution.
20
+ //
21
+ // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
22
+ // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
+ // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
25
+ // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27
+ // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28
+ // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
+ // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30
+ // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31
+ // SUCH DAMAGE.
32
+ //
33
+ // For commentary on this license please see http://www.acme.com/license.html
34
+
35
+
36
+ // Constructor.
37
+ Clusterer = function(markers,icon,maxVisibleMarkers,gridSize,minMarkersPerCluster,maxLinesPerInfoBox) {
38
+ this.markers = [];
39
+ if(markers){
40
+ for(var i =0 ; i< markers.length ; i++){
41
+ this.addMarker(markers[i]);
42
+ }
43
+ }
44
+ this.clusters = [];
45
+ this.timeout = null;
46
+
47
+ this.maxVisibleMarkers = maxVisibleMarkers || 150;
48
+ this.gridSize = gridSize || 5;
49
+ this.minMarkersPerCluster = minMarkersPerCluster || 5;
50
+ this.maxLinesPerInfoBox = maxLinesPerInfoBox || 10;
51
+
52
+ this.icon = icon || G_DEFAULT_ICON;
53
+ }
54
+
55
+ Clusterer.prototype = new GOverlay();
56
+
57
+ Clusterer.prototype.initialize = function ( map ){
58
+ this.map = map;
59
+ this.currentZoomLevel = map.getZoom();
60
+
61
+ GEvent.addListener( map, 'zoomend', Clusterer.makeCaller( Clusterer.display, this ) );
62
+ GEvent.addListener( map, 'moveend', Clusterer.makeCaller( Clusterer.display, this ) );
63
+ GEvent.addListener( map, 'infowindowclose', Clusterer.makeCaller( Clusterer.popDown, this ) );
64
+ //Set map for each marker
65
+ for(var i = 0,len = this.markers.length ; i < len ; i++){
66
+ this.markers[i].setMap( map );
67
+ }
68
+ this.displayLater();
69
+ }
70
+
71
+ Clusterer.prototype.remove = function(){
72
+ for ( var i = 0; i < this.markers.length; ++i ){
73
+ this.removeMarker(this.markers[i]);
74
+ }
75
+ }
76
+
77
+ Clusterer.prototype.copy = function(){
78
+ return new Clusterer(this.markers,this.icon,this.maxVisibleMarkers,this.gridSize,this.minMarkersPerCluster,this.maxLinesPerInfoBox);
79
+ }
80
+
81
+ Clusterer.prototype.redraw = function(force){
82
+ this.displayLater();
83
+ }
84
+
85
+ // Call this to change the cluster icon.
86
+ Clusterer.prototype.setIcon = function ( icon ){
87
+ this.icon = icon;
88
+ }
89
+
90
+ // Call this to add a marker.
91
+ Clusterer.prototype.addMarker = function ( marker, description){
92
+ marker.onMap = false;
93
+ this.markers.push( marker );
94
+ marker.description = marker.description || description;
95
+ if(this.map != null){
96
+ marker.setMap(this.map);
97
+ this.displayLater();
98
+ }
99
+ };
100
+
101
+
102
+ // Call this to remove a marker.
103
+ Clusterer.prototype.removeMarker = function ( marker ){
104
+ for ( var i = 0; i < this.markers.length; ++i )
105
+ if ( this.markers[i] == marker ){
106
+ if ( marker.onMap )
107
+ this.map.removeOverlay( marker );
108
+ for ( var j = 0; j < this.clusters.length; ++j ){
109
+ var cluster = this.clusters[j];
110
+ if ( cluster != null ){
111
+ for ( var k = 0; k < cluster.markers.length; ++k )
112
+ if ( cluster.markers[k] == marker ){
113
+ cluster.markers[k] = null;
114
+ --cluster.markerCount;
115
+ break;
116
+ }
117
+ if ( cluster.markerCount == 0 ){
118
+ this.clearCluster( cluster );
119
+ this.clusters[j] = null;
120
+ }
121
+ else if ( cluster == this.poppedUpCluster )
122
+ Clusterer.rePop( this );
123
+ }
124
+ }
125
+ this.markers[i] = null;
126
+ break;
127
+ }
128
+ this.displayLater();
129
+ };
130
+
131
+ Clusterer.prototype.displayLater = function (){
132
+ if ( this.timeout != null )
133
+ clearTimeout( this.timeout );
134
+ this.timeout = setTimeout( Clusterer.makeCaller( Clusterer.display, this ), 50 );
135
+ };
136
+
137
+ Clusterer.display = function ( clusterer ){
138
+ var i, j, marker, cluster, len, len2;
139
+
140
+ clearTimeout( clusterer.timeout );
141
+
142
+ var newZoomLevel = clusterer.map.getZoom();
143
+ if ( newZoomLevel != clusterer.currentZoomLevel ){
144
+ // When the zoom level changes, we have to remove all the clusters.
145
+ for ( i = 0 , len = clusterer.clusters.length; i < len; ++i ){
146
+ if ( clusterer.clusters[i] != null ){
147
+ clusterer.clearCluster( clusterer.clusters[i] );
148
+ clusterer.clusters[i] = null;
149
+ }
150
+ }
151
+ clusterer.clusters.length = 0;
152
+ clusterer.currentZoomLevel = newZoomLevel;
153
+ }
154
+
155
+ // Get the current bounds of the visible area.
156
+ var bounds = clusterer.map.getBounds();
157
+
158
+ // Expand the bounds a little, so things look smoother when scrolling
159
+ // by small amounts.
160
+ var sw = bounds.getSouthWest();
161
+ var ne = bounds.getNorthEast();
162
+ var dx = ne.lng() - sw.lng();
163
+ var dy = ne.lat() - sw.lat();
164
+ dx *= 0.10;
165
+ dy *= 0.10;
166
+ bounds = new GLatLngBounds(
167
+ new GLatLng( sw.lat() - dy, sw.lng() - dx ),
168
+ new GLatLng( ne.lat() + dy, ne.lng() + dx )
169
+ );
170
+
171
+ // Partition the markers into visible and non-visible lists.
172
+ var visibleMarkers = [];
173
+ var nonvisibleMarkers = [];
174
+ for ( i = 0, len = clusterer.markers.length ; i < len; ++i ){
175
+ marker = clusterer.markers[i];
176
+ if ( marker != null )
177
+ if ( bounds.contains( marker.getPoint() ) )
178
+ visibleMarkers.push( marker );
179
+ else
180
+ nonvisibleMarkers.push( marker );
181
+ }
182
+
183
+ // Take down the non-visible markers.
184
+ for ( i = 0, len = nonvisibleMarkers.length ; i < len; ++i ){
185
+ marker = nonvisibleMarkers[i];
186
+ if ( marker.onMap ){
187
+ clusterer.map.removeOverlay( marker );
188
+ marker.onMap = false;
189
+ }
190
+ }
191
+
192
+ // Take down the non-visible clusters.
193
+ for ( i = 0, len = clusterer.clusters.length ; i < len ; ++i ){
194
+ cluster = clusterer.clusters[i];
195
+ if ( cluster != null && ! bounds.contains( cluster.marker.getPoint() ) && cluster.onMap ){
196
+ clusterer.map.removeOverlay( cluster.marker );
197
+ cluster.onMap = false;
198
+ }
199
+ }
200
+
201
+ // Clustering! This is some complicated stuff. We have three goals
202
+ // here. One, limit the number of markers & clusters displayed, so the
203
+ // maps code doesn't slow to a crawl. Two, when possible keep existing
204
+ // clusters instead of replacing them with new ones, so that the app pans
205
+ // better. And three, of course, be CPU and memory efficient.
206
+ if ( visibleMarkers.length > clusterer.maxVisibleMarkers ){
207
+ // Add to the list of clusters by splitting up the current bounds
208
+ // into a grid.
209
+ var latRange = bounds.getNorthEast().lat() - bounds.getSouthWest().lat();
210
+ var latInc = latRange / clusterer.gridSize;
211
+ var lngInc = latInc / Math.cos( ( bounds.getNorthEast().lat() + bounds.getSouthWest().lat() ) / 2.0 * Math.PI / 180.0 );
212
+ for ( var lat = bounds.getSouthWest().lat(); lat <= bounds.getNorthEast().lat(); lat += latInc )
213
+ for ( var lng = bounds.getSouthWest().lng(); lng <= bounds.getNorthEast().lng(); lng += lngInc ){
214
+ cluster = new Object();
215
+ cluster.clusterer = clusterer;
216
+ cluster.bounds = new GLatLngBounds( new GLatLng( lat, lng ), new GLatLng( lat + latInc, lng + lngInc ) );
217
+ cluster.markers = [];
218
+ cluster.markerCount = 0;
219
+ cluster.onMap = false;
220
+ cluster.marker = null;
221
+ clusterer.clusters.push( cluster );
222
+ }
223
+
224
+ // Put all the unclustered visible markers into a cluster - the first
225
+ // one it fits in, which favors pre-existing clusters.
226
+ for ( i = 0, len = visibleMarkers.length ; i < len; ++i ){
227
+ marker = visibleMarkers[i];
228
+ if ( marker != null && ! marker.inCluster ){
229
+ for ( j = 0, len2 = clusterer.clusters.length ; j < len2 ; ++j ){
230
+ cluster = clusterer.clusters[j];
231
+ if ( cluster != null && cluster.bounds.contains( marker.getPoint() ) ){
232
+ cluster.markers.push( marker );
233
+ ++cluster.markerCount;
234
+ marker.inCluster = true;
235
+ }
236
+ }
237
+ }
238
+ }
239
+
240
+ // Get rid of any clusters containing only a few markers.
241
+ for ( i = 0, len = clusterer.clusters.length ; i < len ; ++i )
242
+ if ( clusterer.clusters[i] != null && clusterer.clusters[i].markerCount < clusterer.minMarkersPerCluster ){
243
+ clusterer.clearCluster( clusterer.clusters[i] );
244
+ clusterer.clusters[i] = null;
245
+ }
246
+
247
+ // Shrink the clusters list.
248
+ for ( i = clusterer.clusters.length - 1; i >= 0; --i )
249
+ if ( clusterer.clusters[i] != null )
250
+ break;
251
+ else
252
+ --clusterer.clusters.length;
253
+
254
+ // Ok, we have our clusters. Go through the markers in each
255
+ // cluster and remove them from the map if they are currently up.
256
+ for ( i = 0, len = clusterer.clusters.length ; i < len; ++i ){
257
+ cluster = clusterer.clusters[i];
258
+ if ( cluster != null ){
259
+ for ( j = 0 , len2 = cluster.markers.length ; j < len2; ++j ){
260
+ marker = cluster.markers[j];
261
+ if ( marker != null && marker.onMap ){
262
+ clusterer.map.removeOverlay( marker );
263
+ marker.onMap = false;
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ // Now make cluster-markers for any clusters that need one.
270
+ for ( i = 0, len = clusterer.clusters.length; i < len; ++i ){
271
+ cluster = clusterer.clusters[i];
272
+ if ( cluster != null && cluster.marker == null ){
273
+ // Figure out the average coordinates of the markers in this
274
+ // cluster.
275
+ var xTotal = 0.0, yTotal = 0.0;
276
+ for ( j = 0, len2 = cluster.markers.length; j < len2 ; ++j ){
277
+ marker = cluster.markers[j];
278
+ if ( marker != null ){
279
+ xTotal += ( + marker.getPoint().lng() );
280
+ yTotal += ( + marker.getPoint().lat() );
281
+ }
282
+ }
283
+ var location = new GLatLng( yTotal / cluster.markerCount, xTotal / cluster.markerCount );
284
+ marker = new GMarker( location, { icon: clusterer.icon } );
285
+ cluster.marker = marker;
286
+ GEvent.addListener( marker, 'click', Clusterer.makeCaller( Clusterer.popUp, cluster ) );
287
+ }
288
+ }
289
+ }
290
+
291
+ // Display the visible markers not already up and not in clusters.
292
+ for ( i = 0, len = visibleMarkers.length; i < len; ++i ){
293
+ marker = visibleMarkers[i];
294
+ if ( marker != null && ! marker.onMap && ! marker.inCluster )
295
+ {
296
+ clusterer.map.addOverlay( marker );
297
+ marker.addedToMap();
298
+ marker.onMap = true;
299
+ }
300
+ }
301
+
302
+ // Display the visible clusters not already up.
303
+ for ( i = 0, len = clusterer.clusters.length ; i < len; ++i ){
304
+ cluster = clusterer.clusters[i];
305
+ if ( cluster != null && ! cluster.onMap && bounds.contains( cluster.marker.getPoint() )){
306
+ clusterer.map.addOverlay( cluster.marker );
307
+ cluster.onMap = true;
308
+ }
309
+ }
310
+
311
+ // In case a cluster is currently popped-up, re-pop to get any new
312
+ // markers into the infobox.
313
+ Clusterer.rePop( clusterer );
314
+ };
315
+
316
+
317
+ Clusterer.popUp = function ( cluster ){
318
+ var clusterer = cluster.clusterer;
319
+ var html = '<table width="300">';
320
+ var n = 0;
321
+ for ( var i = 0 , len = cluster.markers.length; i < len; ++i )
322
+ {
323
+ var marker = cluster.markers[i];
324
+ if ( marker != null )
325
+ {
326
+ ++n;
327
+ html += '<tr><td>';
328
+ if ( marker.getIcon().smallImage != null )
329
+ html += '<img src="' + marker.getIcon().smallImage + '">';
330
+ else
331
+ html += '<img src="' + marker.getIcon().image + '" width="' + ( marker.getIcon().iconSize.width / 2 ) + '" height="' + ( marker.getIcon().iconSize.height / 2 ) + '">';
332
+ html += '</td><td>' + marker.description + '</td></tr>';
333
+ if ( n == clusterer.maxLinesPerInfoBox - 1 && cluster.markerCount > clusterer.maxLinesPerInfoBox )
334
+ {
335
+ html += '<tr><td colspan="2">...and ' + ( cluster.markerCount - n ) + ' more</td></tr>';
336
+ break;
337
+ }
338
+ }
339
+ }
340
+ html += '</table>';
341
+ clusterer.map.closeInfoWindow();
342
+ cluster.marker.openInfoWindowHtml( html );
343
+ clusterer.poppedUpCluster = cluster;
344
+ };
345
+
346
+ Clusterer.rePop = function ( clusterer ){
347
+ if ( clusterer.poppedUpCluster != null )
348
+ Clusterer.popUp( poppedUpCluster );
349
+ };
350
+
351
+ Clusterer.popDown = function ( clusterer ){
352
+ clusterer.poppedUpCluster = null;
353
+ };
354
+
355
+ Clusterer.prototype.clearCluster = function ( cluster ){
356
+ var i, marker;
357
+
358
+ for ( i = 0; i < cluster.markers.length; ++i ){
359
+ if ( cluster.markers[i] != null ){
360
+ cluster.markers[i].inCluster = false;
361
+ cluster.markers[i] = null;
362
+ }
363
+ }
364
+
365
+ cluster.markers.length = 0;
366
+ cluster.markerCount = 0;
367
+
368
+ if ( cluster == this.poppedUpCluster )
369
+ this.map.closeInfoWindow();
370
+
371
+ if ( cluster.onMap )
372
+ {
373
+ this.map.removeOverlay( cluster.marker );
374
+ cluster.onMap = false;
375
+ }
376
+ };
377
+
378
+ // This returns a function closure that calls the given routine with the
379
+ // specified arg.
380
+ Clusterer.makeCaller = function ( func, arg ){
381
+ return function () { func( arg ); };
382
+ };
383
+
384
+
385
+ // Augment GMarker so it handles markers that have been created but
386
+ // not yet addOverlayed.
387
+ GMarker.prototype.setMap = function ( map ){
388
+ this.map = map;
389
+ };
390
+
391
+ GMarker.prototype.getMap = function (){
392
+ return this.map;
393
+ }
394
+
395
+ GMarker.prototype.addedToMap = function (){
396
+ this.map = null;
397
+ };
398
+
399
+
400
+ GMarker.prototype.origOpenInfoWindow = GMarker.prototype.openInfoWindow;
401
+ GMarker.prototype.openInfoWindow = function ( node, opts ){
402
+ if ( this.map != null )
403
+ return this.map.openInfoWindow( this.getPoint(), node, opts );
404
+ else
405
+ return this.origOpenInfoWindow( node, opts );
406
+ };
407
+
408
+ GMarker.prototype.origOpenInfoWindowHtml = GMarker.prototype.openInfoWindowHtml;
409
+ GMarker.prototype.openInfoWindowHtml = function ( html, opts ){
410
+ if ( this.map != null )
411
+ return this.map.openInfoWindowHtml( this.getPoint(), html, opts );
412
+ else
413
+ return this.origOpenInfoWindowHtml( html, opts );
414
+ };
415
+
416
+ GMarker.prototype.origOpenInfoWindowTabs = GMarker.prototype.openInfoWindowTabs;
417
+ GMarker.prototype.openInfoWindowTabs = function ( tabNodes, opts ){
418
+ if ( this.map != null )
419
+ return this.map.openInfoWindowTabs( this.getPoint(), tabNodes, opts );
420
+ else
421
+ return this.origOpenInfoWindowTabs( tabNodes, opts );
422
+ };
423
+
424
+ GMarker.prototype.origOpenInfoWindowTabsHtml = GMarker.prototype.openInfoWindowTabsHtml;
425
+ GMarker.prototype.openInfoWindowTabsHtml = function ( tabHtmls, opts ){
426
+ if ( this.map != null )
427
+ return this.map.openInfoWindowTabsHtml( this.getPoint(), tabHtmls, opts );
428
+ else
429
+ return this.origOpenInfoWindowTabsHtml( tabHtmls, opts );
430
+ };
431
+
432
+ GMarker.prototype.origShowMapBlowup = GMarker.prototype.showMapBlowup;
433
+ GMarker.prototype.showMapBlowup = function ( opts ){
434
+ if ( this.map != null )
435
+ return this.map.showMapBlowup( this.getPoint(), opts );
436
+ else
437
+ return this.origShowMapBlowup( opts );
438
+ };
439
+
440
+
441
+ function addDescriptionToMarker(marker, description){
442
+ marker.description = description;
443
+ return marker;
444
+ }
@@ -43,16 +43,17 @@ module Ym4r
43
43
  class GTileLayer
44
44
  include MappingObject
45
45
 
46
- attr_accessor :opacity, :zoom_inter, :copyright
46
+ attr_accessor :opacity, :zoom_inter, :copyright, :format
47
47
 
48
- def initialize(zoom_inter = 0..17, copyright= {'prefix' => '', 'copyright_texts' => [""]}, opacity = 1.0)
48
+ def initialize(zoom_inter = 0..17, copyright= {'prefix' => '', 'copyright_texts' => [""]}, opacity = 1.0, format = "png")
49
49
  @opacity = opacity
50
50
  @zoom_inter = zoom_inter
51
51
  @copyright = copyright
52
+ @format = format.to_s
52
53
  end
53
54
 
54
55
  def create
55
- "addPropertiesToLayer(new GTileLayer(new GCopyrightCollection(\"\"),#{zoom_inter.begin},#{zoom_inter.end}),#{get_tile_url},function(a,b) {return #{MappingObject.javascriptify_variable(@copyright)};}\n,function() {return #{@opacity};})"
56
+ "addPropertiesToLayer(new GTileLayer(new GCopyrightCollection(\"\"),#{zoom_inter.begin},#{zoom_inter.end}),#{get_tile_url},function(a,b) {return #{MappingObject.javascriptify_variable(@copyright)};}\n,function() {return #{@opacity};},function(){return #{@format == "png"};})"
56
57
  end
57
58
 
58
59
  #for subclasses to implement
@@ -60,32 +61,38 @@ module Ym4r
60
61
  end
61
62
  end
62
63
 
64
+ #Represents a pre tiled layer, taking images directly from a server, without using a server script.
63
65
  class PreTiledLayer < GTileLayer
64
- attr_accessor :base_url, :format
66
+ attr_accessor :base_url
65
67
 
66
- def initialize(base_url = "http://localhost:8080/tiles", copyright = {'prefix' => '', 'copyright_texts' => [""]}, zoom_inter = 0..17, opacity = 1.0,format = "png")
67
- super(zoom_inter, copyright, opacity)
68
+ def initialize(base_url = "/public/tiles", copyright = {'prefix' => '', 'copyright_texts' => [""]}, zoom_inter = 0..17, opacity = 1.0,format = "png")
69
+ super(zoom_inter, copyright, opacity,format)
68
70
  @base_url = base_url
69
- @format = format
70
71
  end
71
72
 
72
73
  #returns the code to determine the url to fetch the tile. Follows the convention adopted by the tiler: {base_url}/tile_{b}_{a.x}_{a.y}.{format}
73
74
  def get_tile_url
74
- "function(a,b,c) { return '#{@base_url}/tile_' + b + '_' + a.x + '_' + a.y + '.#{@format}';}"
75
+ "function(a,b,c) { return '#{@base_url}/tile_' + b + '_' + a.x + '_' + a.y + '.#{format}';}"
75
76
  end
76
77
  end
78
+
79
+ #Represents a pretiled layer (it actually does not really matter where the tiles come from). Calls an action on the server to get back the tiles. It can be used, for example, to return default tiles when the requested tile is not present.
80
+ class PreTiledLayerFromAction < PreTiledLayer
81
+ def get_tile_url
82
+ "function(a,b,c) { return '#{base_url}?x=' + a.x + '&y=' + a.y + '&z=' + b;}"
83
+ end
84
+ end
77
85
 
78
86
  #needs to include the JavaScript file wms-gs.js for this to work
79
87
  #see http://docs.codehaus.org/display/GEOSDOC/Google+Maps
80
88
  class WMSLayer < GTileLayer
81
- attr_accessor :base_url, :layers, :styles, :format, :merc_proj, :use_geographic
89
+ attr_accessor :base_url, :layers, :styles, :merc_proj, :use_geographic
82
90
 
83
91
  def initialize(base_url, layers, styles = "", copyright = {'prefix' => '', 'copyright_texts' => [""]}, use_geographic = false, merc_proj = :mapserver, zoom_inter = 0..17, opacity = 1.0,format= "png")
84
- super(zoom_inter, copyright, opacity)
92
+ super(zoom_inter, copyright, opacity,format)
85
93
  @base_url = base_url
86
94
  @layers = layers
87
95
  @styles = styles
88
- @format = format
89
96
  @merc_proj = if merc_proj == :mapserver
90
97
  "54004"
91
98
  elsif merc_proj == :geoserver
@@ -101,7 +108,7 @@ module Ym4r
101
108
  end
102
109
 
103
110
  def create
104
- "addWMSPropertiesToLayer(#{super},#{MappingObject.javascriptify_variable(@base_url)},#{MappingObject.javascriptify_variable(@layers)},#{MappingObject.javascriptify_variable(@styles)},#{MappingObject.javascriptify_variable(@format)},#{MappingObject.javascriptify_variable(@merc_proj)},#{MappingObject.javascriptify_variable(@use_geographic)})"
111
+ "addWMSPropertiesToLayer(#{super},#{MappingObject.javascriptify_variable(@base_url)},#{MappingObject.javascriptify_variable(@layers)},#{MappingObject.javascriptify_variable(@styles)},#{MappingObject.javascriptify_variable(format)},#{MappingObject.javascriptify_variable(@merc_proj)},#{MappingObject.javascriptify_variable(@use_geographic)})"
105
112
  end
106
113
  end
107
114
  end
@@ -117,7 +117,8 @@ module Ym4r
117
117
  #put the functions in a separate javascript file to be included in the page
118
118
  html << "function addInfoWindowToMarker(marker,info){\nGEvent.addListener(marker, \"click\", function() {\nmarker.openInfoWindowHtml(info);\n});\nreturn marker;\n}\n"
119
119
  html << "function addInfoWindowTabsToMarker(marker,info){\nGEvent.addListener(marker, \"click\", function() {\nmarker.openInfoWindowTabsHtml(info);\n});\nreturn marker;\n}\n"
120
- html << "function addPropertiesToLayer(layer,getTile,copyright,opacity){\nlayer.getTileUrl = getTile;\nlayer.getCopyright = copyright;\nlayer.getOpacity = opacity;\nreturn layer;\n}\n"
120
+ html << "function addPropertiesToLayer(layer,getTile,copyright,opacity,isPng){\nlayer.getTileUrl = getTile;\nlayer.getCopyright = copyright;\nlayer.getOpacity = opacity;\nlayer.isPng = isPng;\nreturn layer;\n}\n"
121
+ html << "function addOptionsToIcon(icon,options){\nfor(var k in options){\nicon[k] = options[k];\n}\nreturn icon;\n}\n"
121
122
  html << @global_init * "\n"
122
123
  html << "var #{@variable};\n" if !no_declare and !no_global
123
124
  html << "window.onload = function() {\nif (GBrowserIsCompatible()) {\n" if !no_load
@@ -56,18 +56,15 @@ module Ym4r
56
56
  #Creates a GIcon.
57
57
  def create
58
58
  if @copy_base
59
- "new GIcon(#{MappingObject.javascriptify_variable(@copy_base)})"
59
+ c = "new GIcon(#{MappingObject.javascriptify_variable(@copy_base)})"
60
60
  else
61
- "new GIcon()"
61
+ c = "new GIcon()"
62
62
  end
63
- end
64
- #Declares a GIcon. It is necessary to declare an icon before using it, since it is the only way to set up its attributes.
65
- def declare(variable)
66
- decl = super(variable) + "\n"
67
- @options.each do |key,value|
68
- decl << "#{to_javascript}.#{MappingObject.javascriptify_method(key.to_s)} = #{MappingObject.javascriptify_variable(value)};\n"
63
+ if !options.empty?
64
+ "addOptionsToIcon(#{c},#{MappingObject.javascriptify_variable(@options)})"
65
+ else
66
+ c
69
67
  end
70
- decl
71
68
  end
72
69
  end
73
70
 
@@ -96,6 +93,7 @@ module Ym4r
96
93
  end
97
94
  end
98
95
 
96
+ #A GOverlay representing a group of GMarkers. The GMarkers can be identified with an id, which can be used to show the info window of a specific marker, in reponse, for example, to a click on a link. The whole group can be shown on and off at once. It should be declared global at initialization time to be useful.
99
97
  class GMarkerGroup
100
98
  include MappingObject
101
99
  attr_accessor :active, :markers, :markers_by_id
@@ -115,6 +113,31 @@ module Ym4r
115
113
  "new GMarkerGroup(#{MappingObject.javascriptify_variable(@active)},#{MappingObject.javascriptify_variable(@markers)},#{MappingObject.javascriptify_variable(@markers_by_id)})"
116
114
  end
117
115
  end
116
+
117
+ #Makes the link with the Clusterer2 library by Jef Poskanzer (slightly modified though). Is a GOverlay making clusters out of its GMarkers, so that GMarkers very close to each other appear as one when the zoom is low. When the zoom gets higher, the individual markers are drawn.
118
+ class Clusterer
119
+ include MappingObject
120
+ attr_accessor :markers,:options
121
+
122
+ def initialize(markers = [], options = {})
123
+ @markers = markers
124
+ @options = options
125
+ end
126
+
127
+ def create
128
+ js_marker = '[' + @markers.collect do |marker|
129
+ add_description(marker)
130
+ end.join(",") + ']'
131
+
132
+ "new Clusterer(#{js_marker},#{MappingObject.javascriptify_variable(@options[:icon] || GIcon::DEFAULT)},#{@options[:max_visible_markers] || 150},#{@options[:grid_size] || 5},#{@options[:min_markers_per_cluster] || 5},#{@options[:max_lines_per_info_box] || 10})"
133
+ end
134
+
135
+ private
136
+ def add_description(marker)
137
+ "addDescriptionToMarker(#{MappingObject.javascriptify_variable(marker)},#{MappingObject.javascriptify_variable(marker.options[:description] || marker.options[:title] || '')})"
138
+ end
139
+
140
+ end
118
141
 
119
142
  #A basic Latitude/longitude point.
120
143
  class GLatLng
@@ -24,7 +24,7 @@ spec = Gem::Specification::new do |s|
24
24
  s.platform = Gem::Platform::RUBY
25
25
 
26
26
  s.name = 'ym4r'
27
- s.version = "0.3.1"
27
+ s.version = "0.3.2"
28
28
  s.summary = "Using Google Maps and Yahoo! Maps from Ruby and Rails"
29
29
  s.description = <<EOF
30
30
  EOF
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: ym4r
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.1
7
- date: 2006-06-18 00:00:00 +05:00
6
+ version: 0.3.2
7
+ date: 2006-06-23 00:00:00 +05:00
8
8
  summary: Using Google Maps and Yahoo! Maps from Ruby and Rails
9
9
  require_paths:
10
10
  - lib
@@ -59,6 +59,7 @@ files:
59
59
  - lib/ym4r/yahoo_maps/flash/tool.rb
60
60
  - lib/ym4r/yahoo_maps/flash/widget.rb
61
61
  - lib/ym4r/google_maps/config/config.yml
62
+ - lib/ym4r/google_maps/javascript/clusterer.js
62
63
  - lib/ym4r/google_maps/javascript/markerGroup.js
63
64
  - lib/ym4r/google_maps/javascript/wms-gs.js
64
65
  - tools/tile_image.rb