ym4r 0.1.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,20 +1,20 @@
1
1
  =YM4R
2
- This is YM4R 0.1.3. The goal of YM4R (which naturally means Yellow Maps For Ruby...) is to ease the use of the Google Maps 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.2.1. The goal of YM4R (which naturally means Yellow Maps For Ruby...) is to ease the use of the Google Maps and Yahoo! Maps API's (including the Geocoding, Map Image, Traffic and Local Search API's) from Ruby and Rails.
3
3
 
4
- ===Operations
5
- ====Google Maps
4
+ ==Operations
5
+ ===Google Maps
6
6
  You can use the library to display Google maps easily with any ruby-based web framework. The version of the API used is v2. The library is engineered so updates to the map through Ajax are possible. I have made available 2 in-depth tutorials to show some of the functionalities of the library and how it can be integrated with GeoRuby and the Spatial Adapter for Rails:
7
7
  - http://thepochisuperstarmegashow.com/2006/06/02/ym4r-georuby-spatial-adapter-demo/
8
8
  - http://thepochisuperstarmegashow.com/2006/06/03/google-maps-yahoo-traffic-mash-up/
9
9
  Following is some notes about using the library:
10
10
 
11
- =====Naming conventions
11
+ ====Naming conventions
12
12
  The names of the Ruby class follow the ones in the JavaScript Google Maps API v2, except for GMap2, which in Ruby code is called simply GMap. To know what is possible to do with each class, you should refer to the documentation available on Google website.
13
13
 
14
14
  On top of that, you have some convenience methods for initializing the map (in the GMap class). Also, the constructors of some classes accept different types of arguments to be converted later in the correct JavaScript format. For example, the +GMarker+ aclass accepts an array of 2 floats as parameter, in addition of a GLatLng object, to indicate its position. It also facilitates the attribution of an HTML info window, displayed when the user clicks on it, since you can pass to the constructor an options hash with the <tt>:info_window</tt> key and the text to display as the value, instead of having to wire the response to a click event yourself.
15
15
 
16
- =====Binding JavaScript and Ruby
17
- Since the Google Maps API uses JavaScript to create and manipulate a map, most of what the library does is outputting JavaScript, although some convenience methods are provided to simplify some common operations at initialization time. When you create a YM4R mapping object (a Ruby object which includes the MappingObject module) and call methods on it, these calls are converted by the library into JavaScript code. At initialization time, you can pass arbitrary JavaScript code to the <tt>GMap#record_init</tt> and <tt>GMap#record_global_init</tt>.Then, at update time, if you use Ruby-on-Rails as your web framework, you can update your map through RJS by passing the result of the method calls to the <tt>page.send :record,...</tt> method to have it then interpreted by the browser.
16
+ ====Binding JavaScript and Ruby
17
+ Since the Google Maps API uses JavaScript to create and manipulate a map, most of what the library does is outputting JavaScript, although some convenience methods are provided to simplify some common operations at initialization time. When you create a YM4R mapping object (a Ruby object which includes the MappingObject module) and call methods on it, these calls are converted by the library into JavaScript code. At initialization time, you can pass arbitrary JavaScript code to the <tt>GMap#record_init</tt> and <tt>GMap#record_global_init</tt>.Then, at update time, if you use Ruby-on-Rails as your web framework, you can update your map through RJS by passing the result of the method calls to the <tt>page << </tt> method to have it then interpreted by the browser.
18
18
 
19
19
  For example, here is a typical initialization sequence for a map
20
20
  @map = GMap.new("map_div")
@@ -29,32 +29,91 @@ Starting with version 0.1.4 of the YM4R library, this call to +record_init+ to a
29
29
  which is strictly equivalent to
30
30
  @map.record_init @map.add_overlay(new GMarker([35.12878, -110.578],:title => "Hello!"))
31
31
 
32
- =====Initialization of the map
33
- The map is represented by a GMap object. You need to pass to the constructor the id of a DIV that will contain the map. You have to place this DIV yourself in your HTML template. You can also optionnally pass to the constructor the JavaScript name of the variable that will reference the map, which by default will be global in JavaScript. You have convenience methods to setup the controls, the center, the zoom, overlays and the icons (which are also global). You can also pass arbitrary JavaScript to +record_init+ and +record_global_init+. Since, by default, the initialization of the map is performed in a callback function, if you want to have a globally accessible variable, you need to use the +global+ version.
32
+ ====Initialization of the map
33
+ The map is represented by a GMap object. You need to pass to the constructor the id of a DIV that will contain the map. You have to place this DIV yourself in your HTML template. You can also optionnally pass to the constructor the JavaScript name of the variable that will reference the map, which by default will be global in JavaScript. You have convenience methods to setup the controls, the center, the zoom, overlays, map types and the icons (which are also global). You can also pass arbitrary JavaScript to +record_init+ and +record_global_init+. Since, by default, the initialization of the map is performed in a callback function, if you want to have a globally accessible variable, you need to use the +global+ version.
34
34
 
35
35
  Then in your template, you have 2 necessary calls:
36
36
  - <tt>GMap#header</tt>: Outputs the inclusion of the JavaScript file from Google to make use of the Google Maps API + a style declaration for VML objects, necessary to display polylines under IE.
37
- - <tt>GMap#to_html</tt>: Outputs the initialization code of the map. By default, it outputs the +script+ tags and initializes the map inside a +load+ function. These settings can however be overridden.
37
+ - <tt>GMap#to_html</tt>: Outputs the initialization code of the map. By default, it outputs the +script+ tags and initializes the map in response to the onload event of the JavaScript window object.
38
38
 
39
- You need to have an <tt>onload="load()"</tt> on the body of the HTML document, as well as place a DIV with the chosen ID to have the map correctly displayed.
39
+ Starting with version 0.2.1, there is no need to manually wire in the body element the response to the +load+ and +unload+ events on the JavaScript window object.
40
40
 
41
- =====Update of the map
41
+ ====Update of the map
42
42
  You are able to update the map through Ajax. For example, in Ruby-on-Rails, you have something called RJS, which sends JavaScript created on the server as a response to an action, which is later interpreted by the browser. It is usually used for visual effects and replacing or adding elements. It can also accept arbitrary JavaScript and this is what YM4R uses.
43
43
 
44
44
  For example, if you want to add a marker to the map, you need to do a few things. First, you have to bind a Ruby mapping object to the global JavaScript map variable. By default its name is +map+, but you could have overriden that at initialization time. You need to do something like this:
45
45
  @map = Variable.new("map")
46
46
  +map+ in the Variable constructor is the name of the global JavaScript map variable. Then any method you call on <tt>@map</tt> will be converted in JavaScript to a method called on +map+. In your RJS code, you would do something like this to add a marker:
47
- page.send :record, @map.add_overlay(GMarker.new([123123.1,12313.76],:title => "Hello again!"))
47
+ page << @map.add_overlay(GMarker.new([123123.1,12313.76],:title => "Hello again!"))
48
48
  What is sent to the browser will be the fllowing JavaScript code:
49
49
  map.addOverlay(new GMarker(new GLatLng(123123.1,12313.76),{title:\"Hello again!\"}))
50
50
 
51
- ====Yahoo Maps Building Block API
51
+ ====Adding new map types
52
+ 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).
53
+
54
+ For exemple, here is how you would setup layers from a public WMS server of the DMAP team of the American Navy:
55
+ layer = WMSLayer.new("http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms",
56
+ "20:3,6:3,0:27,0:29,6:19","",
57
+ {'prefix' => "Map Copyright 2006", 'copyright_texts'=> ["DMAP"]},
58
+ true,:mapserver,0..17,0.8,"png")
59
+ This sets up a connection to a WMS service, requesting layers <tt>20:3,6:3,0:27,0:29,6:19</tt> (you would have to look at the GetCapabilities document of the service to know what the valid layers are), with default styles and a copyright notice attributing the data to DMAP. The layers are valid for zoom levels 0 to 17 (all the zoom levels). The images will be 80% opaque and will come as PNG.
60
+
61
+ The arguments +true+ and <tt>:mapserver</tt> warrant some explanation. The Google Maps are in the Simple Mercator projection in the WGS84 datum and currently do not support the display of data in projections other than that, although it may change in the future. Unfortunately, different WMS servers do not identify this projection the same way. So you can give to the WMSLayer constructor your server type and it will figure out what is the correct identifier. Currently, this works only for <tt>:mapserver</tt> (EPSG:54004) and <tt>:geoserver</tt> (EPSG:41001). For others you can directly pass a number corresponding to the EPSG definition of the simple Mercator projection of your server. On top of that, some servers just don't support the Simple Mercator projection. This is why there is a +true+ argument before <tt>:mapserver</tt> in the example. It is in order to tell the WMSLayer that it should request its tiles using LatLon coordinates in the WGS84 datum (which should be supported by any server). Unfortunately it is not perfect since the deformation is quite important for low zoom levels (< 5 for the US). Higher than that, the deformation is not that visible. However, if you control the WMS server, it is recommended that you have a +false+ in this argument and setup the Mercator projection in your server if it is not done by default.
62
+
63
+ <b>Note that you need to include the wms-gs.js javascript file (in the <tt>lib/ym4r/google_maps/javascript</tt> folder) in your HTML page in order to make use of the WMSLayer functionality. It uses code by John Deck with the participation of others (read the top of the javascript file to know more).</b>
64
+
65
+ Here is how to define a pretiled layer:
66
+ layer = PreTiledLayer.new("http://localhost:3000/tiles",
67
+ {'prefix' => "Map C 2006", 'copyright_texts' => ["Open Atlas"]},
68
+ 13..13,0.7,"gif")
69
+ 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).
70
+
71
+ You can add such a layer to a new map type the following way:
72
+ map_type = GMapType.new(layer,"My WMS")
73
+ 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:
74
+ map_type = GMapType.new([GMapType::G_SATELLITE_MAP.get_tile_layers[0],layer,GMapType::G_HYBRID_MAP.get_tile_layers[1]],
75
+ "Test WMS")
76
+ Here for the "Test WMS" map type, we also take the first layer of the "Satellite" map type in the background and overlay the second layer of the "Hybrid" map type (roads, country boundaries, etc... transparently overlaid on top of the preceding layers) so when the "Test WMS" map type is selected in the interface, all three layers will be displayed.
77
+
78
+ Finally to add a map type to a GMap:
79
+ @map.map_type_init(map_type)
80
+ If you want to wipe out the existing map types (for example the 3 default ones), you can add a +false+ argument to the +map_type_init+ method and the +map_type+ will be the only one.
81
+
82
+
83
+ ====How to generated tiles for use in pretiled layers
84
+
85
+ The YM4R library provides 2 tools to generate tiles. They are in the +tools+ directory of the YM4R distribution.
86
+
87
+ =====tile_wms.rb
88
+ This is to generate tiles from an already existing WMS server. It can be useful if you don't want to setup a permanent WMS server but still want to display your geographic data files (any format compatible with your WMS server can then be used). It can also be used to cache data from public servers, which can sometimes be very slow to answer requests. Run "ruby tile_wms.rb" to know what the options are. These are very similar to the ones passed to the WMSTiler constructor, although the <tt>-g</tt> (<tt>--gmap-setting</tt>) needs to be explained in more details. It uses the data that comes from this tool: http://www.onnyturf.com/google/latlontotile.html. Basically you need to center the map at the zoom you need to get your desired extent. When you are satisfied, the tool will give you the following data: the X and Y values of the upper left corner tile, 17 minus the zoom level (this is because the V2 of the Google Maps API reversed the zoom order), the number of horizontal and vertical tiles that you want. You will then pass to the <tt>-g</tt> options 5 integers in the order previously described, for example:
89
+ -g 300,383,7,3,2
90
+ About the zoom level, if the tools tells you 7, you should pass 10 (17 - 7) to the <tt>-g</tt> option.
91
+
92
+ Note that the zoom levels passed to the <tt>-z</tt> argument must be greater or equal to the zoom level passed to the <tt>-g</tt> argument.
93
+
94
+ =====tile_image.rb
95
+ This is to generate tiles from a local image. You will need the RMagick library for it to work. Basically, you should use this tool: http://open.atlas.free.fr/GMapsTransparenciesImgOver.php. You should have an image that will be displayed at the lowest zoom level that you want and use it with this tool. You can translate and scale the image until you are statisfied. When you are, you must click a link to gather information about the transformation needed for your image. You will need to pass this data to the <tt>-p</tt> option of the tool in the following order: X and Y of the top left corner of the map, 17 minus the zoom level, X and Y padding, scale. So for example, you will have the following argument:
96
+ -p 2503,3667,4,241,115,0.8702928870292888
97
+ Again, about the zoom level, if the tool tells you 7, you should pass 10 (17 - 7) to the <tt>-p</tt> option.
98
+
99
+ About the padding, I had a slight problem on Firefox: I needed to substract 10 to the X and Y paddings given by the online tool to have the correct one to pass to the <tt>-p</tt> option. Maybe it has something to do with the 10-pixel bands at the top and left of the page.
100
+
101
+ You should pass as many images to the tool as you have zoom levels in the <tt>-z</tt> argument: Each image will be used for one zoom level. The string order of the names of the image files must match the order of their corresponding zoom level. Moreover, each image should be exactly twice bigger (in both width and height) than the one in the zoom level immediately preceding it. Also note that the zoom levels passed to the <tt>-z</tt> argument must be greater or equal to the zoom level passed to the <tt>-p</tt> argument.
102
+
103
+ By default, the tool will generate transparent borders. You can pass 4 integers (R, G, B and A) from 0 to 255 to the -b option to change that:
104
+ -b 255,123,456,42
105
+
106
+ Here is an example of command line execution using the image at http://open.atlas.free.fr/GMapsTransparenciesImgOver.php. If you place it over the satellite images, you would get the following parameters: 2503,3667,13,149,76,1.0 (I have accounted for both the reversing of the zoom for v2 and the 10-pixel drift of the padding in Firefox). I have also created another version of the image, at double the size in width and height. Normally you should do the opposite: Get the highest resolution possible (if you scan the map) and downsize it for all the desired zoom levels until you get the image to use in this online tool (lowest zoom level). If you use vector graphics, you can easily create versions at different resolutions. I placed the 2 images (called MAP_zoom1.jpg for the smallest and MAP_zoom2.jpg for the biggest) in directory <tt>./input</tt>. And I launch the tool like this:
107
+ ruby tile_image.rb -o ./tiles -z 13..14 -p 2503,3667,13,149,76,1.0 ./input/*
108
+ This will create tiles of the image for zoom levels 13 and 14. It should take just a few seconds.
109
+
110
+ ===Yahoo Maps Building Block API
52
111
  Building Block API's (Geocoding, Map Image, Traffic and Local Search) are supported. You have to pass to the +get+ method of the module a hash whose keys are a rubyfied version of the request parameters detailed in the documentation for these API's. You get back a ruby object, with accessors that let you get the returned data in a easy way. You get an exception if not all the parameters have been passed, if the connection to the service could not be made or if the parameters you have passed are of the incorrect value.
53
112
 
54
113
  To know what parameters to pass to the +get+ methods and what results you should expect, you should consult the documentation for the building block API's on Yahoo!'s website : http://developer.yahoo.com/maps/index.html#mapsBuildingBlocks .
55
114
 
56
115
  Here are some examples and notes about using the different Building Block API's.
57
- =====Geocoding
116
+ ====Geocoding
58
117
  Here is an example of request:
59
118
  require 'ym4r'
60
119
  include Ym4r::BuildingBlock
@@ -64,7 +123,7 @@ Here is an example of request:
64
123
  :zip => "95014")
65
124
  +results+ is an array of Geocoding::Result objects. The API can return up to 50 results for one request. You can access the data in the result with attributes which are rubyfied versions of the XML elements forming the answer from the service: See the Yahoo! Geocoding documentation to know what these are and their meanings.
66
125
 
67
- =====Map Image
126
+ ====Map Image
68
127
  Here is an example of request:
69
128
  require 'ym4r'
70
129
  include Ym4r::BuildingBlock
@@ -75,7 +134,7 @@ Here is an example of request:
75
134
  :image_type => "png")
76
135
  +result+ is a MapImage::Result object contains the URL for the requested image. You can download this image to a file by calling +download_to+ and passing a path.
77
136
 
78
- =====Traffic
137
+ ====Traffic
79
138
  Here is an example of request:
80
139
  require 'ym4r'
81
140
  include Ym4r::BuildingBlock
@@ -86,7 +145,7 @@ Here is an example of request:
86
145
  :include_map => true)
87
146
  +results+ is a Traffic::ResultSet object (subclass of +Array+), containing Traffic::Result objects, each containing information about one traffic incident.
88
147
 
89
- =====Local Search
148
+ ====Local Search
90
149
  Here is an example of request:
91
150
  require 'ym4r'
92
151
  include Ym4r::BuildingBlock
@@ -97,23 +156,24 @@ Here is an example of request:
97
156
  :query => "chinese")
98
157
  +results+ is a LocalSearch::ResultSet object (subclass of +Array+), containing LocalSearch::Result objects, each containing information about one hit on the Yahoo! local search.
99
158
 
100
- ====Yahoo! Maps JS-Flash API
159
+ ===Yahoo! Maps JS-Flash API
101
160
  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.
102
161
 
103
- ===Changes since last version
104
- - Addition of support for the Google Maps API
105
- - Addition of preliminary support for the Yahoo! Maps JS-Flash API
162
+ ==Changes since last version
163
+ - Addition of support for user-defined layers (WMS and pretiled layers)
164
+ - Addition of tools to generate tiles to be used with the pretiled layers
106
165
 
107
- ===TODO
166
+ ==TODO
167
+ - Add support for easy manipulation of external Google Maps-related libraries: Advanced tooltip manipulation, GeoRSS, Clusterer...
108
168
  - Documentation! Documentation! Documentation!
109
169
  - Tutorials
110
170
  - Finish the support for the Yahoo! Maps JS-Flash API
111
171
 
112
- ===Disclaimer
172
+ ==Disclaimer
113
173
  This software is not endorsed in any way by Yahoo! or Google.
114
174
 
115
- ===License
175
+ ==License
116
176
  YM4R is released under the MIT license.
117
177
 
118
- ===Support
178
+ ==Support
119
179
  Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut+ym4r@gmail.com.
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Call generic wms service for GoogleMaps v2
3
+ * John Deck, UC Berkeley
4
+ * Inspiration & Code from:
5
+ * Mike Williams http://www.econym.demon.co.uk/googlemaps2/ V2 Reference & custommap code
6
+ * Brian Flood http://www.spatialdatalogic.com/cs/blogs/brian_flood/archive/2005/07/11/39.aspx V1 WMS code
7
+ * Kyle Mulka http://blog.kylemulka.com/?p=287 V1 WMS code modifications
8
+ * http://search.cpan.org/src/RRWO/GPS-Lowrance-0.31/lib/Geo/Coordinates/MercatorMeters.pm
9
+ *
10
+ * Modified by Chris Holmes, TOPP to work by default with GeoServer.
11
+ *
12
+ * Bundled with YM4R with John Deck's permission.
13
+ * Slightly modified to fit YM4R.
14
+ * See johndeck.blogspot.com for the original version and for examples and instructions of how to use it.
15
+ */
16
+
17
+ var WGS84_SEMI_MAJOR_AXIS = 6378137.0; //equatorial radius
18
+ var WGS84_ECCENTRICITY = 0.0818191913108718138;
19
+ var DEG2RAD=0.0174532922519943;
20
+ var PI=3.14159267;
21
+
22
+ function dd2MercMetersLng(p_lng) {
23
+ return WGS84_SEMI_MAJOR_AXIS * (p_lng*DEG2RAD);
24
+ }
25
+
26
+ function dd2MercMetersLat(p_lat) {
27
+ var lat_rad = p_lat * DEG2RAD;
28
+ return WGS84_SEMI_MAJOR_AXIS * Math.log(Math.tan((lat_rad + PI / 2) / 2) * Math.pow( ((1 - WGS84_ECCENTRICITY * Math.sin(lat_rad)) / (1 + WGS84_ECCENTRICITY * Math.sin(lat_rad))), (WGS84_ECCENTRICITY/2)));
29
+ }
30
+
31
+ function addWMSPropertiesToLayer(tile_layer,base_url,layers,styles,format,merc_proj,use_geo){
32
+ tile_layer.format = format;
33
+ tile_layer.baseURL = base_url;
34
+ tile_layer.styles = styles;
35
+ tile_layer.layers = layers;
36
+ tile_layer.mercatorEpsg = merc_proj;
37
+ tile_layer.useGeographic = use_geo;
38
+ return tile_layer;
39
+ }
40
+
41
+ getTileUrlForWMS=function(a,b,c) {
42
+ var lULP = new GPoint(a.x*256,(a.y+1)*256);
43
+ var lLRP = new GPoint((a.x+1)*256,a.y*256);
44
+ var lUL = G_NORMAL_MAP.getProjection().fromPixelToLatLng(lULP,b,c);
45
+ var lLR = G_NORMAL_MAP.getProjection().fromPixelToLatLng(lLRP,b,c);
46
+
47
+ if (this.useGeographic){
48
+ var lBbox=lUL.x+","+lUL.y+","+lLR.x+","+lLR.y;
49
+ var lSRS="EPSG:4326";
50
+ }else{
51
+ var lBbox=dd2MercMetersLng(lUL.x)+","+dd2MercMetersLat(lUL.y)+","+dd2MercMetersLng(lLR.x)+","+dd2MercMetersLat(lLR.y);
52
+ var lSRS="EPSG:" + this.mercatorEpsg;
53
+ }
54
+ var lURL=this.baseURL;
55
+ lURL+="?REQUEST=GetMap";
56
+ lURL+="&SERVICE=WMS";
57
+ lURL+="&VERSION=1.1.1";
58
+ lURL+="&LAYERS="+this.layers;
59
+ lURL+="&STYLES="+this.styles;
60
+ lURL+="&FORMAT=image/"+this.format;
61
+ lURL+="&BGCOLOR=0xFFFFFF";
62
+ lURL+="&TRANSPARENT=TRUE";
63
+ lURL+="&SRS="+lSRS;
64
+ lURL+="&BBOX="+lBbox;
65
+ lURL+="&WIDTH=256";
66
+ lURL+="&HEIGHT=256";
67
+ lURL+="&reaspect=false";
68
+ return lURL;
69
+ }
@@ -0,0 +1,108 @@
1
+ module Ym4r
2
+ module GoogleMaps
3
+ #Map types of the map
4
+ class GMapType
5
+ include MappingObject
6
+
7
+ G_NORMAL_MAP = Variable.new("G_NORMAL_MAP")
8
+ G_SATELLITE_MAP = Variable.new("G_SATELLITE_MAP")
9
+ G_HYBRID_MAP = Variable.new("G_HYBRID_MAP")
10
+
11
+ attr_accessor :layers, :name, :projection, :options
12
+
13
+ def initialize(layers, name, projection = GMercatorProjection.new,options = {})
14
+ @layers = layers
15
+ @name = name
16
+ @projection = projection
17
+ @options = options
18
+ end
19
+
20
+ def create
21
+ "new GMapType(#{MappingObject.javascriptify_variable(Array(layers))}, #{MappingObject.javascriptify_variable(projection)}, #{MappingObject.javascriptify_variable(name)}, #{MappingObject.javascriptify_variable(options)})"
22
+ end
23
+ end
24
+
25
+ class GMercatorProjection
26
+ include MappingObject
27
+
28
+ attr_accessor :n
29
+
30
+ def initialize(n = nil)
31
+ @n = n
32
+ end
33
+
34
+ def create
35
+ if n.nil?
36
+ return "G_NORMAL_MAP.getProjection()"
37
+ else
38
+ "new GMercatorProjection(#{@n})"
39
+ end
40
+ end
41
+ end
42
+
43
+ class GTileLayer
44
+ include MappingObject
45
+
46
+ attr_accessor :opacity, :zoom_inter, :copyright
47
+
48
+ def initialize(zoom_inter = 0..17, copyright= {'prefix' => '', 'copyright_texts' => [""]}, opacity = 1.0)
49
+ @opacity = opacity
50
+ @zoom_inter = zoom_inter
51
+ @copyright = copyright
52
+ end
53
+
54
+ 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
+ end
57
+
58
+ #for subclasses to implement
59
+ def get_tile_url
60
+ end
61
+ end
62
+
63
+ class PreTiledLayer < GTileLayer
64
+ attr_accessor :base_url, :format
65
+
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
+ @base_url = base_url
69
+ @format = format
70
+ end
71
+
72
+ #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
+ def get_tile_url
74
+ "function(a,b,c) { return '#{@base_url}/tile_' + b + '_' + a.x + '_' + a.y + '.#{@format}';}"
75
+ end
76
+ end
77
+
78
+ #needs to include the JavaScript file wms-gs.js for this to work
79
+ #see http://docs.codehaus.org/display/GEOSDOC/Google+Maps
80
+ class WMSLayer < GTileLayer
81
+ attr_accessor :base_url, :layers, :styles, :format, :merc_proj, :use_geographic
82
+
83
+ 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)
85
+ @base_url = base_url
86
+ @layers = layers
87
+ @styles = styles
88
+ @format = format
89
+ @merc_proj = if merc_proj == :mapserver
90
+ "54004"
91
+ elsif merc_proj == :geoserver
92
+ "41001"
93
+ else
94
+ merc_proj.to_s
95
+ end
96
+ @use_geographic = use_geographic
97
+ end
98
+
99
+ def get_tile_url
100
+ "getTileUrlForWMS"
101
+ end
102
+
103
+ 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)})"
105
+ end
106
+ end
107
+ end
108
+ end
@@ -15,6 +15,7 @@ module Ym4r
15
15
  @container = container
16
16
  @variable = variable
17
17
  @init = []
18
+ @init_end = [] #for stuff that must be initialized at the end (controls)
18
19
  @global_init = []
19
20
  end
20
21
 
@@ -24,6 +25,11 @@ module Ym4r
24
25
  a << "<style type=\"text/css\">\n v\:* { behavior:url(#default#VML);}\n</style>" if with_vml
25
26
  a
26
27
  end
28
+
29
+ #Outputs the <div id=...></div> which has been configured to contain the map
30
+ def div
31
+ "<div id=\"#{@container}\"></div>"
32
+ end
27
33
 
28
34
  #Outputs a style declaration setting the dimensions of the DIV container of the map. This info can also be set manually in a CSS.
29
35
  def header_width_height(width,height)
@@ -37,12 +43,12 @@ module Ym4r
37
43
 
38
44
  #Initializes the controls: you can pass a hash with keys <tt>:small_map</tt>, <tt>:large_map</tt>, <tt>:small_zoom</tt>, <tt>:scale</tt>, <tt>:map_type</tt> and <tt>:overview_map</tt> and a boolean value as the value (usually true, since the control is not displayed by default)
39
45
  def control_init(controls = {})
40
- @init << add_control(GSmallMapControl.new) if controls[:small_map]
41
- @init << add_control(GLargeMapControl.new) if controls[:large_map]
42
- @init << add_control(GSmallZoomControl.new) if controls[:small_zoom]
43
- @init << add_control(GScaleControl.new) if controls[:scale]
44
- @init << add_control(GMapTypeControl.new) if controls[:map_type]
45
- @init << add_control(GOverviewMapControl.new) if controls[:overview_map]
46
+ @init_end << add_control(GSmallMapControl.new) if controls[:small_map]
47
+ @init_end << add_control(GLargeMapControl.new) if controls[:large_map]
48
+ @init_end << add_control(GSmallZoomControl.new) if controls[:small_zoom]
49
+ @init_end << add_control(GScaleControl.new) if controls[:scale]
50
+ @init_end << add_control(GMapTypeControl.new) if controls[:map_type]
51
+ @init_end << add_control(GOverviewMapControl.new) if controls[:overview_map]
46
52
  end
47
53
 
48
54
  #Initializes the initial center and zoom of the map. +center+ can be both a GLatLng object or a 2-float array.
@@ -54,43 +60,64 @@ module Ym4r
54
60
  end
55
61
  end
56
62
 
57
- #Initializes the map by adding an overlay (marker or polyline). It can be called multiple times
63
+ #Initializes the map by adding an overlay (marker or polyline).
58
64
  def overlay_init(overlay)
59
65
  @init << add_overlay(overlay)
60
66
  end
61
67
 
68
+ #Sets up a new map type. If +add+ is false, all the other map types of the map are wiped out. If +set+ is true, the current map type for the map is set to +map_type+. If you want to access the map type in other methods, you should declare the map type first.
69
+ def map_type_init(map_type, add = true)
70
+ unless add
71
+ @init << get_map_types.set_property(:length,0)
72
+ end
73
+ @init << add_map_type(map_type)
74
+ end
75
+
76
+ #Locally declare a MappingObject with variable name "name"
77
+ def declare_init(variable, name)
78
+ @init << variable.declare(name)
79
+ end
80
+
62
81
  #Records arbitrary JavaScript code and outputs it during initialization outside the +load+ function (ie globally).
63
82
  def record_global_init(code)
64
83
  @global_init << code
65
84
  end
66
85
 
67
86
  #Initializes an icon and makes it globally accessible through the JavaScript variable of name +variable+.
68
- def icon_init(icon , variable)
69
- @global_init << icon.declare(variable)
87
+ def icon_init(icon , name)
88
+ declare_global_init(icon,name)
89
+ end
90
+
91
+ #Globally declare a MappingObject with variable name "name"
92
+ def declare_global_init(variable,name)
93
+ @global_init << variable.declare(name)
70
94
  end
71
95
 
72
96
  #Outputs the initialization code for the map. By default, it outputs the script tags, performs the initialization inside a function called +load+ and makes the map globally available.
73
97
  def to_html(options = {})
74
98
  no_load = options[:no_load]
75
- load_method = options[:load_method] || "load"
76
99
  no_script_tag = options[:no_script_tag]
77
100
  no_declare = options[:no_declare]
78
101
  no_global = options[:no_global]
79
102
 
80
103
  html = ""
81
104
  html << "<script type=\"text/javascript\">\n" if !no_script_tag
105
+ #put the functions in a separate javascript file to be included in the page
82
106
  html << "function addInfoWindowToMarker(marker,info){\nGEvent.addListener(marker, \"click\", function() {\nmarker.openInfoWindowHtml(info);\n});\nreturn marker;\n}\n"
83
107
  html << "function addInfoWindowTabsToMarker(marker,info){\nGEvent.addListener(marker, \"click\", function() {\nmarker.openInfoWindowTabsHtml(info);\n});\nreturn marker;\n}\n"
108
+ html << "function addPropertiesToLayer(layer,getTile,copyright,opacity){\nlayer.getTileUrl = getTile;\nlayer.getCopyright = copyright;\nlayer.getOpacity = opacity;\nreturn layer;\n}\n"
84
109
  html << @global_init * "\n"
85
110
  html << "var #{@variable};\n" if !no_declare and !no_global
86
- html << "function #{load_method}() {\nif (GBrowserIsCompatible()) {\n" if !no_load
111
+ html << "window.onload = function() {\nif (GBrowserIsCompatible()) {\n" if !no_load
87
112
  if !no_declare and no_global
88
113
  html << "#{declare(@variable)}\n"
89
114
  else
90
115
  html << "#{assign_to(@variable)}\n"
91
116
  end
92
117
  html << @init * "\n"
118
+ html << @init_end * "\n"
93
119
  html << "\n}\n}\n" if !no_load
120
+ html << "window.onunload = GUnload;\n"
94
121
  html << "</script>" if !no_script_tag
95
122
  html
96
123
  end
@@ -101,12 +128,7 @@ module Ym4r
101
128
  end
102
129
  end
103
130
 
104
- #Map types of the map
105
- module GMapType
106
- G_NORMAL_MAP = Variable.new("GMapType.G_NORMAL_MAP")
107
- G_SATELLITE_MAP = Variable.new("GMapType.G_SATELLITE_MAP")
108
- G_HYBRID_MAP = Variable.new("GMapType.G_HYBRID_MAP")
109
- end
131
+
110
132
  end
111
133
  end
112
134
 
@@ -8,42 +8,75 @@ module Ym4r
8
8
  #Creates javascript code for missing methods
9
9
  def method_missing(name,*args)
10
10
  args.collect! do |arg|
11
- javascriptify_variable(arg)
11
+ MappingObject.javascriptify_variable(arg)
12
12
  end
13
- Variable.new("#{to_javascript}.#{javascriptify_method(name.to_s)}(#{args.join(",")})")
13
+ Variable.new("#{to_javascript}.#{MappingObject.javascriptify_method(name.to_s)}(#{args.join(",")})")
14
+ end
15
+
16
+ #Creates javascript code for array or hash indexing
17
+ def [](index) #index could be an integer or string
18
+ return Variable.new("#{to_javascript}[#{MappingObject.javascriptify_variable(index)}]")
14
19
  end
15
20
 
16
- #Transforms a Ruby object into a JavaScript string
17
- def javascriptify_variable(arg)
21
+ #Transforms a Ruby object into a JavaScript string : MAppingObject, String, Array, Hash and general case (using to_s)
22
+ def self.javascriptify_variable(arg)
18
23
  if arg.is_a?(MappingObject)
19
24
  arg.to_javascript
20
25
  elsif arg.is_a?(String)
21
- "\"#{escape_javascript(arg)}\""
26
+ "\"#{MappingObject.escape_javascript(arg)}\""
27
+ elsif arg.is_a?(Array)
28
+ "[" + arg.collect{ |a| MappingObject.javascriptify_variable(a)}.join(",") + "]"
29
+ elsif arg.is_a?(Hash)
30
+ "{" + arg.to_a.collect do |v|
31
+ "#{MappingObject.javascriptify_method(v[0].to_s)} : #{MappingObject.javascriptify_variable(v[1])}"
32
+ end.join(",") + "}"
22
33
  else
23
34
  arg.to_s
24
35
  end
25
36
  end
26
37
 
27
38
  #Escape string to be used in JavaScript. Lifted from rails.
28
- def escape_javascript(javascript)
39
+ def self.escape_javascript(javascript)
29
40
  javascript.gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" }
30
41
  end
31
42
 
32
43
  #Transform a ruby-type method name (like add_overlay) to a JavaScript-style one (like addOverlay).
33
- def javascriptify_method(method_name)
44
+ def self.javascriptify_method(method_name)
34
45
  method_name.gsub(/_(\w)/){|s| $1.upcase}
35
46
  end
36
47
 
37
48
  #Declares a Mapping Object bound to a JavaScript variable of name +variable+.
38
49
  def declare(variable)
39
50
  @variable = variable
40
- "var #{variable} = #{create};"
51
+ "var #{@variable} = #{create};"
52
+ end
53
+
54
+ #declare with a random variable name
55
+ def declare_random(init,size = 8)
56
+ s = init.clone
57
+ 6.times { s << (i = Kernel.rand(62); i += ((i < 10) ? 48 : ((i < 36) ? 55 : 61 ))).chr }
58
+ declare(s)
59
+ end
60
+
61
+ #Checks if the MappinObject has been declared
62
+ def declared?
63
+ !@variable.nil?
41
64
  end
42
65
 
43
66
  #Binds a Mapping object to a previously declared JavaScript variable of name +variable+.
44
67
  def assign_to(variable)
45
68
  @variable = variable
46
- "#{variable} = #{create};"
69
+ "#{@variable} = #{create};"
70
+ end
71
+
72
+ #Assign the +value+ to the +property+ of the MappingObject
73
+ def set_property(property, value)
74
+ "#{to_javascript}.#{MappingObject.javascriptify_method(property.to_s)} = #{MappingObject.javascriptify_variable(value)}"
75
+ end
76
+
77
+ #Returns the code to get a +property+ from the MappingObject
78
+ def get_property(property)
79
+ Variable.new("#{to_javascript}.#{MappingObject.javascriptify_method(property.to_s)}")
47
80
  end
48
81
 
49
82
  #Returns a Javascript code representing the object
@@ -61,7 +94,7 @@ module Ym4r
61
94
  end
62
95
  end
63
96
 
64
- #Used to bind a ruby variable to an already existing JavaScript one. IT doesn't have to be a variable in the sense "var variable" but it can be any valid JavaScript expression that has a value.
97
+ #Used to bind a ruby variable to an already existing JavaScript one. It doesn't have to be a variable in the sense "var variable" but it can be any valid JavaScript expression that has a value.
65
98
  class Variable
66
99
  include MappingObject
67
100
  def initialize(variable)
@@ -20,19 +20,14 @@ module Ym4r
20
20
  #Creates a marker: If an info_window or info_window_tabs is present, the response to the click action from the user is setup here.
21
21
  def create
22
22
  if @options.empty?
23
- creation = "new GMarker(#{@point.to_javascript})"
23
+ creation = "new GMarker(#{MappingObject.javascriptify_variable(@point)})"
24
24
  else
25
- options = "{"
26
- options << @options.to_a.collect do |v|
27
- "#{v[0].to_s} : #{javascriptify_variable(v[1])}"
28
- end.join(",")
29
- options << "}"
30
- creation = "new GMarker(#{@point.to_javascript},#{options})"
25
+ creation = "new GMarker(#{MappingObject.javascriptify_variable(@point)},#{MappingObject.javascriptify_variable(@options)})"
31
26
  end
32
27
  if @info_window
33
- "addInfoWindowToMarker(#{creation},#{javascriptify_variable(@info_window)})"
28
+ "addInfoWindowToMarker(#{creation},#{MappingObject.javascriptify_variable(@info_window)})"
34
29
  elsif @tab_info_window
35
- "addInfoWindowTabsToMarker(#{creation},[#{@tab_info_window.collect{ |tab| tab.to_javascript}.join(",")}])"
30
+ "addInfoWindowTabsToMarker(#{creation},#{MappingObject.javascriptify_variable(Array(@tab_info_window))})"
36
31
  else
37
32
  creation
38
33
  end
@@ -43,7 +38,7 @@ module Ym4r
43
38
  class GInfoWindowTab < Struct.new(:tab,:content)
44
39
  include MappingObject
45
40
  def create
46
- "new GInfoWindowTab(#{javascriptify_variable(tab)},#{javascriptify_variable(content)})"
41
+ "new GInfoWindowTab(#{MappingObject.javascriptify_variable(tab)},#{MappingObject.javascriptify_variable(content)})"
47
42
  end
48
43
  end
49
44
 
@@ -61,7 +56,7 @@ module Ym4r
61
56
  #Creates a GIcon.
62
57
  def create
63
58
  if @copy_base
64
- "new GIcon(#{@copy_base.to_javascript})"
59
+ "new GIcon(#{MappingObject.javascriptify_variable(@copy_base)})"
65
60
  else
66
61
  "new GIcon()"
67
62
  end
@@ -70,7 +65,7 @@ module Ym4r
70
65
  def declare(variable)
71
66
  decl = super(variable) + "\n"
72
67
  @options.each do |key,value|
73
- decl << "#{to_javascript}.#{javascriptify_method(key.to_s)} = #{javascriptify_variable(value)};\n"
68
+ decl << "#{to_javascript}.#{MappingObject.javascriptify_method(key.to_s)} = #{MappingObject.javascriptify_variable(value)};\n"
74
69
  end
75
70
  decl
76
71
  end
@@ -93,10 +88,10 @@ module Ym4r
93
88
  end
94
89
  #Creates a new polyline.
95
90
  def create
96
- a = "new GPolyline([#{@points.collect{|pt| pt.to_javascript}.join(",")}]"
97
- a << ",#{javascriptify_variable(@color)}" if @color
98
- a << ",#{javascriptify_variable(@weight)}" if @weight
99
- a << ",#{javascriptify_variable(@opacity)}" if @opacity
91
+ a = "new GPolyline([#{@points.collect{|pt| MappingObject.javascriptify_variable(pt)}.join(",")}]"
92
+ a << ",#{MappingObject.javascriptify_variable(@color)}" if @color
93
+ a << ",#{MappingObject.javascriptify_variable(@weight)}" if @weight
94
+ a << ",#{MappingObject.javascriptify_variable(@opacity)}" if @opacity
100
95
  a << ")"
101
96
  end
102
97
 
@@ -0,0 +1,107 @@
1
+ require 'RMagick'
2
+
3
+ module Ym4r
4
+ module GoogleMaps
5
+ module Tiler
6
+ module ImageTiler
7
+ class Point < Struct.new(:x,:y)
8
+ def -(point)
9
+ Point.new(x - point.x , y - point.y)
10
+ end
11
+ def +(point)
12
+ Point.new(x + point.x , y + point.y)
13
+ end
14
+ def *(scale)
15
+ Point.new(scale * x,scale * y)
16
+ end
17
+ def to_s
18
+ "Point #{x} #{y}"
19
+ end
20
+ end
21
+
22
+ class TileParam < Struct.new(:ul_corner,:zoom,:padding,:scale)
23
+ end
24
+
25
+ TILE_SIZE = 256
26
+
27
+ def self.get_tiles(output_dir, input_files, tile_param, zooms, bg_color = Magick::Pixel.new(255,255,255,0), format = "png")
28
+ #order the input files: string order.
29
+ sorted_input_files = input_files.sort
30
+
31
+ #Whatever the zoom level, the tiles must cover the same surface : we get the surface of the furthest zoom.
32
+ furthest_dimension_tiles = get_dimension_tiles(sorted_input_files[0],tile_param)
33
+ puts furthest_dimension_tiles.to_s
34
+
35
+ zooms.each do |zoom|
36
+ next if zoom < tile_param.zoom
37
+ return if (input_file = sorted_input_files.shift).nil?
38
+
39
+ image = Magick::ImageList::new(input_file)
40
+ image.scale!(tile_param.scale)
41
+ image_size = Point.new(image.columns , image.rows)
42
+
43
+ factor = 2 ** (zoom - tile_param.zoom)
44
+
45
+ #index of the upper left corner for the current zoom
46
+ start = tile_param.ul_corner * factor
47
+ dimension_tiles = furthest_dimension_tiles * factor
48
+ dimension_tiles_pixel = dimension_tiles * TILE_SIZE
49
+ padding = tile_param.padding * factor
50
+
51
+ puts "Padding" + padding.to_s
52
+ puts "Size " + image_size.to_s
53
+ puts "Dimension " + dimension_tiles_pixel.to_s
54
+
55
+ #create an image at dimension_tiles_pixel ; copy the current image there (a bit inefficient memory wise even if it simplifies )
56
+ image_with_padding = Magick::Image.new(dimension_tiles_pixel.x, dimension_tiles_pixel.y) do
57
+ self.background_color = bg_color
58
+ end
59
+
60
+ image_with_padding.import_pixels(padding.x,padding.y,image_size.x,image_size.y,"RGBA",image.export_pixels(0,0,image_size.x,image_size.y,"RGBA"))
61
+
62
+ image_with_padding.write(output_dir + "/tile_glob_#{zoom}.png")
63
+
64
+ total_tiles = dimension_tiles.x * dimension_tiles.y
65
+
66
+ counter = Point.new(0,0)
67
+
68
+ cur_tile = Point.new(start.x,start.y)
69
+
70
+ 1.upto(total_tiles) do |tile|
71
+ #progress column by column
72
+ if counter.y == dimension_tiles.y
73
+ counter.x += 1
74
+ counter.y = 0
75
+ cur_tile.x += 1
76
+ cur_tile.y = start.y
77
+ end
78
+
79
+ pt_nw = counter * TILE_SIZE
80
+
81
+ tile_image = Magick::Image.new(TILE_SIZE,TILE_SIZE)
82
+ tile_image.import_pixels(0,0,TILE_SIZE,TILE_SIZE,"RGBA",image_with_padding.export_pixels(pt_nw.x,pt_nw.y,TILE_SIZE,TILE_SIZE,"RGBA"))
83
+ tile_image.write("#{output_dir}/tile_#{zoom}_#{cur_tile.x}_#{cur_tile.y}.#{format}")
84
+
85
+ counter.y += 1
86
+ cur_tile.y += 1
87
+
88
+ end
89
+ end
90
+ end
91
+
92
+
93
+ def self.get_dimension_tiles(file,tile_param)
94
+ #Get the size of the first input_file
95
+ image = Magick::ImageList::new(file)
96
+ image.scale!(tile_param.scale)
97
+ image_size = Point.new(image.columns , image.rows)
98
+ puts "ISize " + image_size.to_s
99
+ ending = tile_param.padding + image_size
100
+ puts "Ending " + ending.to_s
101
+ Point.new((ending.x / TILE_SIZE.to_f).ceil,(ending.y / TILE_SIZE.to_f).ceil)
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,157 @@
1
+ require 'open-uri'
2
+
3
+ module Ym4r
4
+ module GoogleMaps
5
+ module Tiler
6
+ module WmsTiler
7
+ #Contains LatLon coordinates
8
+ class LatLng < Struct.new(:lat,:lng)
9
+ end
10
+
11
+ #Contain projected coordinates (in pixel or meter)
12
+ class Point < Struct.new(:x,:y)
13
+ end
14
+
15
+ #Structure that contains configuration data for the WMS tiler
16
+ class FurthestZoom < Struct.new(:ul_corner, :zoom, :tile_size)
17
+ end
18
+
19
+ #The size of a Google Maps tile. There are square so only one size.
20
+ TILE_SIZE = 256
21
+
22
+ #Defines a Simple Mercator projection for one level of Google Maps zoom.
23
+ class MercatorProjection
24
+ DEG_2_RAD = Math::PI / 180
25
+ WGS84_SEMI_MAJOR_AXIS = 6378137.0
26
+ WGS84_ECCENTRICITY = 0.0818191913108718138
27
+
28
+ attr_reader :zoom, :size, :pixel_per_degree, :pixel_per_radian, :origin
29
+
30
+ def initialize(zoom)
31
+ @zoom = zoom
32
+ @size = TILE_SIZE * (2 ** zoom)
33
+ @pixel_per_degree = @size / 360.0
34
+ @pixel_per_radian = @size / (2 * Math::PI)
35
+ @origin = Point.new(@size / 2 , @size / 2)
36
+ end
37
+
38
+ def borne(number, inf, sup)
39
+ if(number < inf)
40
+ inf
41
+ elsif(number > sup)
42
+ sup
43
+ else
44
+ number
45
+ end
46
+ end
47
+
48
+ #Transforms LatLon coordinate into pixel coordinates in the Google Maps sense
49
+ #See http://www.math.ubc.ca/~israel/m103/mercator/mercator.html for details
50
+ def latlng_to_pixel(latlng)
51
+ answer = Point.new
52
+ answer.x = (@origin.x + latlng.lng * @pixel_per_degree).round
53
+ sin = borne(Math.sin(latlng.lat * DEG_2_RAD),-0.9999,0.9999)
54
+ answer.y = (@origin.y + 0.5 * Math.log((1 + sin) / (1 - sin)) * -@pixel_per_radian).round
55
+ answer
56
+ end
57
+
58
+ #Transforms pixel coordinates in the Google Maps sense to LatLon coordinates
59
+ def pixel_to_latlng(point)
60
+ answer = LatLng.new
61
+ lng = (point.x - @origin.x) / @pixel_per_degree;
62
+ answer.lng = lng - (((lng + 180)/360).round * 360)
63
+ lat = (2 * Math.atan(Math.exp((point.y - @origin.y) / -@pixel_per_radian))- Math::PI / 2) / DEG_2_RAD
64
+ answer.lat = borne(lat,-90,90)
65
+ answer
66
+ end
67
+
68
+ #Projects LatLon coordinates in the WGS84 datum to meter coordinates using the Simple Mercator projection
69
+ def self.latlng_to_meters(latlng)
70
+ answer = Point.new
71
+ answer.x = WGS84_SEMI_MAJOR_AXIS * latlng.lng * DEG_2_RAD
72
+ lat_rad = latlng.lat * DEG_2_RAD
73
+ answer.y = WGS84_SEMI_MAJOR_AXIS * Math.log(Math.tan((lat_rad + Math::PI / 2) / 2) * ( (1 - WGS84_ECCENTRICITY * Math.sin(lat_rad)) / (1 + WGS84_ECCENTRICITY * Math.sin(lat_rad))) ** (WGS84_ECCENTRICITY/2))
74
+ answer
75
+ end
76
+ end
77
+
78
+ #Get tiles from a WMS server
79
+ def self.get_tiles(output_dir, url, furthest_zoom, zooms, layers, geographic = false, epsg = 54004, styles = "", format = "png")
80
+
81
+ unless geographic
82
+ srs_str = epsg
83
+ else
84
+ srs_str = 4326 #Geographic WGS84
85
+ end
86
+
87
+ base_url = url << "?REQUEST=GetMap&SERVICE=WMS&VERSION=1.1&LAYERS=#{layers}&STYLES=#{styles}&BGCOLOR=0xFFFFFF&FORMAT=image/#{format}&TRANSPARENT=TRUE&WIDTH=#{TILE_SIZE}&HEIGHT=#{TILE_SIZE}&SRS=EPSG:#{srs_str}&reaspect=false"
88
+
89
+ zooms.each do |zoom|
90
+ next if zoom < furthest_zoom.zoom
91
+
92
+ proj = MercatorProjection.new(zoom)
93
+
94
+ #from mapki.com
95
+ factor = 2 ** (zoom - furthest_zoom.zoom)
96
+
97
+ #index of the upper left corner
98
+ x_start = furthest_zoom.ul_corner.x * factor
99
+ y_start = furthest_zoom.ul_corner.y * factor
100
+
101
+ x_tiles = furthest_zoom.tile_size.x * factor
102
+ y_tiles = furthest_zoom.tile_size.y * factor
103
+
104
+ total_tiles = x_tiles * y_tiles
105
+
106
+ x_counter = 0
107
+ y_counter = 0
108
+
109
+ x_tile = x_start
110
+ y_tile = y_start
111
+
112
+ 1.upto(total_tiles) do |tile|
113
+ #progress column by column
114
+ if y_counter == y_tiles
115
+ x_counter += 1
116
+ y_counter = 0
117
+ x_tile += 1
118
+ y_tile = y_start
119
+ end
120
+
121
+ pt_sw = Point.new( (x_start + x_counter) * TILE_SIZE, (y_start + (y_counter + 1)) * TILE_SIZE) #y grows southbound
122
+ pt_ne = Point.new((x_start + (x_counter + 1)) * TILE_SIZE, (y_start + y_counter) * TILE_SIZE)
123
+
124
+ ll_sw = proj.pixel_to_latlng(pt_sw)
125
+ ll_ne = proj.pixel_to_latlng(pt_ne)
126
+
127
+ unless geographic
128
+ pt_sw = MercatorProjection.latlng_to_meters(ll_sw)
129
+ pt_ne = MercatorProjection.latlng_to_meters(ll_ne)
130
+ bbox_str = "#{pt_sw.x},#{pt_sw.y},#{pt_ne.x},#{pt_ne.y}"
131
+ else
132
+ bbox_str = "#{ll_sw.lon},#{ll_sw.lat},#{ll_ne.lon},#{ll_ne.lat}"
133
+ end
134
+
135
+ request_url = "#{base_url}&BBOX=#{bbox_str}"
136
+
137
+ begin
138
+ open("#{output_dir}/tile_#{zoom}_#{x_tile}_#{y_tile}.#{format}","wb") do |f|
139
+ f.write open(request_url).read
140
+ end
141
+ rescue Exception => e
142
+ puts e
143
+ raise
144
+ end
145
+
146
+ y_counter += 1
147
+ y_tile += 1
148
+
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+
@@ -4,4 +4,5 @@ require 'ym4r/google_maps/control'
4
4
  require 'ym4r/google_maps/overlay'
5
5
  require 'ym4r/google_maps/point'
6
6
  require 'ym4r/google_maps/api_key'
7
+ require 'ym4r/google_maps/layer.rb'
7
8
  require 'ym4r/google_maps/helper'
data/rakefile.rb CHANGED
@@ -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.1.4"
27
+ s.version = "0.2.1"
28
28
  s.summary = "Using Google Maps and Yahoo! Maps from Ruby and Rails"
29
29
  s.description = <<EOF
30
30
  EOF
@@ -34,7 +34,7 @@ EOF
34
34
 
35
35
  s.requirements << 'none'
36
36
  s.require_path = 'lib'
37
- s.files = FileList["lib/**/*.rb", "lib/**/*.yml","test/**/*.rb", "README","MIT-LICENSE","rakefile.rb"]
37
+ s.files = FileList["lib/**/*.rb", "lib/**/*.yml","lib/**/*.js","tools/**/*.rb","test/**/*.rb", "README","MIT-LICENSE","rakefile.rb"]
38
38
  s.test_files = FileList['test/test*.rb']
39
39
 
40
40
  s.has_rdoc = true
@@ -6,14 +6,49 @@ require 'test/unit'
6
6
  include Ym4r::GoogleMaps
7
7
 
8
8
  class TestGoogleMaps< Test::Unit::TestCase
9
- def test_js_export
10
- map = GMap.new("map_div")
11
- var = Variable.new("hello")
12
- yuo = Variable.new("salam")
13
- poi = Variable.new("poi")
14
- map.record_init map.add_overlay(GMarker.new([123.5,123.56]))
15
- map.record_init map.dummy_method(var.other_dummy_method(yuo.kaka_boudin),poi)
16
- map.control_init(:small_map => true)
17
- puts map.to_html
9
+ def test_javascriptify_method
10
+ assert_equal("addOverlayToHello",MappingObject::javascriptify_method("add_overlay_to_hello"))
18
11
  end
12
+
13
+ def test_javascriptify_variable_mapping_object
14
+ map = GMap.new("div")
15
+ assert_equal(map.to_javascript,MappingObject::javascriptify_variable(map))
16
+ end
17
+
18
+ def test_javascriptify_variable_numeric
19
+ assert_equal("123.4",MappingObject::javascriptify_variable(123.4))
20
+ end
21
+
22
+ def test_javascriptify_variable_array
23
+ map = GMap.new("div")
24
+ assert_equal("[123.4,#{map.to_javascript},[123.4,#{map.to_javascript}]]",MappingObject::javascriptify_variable([123.4,map,[123.4,map]]))
25
+ end
26
+
27
+ def test_javascriptify_variable_hash
28
+ map = GMap.new("div")
29
+ test_str = MappingObject::javascriptify_variable("hello" => map, "chopotopoto" => [123.55,map])
30
+ assert("{hello : #{map.to_javascript},chopotopoto : [123.55,#{map.to_javascript}]}" == test_str || "{chopotopoto : [123.55,#{map.to_javascript}],hello : #{map.to_javascript}}" == test_str)
31
+ end
32
+
33
+ def test_method_call_on_mapping_object
34
+ map = GMap.new("div","map")
35
+ assert_equal("map.addHello(123.4);",map.add_hello(123.4).to_s)
36
+ end
37
+
38
+ def test_nested_calls_on_mapping_object
39
+ gmap = GMap.new("div","map")
40
+ assert_equal("map.addHello(map.hoYoYo(123.4),map);",gmap.add_hello(gmap.ho_yo_yo(123.4),gmap).to_s)
41
+ end
42
+
43
+ def test_declare_variable_latlng
44
+ point = GLatLng.new([123.4,123.6])
45
+ assert_equal("var point = new GLatLng(123.4,123.6);",point.declare("point"))
46
+ assert_equal("point",point.variable)
47
+ end
48
+
49
+ def test_array_indexing
50
+ obj = Variable.new("obj")
51
+ assert_equal("obj[0]",obj[0].variable)
52
+ end
53
+
19
54
  end
@@ -0,0 +1,66 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'ym4r/google_maps/tiler/image_tiler'
3
+ include Ym4r::GoogleMaps::Tiler
4
+
5
+ require 'optparse'
6
+ require 'ostruct'
7
+
8
+ OptionParser.accept(Range, /(\d+)\.\.(\d+)/) do |range,start,finish|
9
+ Range.new(start.to_i,finish.to_i)
10
+ end
11
+
12
+ OptionParser.accept(ImageTiler::TileParam, /(\d+),(\d+),(\d+),(\d+),(\d+),([\d.]+)/) do |setting,l_corner, u_corner, zoom, padding_x, padding_y, scale|
13
+ ImageTiler::TileParam.new(ImageTiler::Point.new(l_corner.to_i,u_corner.to_i),zoom.to_i,ImageTiler::Point.new(padding_x.to_i,padding_y.to_i),scale.to_f)
14
+ end
15
+
16
+ OptionParser.accept(Magick::Pixel,/(\d+),(\d+),(\d+),(\d+)/) do |pixel, r,g,b,a|
17
+ Magick::Pixel.new(r.to_f,g.to_f,b.to_f,a.to_f)
18
+ end
19
+
20
+ options = OpenStruct.new
21
+ #set some defaults
22
+ options.format = "png"
23
+ options.zoom_range = 0..17
24
+ options.bg_color = Magick::Pixel.new(255,255,255,255)
25
+
26
+ opts = OptionParser.new do |opts|
27
+ opts.banner = "Image Tiler for Google Maps\nUsage: tile_image.rb [options]\nExample: tile_image.rb -o ./tiles -z 11..12 -p 602,768,11,78,112,1.91827348 ./input_files/*.jpg"
28
+ opts.separator ""
29
+ opts.on("-o","--output OUTPUT_DIR","Directory where the tiles will be created") do |dir|
30
+ options.output_dir = dir
31
+ end
32
+ opts.on("-f","--format FORMAT","Image format in which to get the file (gif, jpeg, png...). Is png by default") do |format|
33
+ options.format = format
34
+ end
35
+ opts.on("-z","--zooms ZOOM_RANGE",Range,"Range of zoom values at which the tiles must be generated. Is 0..17 by default") do |range|
36
+ options.zoom_range = range
37
+ end
38
+ opts.on("-p","--tile-param PARAM",ImageTiler::TileParam,"Corner coordinates, furthest zoom level, padding in X and Y, scale") do |tp|
39
+ options.tile_param = tp
40
+ end
41
+ opts.on("-b","--background COLOR",Magick::Pixel,"Background color components. Is fully transparent par default") do |bg|
42
+ options.bg_color = bg
43
+ end
44
+ opts.on_tail("-h", "--help", "Show this message") do
45
+ puts opts
46
+ exit
47
+ end
48
+ end
49
+
50
+ opts.parse!(ARGV)
51
+
52
+ #test the presence of all the options and exit with an error message
53
+ error = []
54
+ error << "No output directory defined (-o,--output)" if options.output_dir.nil?
55
+ error << "No tile parameter defined (-p,--tile-param)" if options.tile_param.nil?
56
+ error << "No input files defined" if ARGV.empty?
57
+
58
+ unless error.empty?
59
+ puts error * "\n" + "\n\n"
60
+ puts opts
61
+ exit
62
+ end
63
+
64
+ ImageTiler.get_tiles(options.output_dir,ARGV,options.tile_param,options.zoom_range,options.bg_color,options.format)
65
+
66
+
data/tools/tile_wms.rb ADDED
@@ -0,0 +1,78 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'ym4r/google_maps/tiler/wms_tiler'
3
+ include Ym4r::GoogleMaps::Tiler
4
+
5
+ require 'optparse'
6
+ require 'ostruct'
7
+
8
+ OptionParser.accept(Range, /(\d+)\.\.(\d+)/) do |range,start,finish|
9
+ Range.new(start.to_i,finish.to_i)
10
+ end
11
+
12
+ OptionParser.accept(WmsTiler::FurthestZoom, /(\d+),(\d+),(\d+),(\d+),(\d+)/) do |setting,l_corner, u_corner, zoom, width, height|
13
+ WmsTiler::FurthestZoom.new(WmsTiler::Point.new(l_corner.to_i,u_corner.to_i),zoom.to_i,WmsTiler::Point.new(width.to_i,height.to_i))
14
+ end
15
+
16
+ options = OpenStruct.new
17
+ #set some defaults
18
+ options.format = "png"
19
+ options.zoom_range = 0..17
20
+ options.styles = ""
21
+ options.srs = 54004
22
+ options.geographic = false
23
+
24
+ opts = OptionParser.new do |opts|
25
+ opts.banner = "WMS Tiler for Google Maps\nUsage: tile_wms.rb [options]\nExample: tile_wms.rb -o ./tiles -u http://localhost:8080/geoserver/wms -l \"topp:states\" -z 11..12 -g 602,768,11,3,3"
26
+ opts.separator ""
27
+ opts.on("-o","--output OUTPUT_DIR","Directory where the tiles will be created") do |dir|
28
+ options.output_dir = dir
29
+ end
30
+ opts.on("-u","--url WMS_SERVICE","URL to the WMS server") do |url|
31
+ options.url = url
32
+ end
33
+ opts.on("-l","--layers LAYERS","String of comma-separated layer names") do |layers|
34
+ options.layers = layers
35
+ end
36
+ opts.on("-s","--styles STYLES","String of comma-separated style names. Is empty by default") do |styles|
37
+ options.styles = styles
38
+ end
39
+ opts.on("-f","--format FORMAT","Image format in which to get the file (gif, jpeg, png...). Is png by default") do |format|
40
+ options.format = format
41
+ end
42
+ opts.on("-z","--zooms ZOOM_RANGE",Range,"Range of zoom values at which the tiles must be generated. Is 0..17 by default") do |range|
43
+ options.zoom_range = range
44
+ end
45
+ opts.on("-g","--gmap-setting SETTING",WmsTiler::FurthestZoom,"Corner coordinates, furthest zoom level, tile width and height") do |fz|
46
+ options.furthest_zoom = fz
47
+ end
48
+ opts.on("-w","--[no-]geographic","Query the WMS server with LatLon coordinates isntead of using the Mercator projection") do |g|
49
+ options.geographic = g
50
+ end
51
+ opts.on("-e", "--epsg SRS","SRS to query the WMS server. Should be a the SRS id of a Simple Mercator projection. Can vary between WMS servers. Is 54004 (Simple Mercator for Mapserver) by default") do |srs|
52
+ options.srs = srs
53
+
54
+ end
55
+ opts.on_tail("-h", "--help", "Show this message") do
56
+ puts opts
57
+ exit
58
+ end
59
+ end
60
+
61
+ opts.parse!(ARGV)
62
+
63
+ #test the presence of all the options and exit with an error message
64
+ error = []
65
+ error << "No output directory defined (-o,--output)" if options.output_dir.nil?
66
+ error << "No WMS URL defined (-u,--url)" if options.url.nil?
67
+ error << "No Google Maps setting defined (-g,--gmap-setting)" if options.furthest_zoom.nil?
68
+ error << "No WMS layer defined (-l,--layers)" if options.layers.nil?
69
+
70
+ unless error.empty?
71
+ puts error * "\n" + "\n\n"
72
+ puts opts
73
+ exit
74
+ end
75
+
76
+ WmsTiler.get_tiles(options.output_dir,options.url,options.furthest_zoom,options.zoom_range,options.layers,options.geographic,options.srs,options.styles,options.format)
77
+
78
+
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.1.4
7
- date: 2006-06-05 00:00:00 +05:00
6
+ version: 0.2.1
7
+ date: 2006-06-12 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
@@ -34,10 +34,13 @@ files:
34
34
  - lib/ym4r/google_maps/api_key.rb
35
35
  - lib/ym4r/google_maps/control.rb
36
36
  - lib/ym4r/google_maps/helper.rb
37
+ - lib/ym4r/google_maps/layer.rb
37
38
  - lib/ym4r/google_maps/map.rb
38
39
  - lib/ym4r/google_maps/mapping.rb
39
40
  - lib/ym4r/google_maps/overlay.rb
40
41
  - lib/ym4r/google_maps/point.rb
42
+ - lib/ym4r/google_maps/tiler/image_tiler.rb
43
+ - lib/ym4r/google_maps/tiler/wms_tiler.rb
41
44
  - lib/ym4r/yahoo_maps/app_id.rb
42
45
  - lib/ym4r/yahoo_maps/building_block.rb
43
46
  - lib/ym4r/yahoo_maps/flash.rb
@@ -54,6 +57,9 @@ files:
54
57
  - lib/ym4r/yahoo_maps/flash/tool.rb
55
58
  - lib/ym4r/yahoo_maps/flash/widget.rb
56
59
  - lib/ym4r/google_maps/config/config.yml
60
+ - lib/ym4r/google_maps/javascript/wms-gs.js
61
+ - tools/tile_image.rb
62
+ - tools/tile_wms.rb
57
63
  - test/test_geocoding.rb
58
64
  - test/test_google_maps.rb
59
65
  - test/test_local_search.rb