google_static_maps_helper 1.3.2 → 1.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1dc3b1f8fedc9305d84b0cc3332f378bd40cdebf101d0e41dc43612c088fbbd0
4
+ data.tar.gz: eb7c8f2723e3ec54a27f39362320212047d5cbc83b793ea8b64956be068432c9
5
+ SHA512:
6
+ metadata.gz: 0417026d675d2e70d4f1a4288432d3614bb0c49c46fa9ae963cf162aa59054bb1323264b42bffd747810cfcf6721c533f9615ac2439e8f84ea52e1f9bbb334b0
7
+ data.tar.gz: 900a3386da65ee3e2f9c273d2398b000937bb968b7d61ed8364025aefbf204f13e136b3aae0f937a425e306cfee994743140f9433c760cd4c09d63bceaf06d11
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ google_static_maps_helper (1.3.6)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.3)
10
+ rake (13.0.1)
11
+ rspec (3.9.0)
12
+ rspec-core (~> 3.9.0)
13
+ rspec-expectations (~> 3.9.0)
14
+ rspec-mocks (~> 3.9.0)
15
+ rspec-core (3.9.2)
16
+ rspec-support (~> 3.9.3)
17
+ rspec-expectations (3.9.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.9.0)
20
+ rspec-mocks (3.9.1)
21
+ diff-lcs (>= 1.2.0, < 2.0)
22
+ rspec-support (~> 3.9.0)
23
+ rspec-support (3.9.3)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ bundler (>= 1.6)
30
+ google_static_maps_helper!
31
+ rake (>= 10.0)
32
+ rspec
33
+
34
+ BUNDLED WITH
35
+ 2.1.4
@@ -4,10 +4,6 @@ This gem provides a simple interface to the Google Static Maps V2 API (http://co
4
4
 
5
5
 
6
6
  = Installation
7
- If you haven't already, add gemcutter as a gem source
8
- [sudo] gem sources --add http://gemcutter.org
9
-
10
- Then install this gem
11
7
  [sudo] gem install google_static_maps_helper
12
8
 
13
9
 
@@ -18,15 +14,14 @@ Then install this gem
18
14
  marker :lng => 3, :lat => 4
19
15
  end
20
16
 
21
- This will return the URL for a map with these markers. Please note that if you do use this approach you have to set key, size and sensor setting.
17
+ This will return the URL for a map with these markers. Please note that if you do use this approach you have to set size and sensor setting.
22
18
  You do this by:
23
- GoogleStaticMapsHelper.key = 'your google key'
24
19
  GoogleStaticMapsHelper.size = '300x600'
25
20
  GoogleStaticMapsHelper.sensor = false
26
21
 
27
- If you are on Rails you can put it in your environment configuration file and while you are there you can add the following line inside the configuration block:
22
+ If you are on Rails (2.3.x) you can put it in your environment configuration file and while you are there you can add the following line inside the configuration block:
28
23
  Rails::Initializer.run do |config|
29
- config.gem 'google_static_maps_helper', :source => 'http://gemcutter.org'
24
+ config.gem 'google_static_maps_helper'
30
25
  ...
31
26
  end
32
27
 
@@ -56,24 +51,24 @@ You uild a marker by simply sending in an object which responds to lng and lat,
56
51
  marker = GoogleStaticMapsHelper::Marker.new(location)
57
52
  marker = GoogleStaticMapsHelper::Marker.new(:lng => 1, :lat => 2)
58
53
 
59
- Google's static maps supports some options on markers. You can change the color, the label and the size.
54
+ Google's static maps supports some options on markers. You can change the color, the label and the size or you can send in your own icon.
60
55
  (http://code.google.com/apis/maps/documentation/staticmaps/#MarkerStyles). You send in options as a second
61
56
  parameter to the new method if you gave a location object, or include it in the hash where lng and lat is.
62
57
  marker = GoogleStaticMapsHelper::Marker.new(location, :color => 'blue')
63
58
  marker = GoogleStaticMapsHelper::Marker.new(:lng => 1, :lat => 2, :color => 'blue')
59
+ marker = GoogleStaticMapsHelper::Marker.new(:lng => 1, :lat => 2, :icon => 'http://www.domain.com/icon.png')
64
60
 
65
61
 
66
62
  == Map
67
- A Map holds many markers, paths, pluss some additional options like the size of the static map, Google key, what zoom level and center point it should be fixed to
63
+ A Map holds many markers, paths, pluss some additional options like the size of the static map, what zoom level and center point it should be fixed to
68
64
  etc. If no zoom or center point is give it will calculate the map view based on the markers or paths. You can leave markers and paths out, but then you have to supply
69
65
  zoom and center. More info can be found here: http://code.google.com/apis/maps/documentation/staticmaps/#URL_Parameters.
70
66
 
71
67
  === How to build a map?
72
- When building a map object you have to supply key, sensor and size. Other options are optional.
73
- map = GoogleStaticMapsHelper::Map.new(:key => YOUR_GOOGLE_KEY, :sensor => false, :size => '200x300')
68
+ When building a map object you have to supply sensor and size. Other options are optional.
69
+ map = GoogleStaticMapsHelper::Map.new(:sensor => false, :size => '200x300')
74
70
 
75
71
  Or, if you have set default options on GoogleStaticMapsHelper, you can skip the required keys when creating a map
76
- GoogleStaticMapsHelper.key = 'your google key'
77
72
  GoogleStaticMapsHelper.size = '300x600'
78
73
  GoogleStaticMapsHelper.sensor = false
79
74
  map = GoogleStaticMapsHelper::Map.new
@@ -84,12 +79,11 @@ With the map object made, we are ready to add some markers to it:
84
79
  map << marker
85
80
  map << another_marker << yet_another_marker << and_one_more_marker
86
81
 
87
- We can now ask the map for it's URL to where we'll get the requested map from. This URL can be used as src attribute on an image tag.
82
+ We can now ask the map for it's URL to where we'll get the map from. This URL can be used as source attribute on an image tag.
88
83
  map.url
89
84
 
90
85
  Another thing you might want to do is to override the center point and zoom level. This is done during map construction
91
- map = GoogleStaticMapsHelper::Map.new(:key => YOUR_GOOGLE_KEY,
92
- :sensor => false,
86
+ map = GoogleStaticMapsHelper::Map.new(:sensor => false,
93
87
  :size => '200x300',
94
88
  :center => '1,2',
95
89
  :zoom => 4)
@@ -97,7 +91,10 @@ Another thing you might want to do is to override the center point and zoom leve
97
91
 
98
92
 
99
93
  == Paths and Polygons
100
- It is also possible to create Paths and Polygons and to use them together with markers. To create a Path you need two Points and a Path object:
94
+ It is also possible to create Paths and Polygons and to use them together with markers.
95
+ Paths and Polygons will by default encode it's points with Google's encoding algorithm.
96
+
97
+ To create a Path you need two Points and a Path object:
101
98
  path = GoogleStaticMapsHelper::Path.new
102
99
  start_point = {:lat => 1, :lng => 2} # You can also use any object which responds to lat and lng here
103
100
  end_point = {:lat => 1, :lng => 2}
@@ -119,10 +116,10 @@ If you feel like it, you can add points at construction time:
119
116
 
120
117
 
121
118
  == Locations
122
- The location class is what we wrap all our lat- and lng values in. Either it is a Marker, or points for a Path. It has a few helper methods which
123
- you can use to calculate distance between locations (<tt>distance_to(another_location)</tt>) and use to generate new locations based on
124
- distance and heading (<tt>endpoint(distance, heading)</tt>). It also have a helper method called <tt>endpoints_for_circle_with_radius</tt>
125
- which returns locations that make up a circle around the receiving location. Some examples:
119
+ The location class is what we wrap all our lat- and lng values in. Either it is a Marker, or points for a Path; it will store it's lng and lat value
120
+ in a Location object. Location objects have a few helper methods which you can use to calculate distance between
121
+ locations (<tt>distance_to(another_location)</tt>) and use to generate new locations based on distance and heading from current location (<tt>endpoint(distance, heading)</tt>).
122
+ It also have a helper method called <tt>endpoints_for_circle_with_radius</tt> which returns locations that makes up a circle around the receiving location. Some examples:
126
123
 
127
124
  # Create a marker object, which it's lng and lat are stored under the hood as a location object.
128
125
  # Methods which the marker itself cannot answer, like lat and lng and distance_to is delegated to the location object
@@ -136,9 +133,6 @@ which returns locations that make up a circle around the receiving location. Som
136
133
  map << path
137
134
 
138
135
 
139
- == TODO
140
- * Ruby 1.9 support
141
-
142
136
  == Copyright
143
137
 
144
138
  Copyright (c) 2009 Thorbjørn Hermansen. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,48 +1 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "google_static_maps_helper"
8
- gem.summary = %Q{This gem provides a simple interface to the Google Static Maps V2 API}
9
- gem.description = %Q{This gem provides a simple interface to the Google Static Maps V2 API.}
10
- gem.email = "thhermansen@gmail.com"
11
- gem.homepage = "http://github.com/thhermansen/google_static_maps_helper"
12
- gem.authors = ["Thorbjørn Hermansen"]
13
- gem.add_development_dependency "rspec"
14
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
- end
16
- rescue LoadError
17
- puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
- end
19
-
20
- require 'spec/rake/spectask'
21
- Spec::Rake::SpecTask.new(:spec) do |spec|
22
- spec.libs << 'lib' << 'spec'
23
- spec.spec_files = FileList['spec/**/*_spec.rb']
24
- end
25
-
26
- Spec::Rake::SpecTask.new(:rcov) do |spec|
27
- spec.libs << 'lib' << 'spec'
28
- spec.pattern = 'spec/**/*_spec.rb'
29
- spec.rcov = true
30
- end
31
-
32
- task :spec => :check_dependencies
33
-
34
- task :default => :spec
35
-
36
- require 'rake/rdoctask'
37
- Rake::RDocTask.new do |rdoc|
38
- if File.exist?('VERSION')
39
- version = File.read('VERSION')
40
- else
41
- version = ""
42
- end
43
-
44
- rdoc.rdoc_dir = 'rdoc'
45
- rdoc.title = "google_static_maps_helper #{version}"
46
- rdoc.rdoc_files.include('README*')
47
- rdoc.rdoc_files.include('lib/**/*.rb')
48
- end
1
+ require "bundler/gem_tasks"
@@ -1,4 +1,21 @@
1
- = v.1.3.2 (in git)
1
+ = v.1.3.7
2
+ * Use HTTPS in google url (by morgoth)
3
+
4
+ = v.1.3.6
5
+ * Removed deprecation warnings `URI.escape is obsolete` in Ruby 2.7 (by morgoth)
6
+
7
+ = v.1.3.5
8
+ * Added support for icon on markers.
9
+ * DEPRECATED Google API key as its no longer required when requesting a static map (will be removed from this gem in upcoming versions).
10
+
11
+ = v.1.3.4
12
+ * Paths are now as default encoding it's points with Google's encoding algorithm. Thanks to Joel Rosenberg for providing an easy class to use.
13
+ This will cut the length of the generated URL quite a bit; down to between 1/3 and 1/2 of the old size.
14
+
15
+ = v.1.3.3
16
+ * We are now supporting ruby 1.9.1.
17
+
18
+ = v.1.3.2
2
19
  * Location will now round and reduce numbers so it doesn't get above a precision of 6.
3
20
  * Added two helper methods to Location: distance_to(another_location) and endpoint(distance, heading).
4
21
  * a_location.endpoints_for_circle_with_radius(radius) returns an array of end points making a circle around location.
@@ -1,67 +1,31 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ require_relative "lib/google_static_maps_helper/version"
5
3
 
6
- Gem::Specification.new do |s|
7
- s.name = %q{google_static_maps_helper}
8
- s.version = "1.3.1"
4
+ Gem::Specification.new do |spec|
5
+ spec.name = "google_static_maps_helper"
6
+ spec.version = GoogleStaticMapsHelper::VERSION
7
+ spec.authors = ["Thorbjørn Hermansen"]
8
+ spec.email = ["thhermansen@gmail.com"]
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Thorbj\303\270rn Hermansen"]
12
- s.date = %q{2009-10-31}
13
- s.description = %q{This gem provides a simple interface to the Google Static Maps V2 API.}
14
- s.email = %q{thhermansen@gmail.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- ".gitignore",
22
- "LICENSE",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "changelog.txt",
27
- "google_static_maps_helper.gemspec",
28
- "lib/google_static_maps_helper.rb",
29
- "lib/google_static_maps_helper/location.rb",
30
- "lib/google_static_maps_helper/map.rb",
31
- "lib/google_static_maps_helper/marker.rb",
32
- "lib/google_static_maps_helper/path.rb",
33
- "spec/google_static_maps_helper_spec.rb",
34
- "spec/location_spec.rb",
35
- "spec/map_spec.rb",
36
- "spec/marker_spec.rb",
37
- "spec/path_spec.rb",
38
- "spec/spec_helper.rb"
39
- ]
40
- s.homepage = %q{http://github.com/thhermansen/google_static_maps_helper}
41
- s.rdoc_options = ["--charset=UTF-8"]
42
- s.require_paths = ["lib"]
43
- s.rubygems_version = %q{1.3.5}
44
- s.summary = %q{This gem provides a simple interface to the Google Static Maps V2 API}
45
- s.test_files = [
46
- "spec/spec_helper.rb",
47
- "spec/marker_spec.rb",
48
- "spec/google_static_maps_helper_spec.rb",
49
- "spec/location_spec.rb",
50
- "spec/map_spec.rb",
51
- "spec/path_spec.rb"
52
- ]
10
+ spec.summary = %Q{This gem provides a simple interface to the Google Static Maps V2 API}
11
+ spec.description = %Q{This gem provides a simple interface to the Google Static Maps V2 API.}
12
+ spec.homepage = "http://github.com/thhermansen/google_static_maps_helper"
13
+ spec.license = "MIT"
53
14
 
54
- if s.respond_to? :specification_version then
55
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
- s.specification_version = 3
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "http://github.com/thhermansen/google_static_maps_helper"
17
+ spec.metadata["changelog_uri"] = "https://github.com/thhermansen/google_static_maps_helper/blob/master/changelog.txt"
57
18
 
58
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
59
- s.add_development_dependency(%q<rspec>, [">= 0"])
60
- else
61
- s.add_dependency(%q<rspec>, [">= 0"])
62
- end
63
- else
64
- s.add_dependency(%q<rspec>, [">= 0"])
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
65
23
  end
66
- end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
67
27
 
28
+ spec.add_development_dependency "bundler", ">= 1.6"
29
+ spec.add_development_dependency "rake", ">= 10.0"
30
+ spec.add_development_dependency "rspec"
31
+ end
@@ -1,10 +1,12 @@
1
+ # -*- encoding: utf-8 -*-
1
2
  require 'uri'
2
3
  require File.dirname(__FILE__) + '/google_static_maps_helper/map'
3
4
  require File.dirname(__FILE__) + '/google_static_maps_helper/location'
4
5
  require File.dirname(__FILE__) + '/google_static_maps_helper/marker'
5
6
  require File.dirname(__FILE__) + '/google_static_maps_helper/path'
7
+ require File.dirname(__FILE__) + '/google_static_maps_helper/gmap_polyline_encoder'
6
8
 
7
- #
9
+ #
8
10
  # The Google Static Map Helper provides a simple interface to the
9
11
  # Google Static Maps V2 API (http://code.google.com/apis/maps/documentation/staticmaps/).
10
12
  #
@@ -12,14 +14,14 @@ require File.dirname(__FILE__) + '/google_static_maps_helper/path'
12
14
  # <tt>Map</tt>:: A map is what keeps all of the state of which you'll build a URL for.
13
15
  # <tt>Marker</tt>:: One or more markers can be added to the map. A marker can be customized with size, label and color.
14
16
  # <tt>Path</tt>:: A path will create lines or polygons in your map.
15
- #
17
+ #
16
18
  # == About
17
19
  #
18
20
  # Author:: Thorbjørn Hermansen (thhermansen@gmail.com)
19
21
  #
20
22
  module GoogleStaticMapsHelper
21
23
  # The basic url to the API which we'll build the URL from
22
- API_URL = 'http://maps.google.com/maps/api/staticmap'
24
+ API_URL = 'https://maps.google.com/maps/api/staticmap'
23
25
 
24
26
  class OptionMissing < ArgumentError; end # Raised when required options is not sent in during construction
25
27
  class OptionNotExist < ArgumentError; end # Raised when incoming options include keys which is invalid
@@ -29,7 +31,7 @@ module GoogleStaticMapsHelper
29
31
 
30
32
  class << self
31
33
  attr_accessor :key, :size, :sensor
32
-
34
+
33
35
  #
34
36
  # Provides a simple DSL stripping away the need of manually instantiating classes
35
37
  #
@@ -39,7 +41,7 @@ module GoogleStaticMapsHelper
39
41
  # GoogleStaticMapsHelper.key = 'your google key'
40
42
  # GoogleStaticMapsHelper.size = '300x600'
41
43
  # GoogleStaticMapsHelper.sensor = false
42
- #
44
+ #
43
45
  # # Then, you'll be able to do:
44
46
  # url = GoogleStaticMapsHelper.url_for do
45
47
  # marker :lng => 1, :lat => 2
@@ -62,5 +64,11 @@ module GoogleStaticMapsHelper
62
64
  block.arity < 1 ? map.instance_eval(&block) : block.call(map)
63
65
  map.url
64
66
  end
67
+
68
+
69
+ def key=(key)# :nodoc:
70
+ warn "[DEPRECATION] 'key' is deprecated. Key is no longer used when require a static map. Key will be removed from this gem soon!"
71
+ @key = key
72
+ end
65
73
  end
66
74
  end
@@ -0,0 +1,390 @@
1
+ #--
2
+ #
3
+ # Utility for creating Google Maps Encoded GPolylines
4
+ #
5
+ # License: You may distribute this code under the same terms as Ruby itself
6
+ #
7
+ # Author: Joel Rosenberg
8
+ #
9
+ # ( Drawing from the official example pages as well as Mark McClure's work )
10
+ #
11
+ # == Example
12
+ #
13
+ # data = [
14
+ # [ 37.4419, -122.1419],
15
+ # [ 37.4519, -122.1519],
16
+ # [ 37.4619, -122.1819],
17
+ # ]
18
+ #
19
+ # encoder = GMapPolylineEncoder.new()
20
+ # result = encoder.encode( data )
21
+ #
22
+ # javascript << " var myLine = new GPolyline.fromEncoded({\n"
23
+ # javascript << " color: \"#FF0000\",\n"
24
+ # javascript << " weight: 10,\n"
25
+ # javascript << " opacity: 0.5,\n"
26
+ # javascript << " zoomFactor: #{result[:zoomFactor]},\n"
27
+ # javascript << " numLevels: #{result[:numLevels]},\n"
28
+ # javascript << " points: \"#{result[:points]}\",\n"
29
+ # javascript << " levels: \"#{result[:levels]}\"\n"
30
+ # javascript << " });"
31
+ #
32
+ # == Methods
33
+ #
34
+ # Constructor args (all optional):
35
+ # :numLevels (default 18)
36
+ # :zoomFactor (default 2)
37
+ # :reduce: Reduce points (default true)
38
+ # :escape: Escape backslashes (default true)
39
+ #
40
+ # encode( points ) method
41
+ # points (required): array of longitude, latitude pairs
42
+ #
43
+ # returns hash with keys :points, :levels, :zoomFactor, :numLevels
44
+ #
45
+ # == Background
46
+ #
47
+ # Description: http://www.google.com/apis/maps/documentation/#Encoded_Polylines
48
+ # API: http://www.google.com/apis/maps/documentation/reference.html#GPolyline
49
+ # Hints: http://www.google.com/apis/maps/documentation/polylinealgorithm.html
50
+ #
51
+ # Example Javascript for instantiating an encoded polyline:
52
+ # var encodedPolyline = new GPolyline.fromEncoded({
53
+ # color: "#FF0000",
54
+ # weight: 10,
55
+ # points: "yzocFzynhVq}@n}@o}@nzD",
56
+ # levels: "BBB",
57
+ # zoomFactor: 32,
58
+ # numLevels: 4
59
+ # });
60
+ #
61
+ # == Changes
62
+ #
63
+ # 06.29.2007 - Release 0.1
64
+ # Profiling showed that distance() accounted for 50% of the time when
65
+ # processing McClure's British coast data. By moving the distance
66
+ # calculation into encode(), we can cache a few of the calculations
67
+ # (magnitude) and eliminate the overhead of the function call. This
68
+ # reduced the time to encode by ~ 30%
69
+ #
70
+ # 06.21.2007 Implementing the Doublas-Peucker algorithm for removing superflous
71
+ # points as per Mark McClure's design:
72
+ # http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/
73
+ #
74
+ # 10.14.2006 Cleaned up (and finally grasped) zoom levels
75
+ #
76
+ # 09.2006 First port of the official example's javascript. Ignoring zoom
77
+ # levels for now, showing points at all zoom levels
78
+ #
79
+ #++
80
+ module GoogleStaticMapsHelper
81
+ class GMapPolylineEncoder
82
+ attr_accessor :reduce, :escape #zoomFactor and numLevels need side effects
83
+ attr_reader :zoomFactor, :numLevels
84
+
85
+ # The minimum distance from the line that a point must exceed to avoid
86
+ # elimination under the DP Algorithm.
87
+ @@dp_threshold = 0.00001
88
+
89
+ def initialize(options = {})
90
+ # There are no required parameters
91
+
92
+ # Nice defaults
93
+ @numLevels = options.has_key?(:numLevels) ? options[:numLevels] : 18
94
+ @zoomFactor = options.has_key?(:zoomFactor) ? options[:zoomFactor] : 2
95
+
96
+ # Calculate the distance thresholds for each zoom level
97
+ calculate_zoom_breaks()
98
+
99
+ # By default we'll simplify the polyline unless told otherwise
100
+ @reduce = ! options.has_key?(:reduce) ? true : options[:reduce]
101
+
102
+ # Escape by default; most people are using this in a web context
103
+ @escape = ! options.has_key?(:escape) ? true : options[:escape]
104
+
105
+ end
106
+
107
+ def numLevels=( new_num_levels )
108
+ @numLevels = new_num_levels
109
+ # We need to recalculate our zoom breaks
110
+ calculate_zoom_breaks()
111
+ end
112
+
113
+ def zoomFactor=( new_zoom_factor )
114
+ @zoomFactor = new_zoom_factor
115
+ # We need to recalculate our zoom breaks
116
+ calculate_zoom_breaks()
117
+ end
118
+
119
+ def encode( points )
120
+
121
+ #
122
+ # This is an implementation of the Douglas-Peucker algorithm for simplifying
123
+ # a line. You can thing of it as an elimination of points that do not
124
+ # deviate enough from a vector. That threshold for point elimination is in
125
+ # @@dp_threshold. See
126
+ #
127
+ # http://everything2.com/index.pl?node_id=859282
128
+ #
129
+ # for an explanation of the algorithm
130
+ #
131
+
132
+ max_dist = 0 # Greatest distance we measured during the run
133
+ stack = []
134
+ distances = Array.new(points.size)
135
+
136
+ if(points.length > 2)
137
+ stack << [0, points.size-1]
138
+
139
+ while(stack.length > 0)
140
+ current_line = stack.pop()
141
+ p1_idx = current_line[0]
142
+ pn_idx = current_line[1]
143
+ pb_dist = 0
144
+ pb_idx = nil
145
+
146
+ x1 = points[p1_idx][0]
147
+ y1 = points[p1_idx][1]
148
+ x2 = points[pn_idx][0]
149
+ y2 = points[pn_idx][1]
150
+
151
+ # Caching the line's magnitude for performance
152
+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
153
+ magnitude_squared = magnitude ** 2
154
+
155
+ # Find the farthest point and its distance from the line between our pair
156
+ for i in (p1_idx+1)..(pn_idx-1)
157
+
158
+ # Refactoring distance computation inline for performance
159
+ #current_distance = compute_distance(points[i], points[p1_idx], points[pn_idx])
160
+
161
+ #
162
+ # This uses Euclidian geometry. It shouldn't be that big of a deal since
163
+ # we're using it as a rough comparison for line elimination and zoom
164
+ # calculation.
165
+ #
166
+ # TODO: Implement Haversine functions which would probably bring this to
167
+ # a snail's pace (ehhhh)
168
+ #
169
+
170
+ px = points[i][0]
171
+ py = points[i][1]
172
+
173
+ current_distance = nil
174
+
175
+ if( magnitude == 0 )
176
+ # The line is really just a point
177
+ current_distance = Math.sqrt((x2-px)**2 + (y2-py)**2)
178
+ else
179
+
180
+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / magnitude_squared
181
+
182
+ if( u <= 0 || u > 1 )
183
+ # The point is closest to an endpoint. Find out which one
184
+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
185
+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
186
+ if( ix > iy )
187
+ current_distance = iy
188
+ else
189
+ current_distance = ix
190
+ end
191
+ else
192
+ # The perpendicular point intersects the line
193
+ ix = x1 + u * (x2 - x1)
194
+ iy = y1 + u * (y2 - y1)
195
+ current_distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
196
+ end
197
+ end
198
+
199
+ # See if this distance is the greatest for this segment so far
200
+ if(current_distance > pb_dist)
201
+ pb_dist = current_distance
202
+ pb_idx = i
203
+ end
204
+ end
205
+
206
+ # See if this is the greatest distance for all points
207
+ if(pb_dist > max_dist)
208
+ max_dist = pb_dist
209
+ end
210
+
211
+ if(pb_dist > @@dp_threshold)
212
+ # Our point, Pb, that had the greatest distance from the line, is also
213
+ # greater than our threshold. Process again using Pb as a new
214
+ # start/end point. Record this distance - we'll use it later when
215
+ # creating zoom values
216
+ distances[pb_idx] = pb_dist
217
+ stack << [p1_idx, pb_idx]
218
+ stack << [pb_idx, pn_idx]
219
+ end
220
+
221
+ end
222
+ end
223
+
224
+ # Force line endpoints to be included (sloppy, but faster than checking for
225
+ # endpoints in encode_points())
226
+ distances[0] = max_dist
227
+ distances[distances.length-1] = max_dist
228
+
229
+ # Create Base64 encoded strings for our points and zoom levels
230
+ points_enc = encode_points( points, distances)
231
+ levels_enc = encode_levels( points, distances, max_dist)
232
+
233
+ # Make points_enc an escaped string if desired.
234
+ # We should escape the levels too, in case google pulls a switcheroo
235
+ @escape && points_enc && points_enc.gsub!( /\\/, '\\\\\\\\' )
236
+
237
+
238
+ # Returning a hash. Yes, I am a Perl programmer
239
+ return {
240
+ :points => points_enc,
241
+ :levels => levels_enc,
242
+ :zoomFactor => @zoomFactor,
243
+ :numLevels => @numLevels,
244
+ }
245
+
246
+ end
247
+
248
+ private
249
+
250
+ def calculate_zoom_breaks()
251
+ # Calculate the distance thresholds for each zoom level
252
+ @zoom_level_breaks = Array.new(@numLevels);
253
+
254
+ for i in 0..(@numLevels-1)
255
+ @zoom_level_breaks[i] = @@dp_threshold * (@zoomFactor ** ( @numLevels-i-1));
256
+ end
257
+
258
+ return
259
+ end
260
+
261
+ def encode_points( points, distances )
262
+ encoded = ""
263
+
264
+ plat = 0
265
+ plon = 0
266
+
267
+ #points.each do |point| # Gah, need the distances.
268
+ for i in 0..(points.size() - 1)
269
+ if(! @reduce || distances[i] != nil )
270
+ point = points[i]
271
+ late5 = (point[0] * 1e5).floor();
272
+ lone5 = (point[1] * 1e5).floor();
273
+
274
+ dlat = late5 - plat
275
+ dlon = lone5 - plon
276
+
277
+ plat = late5;
278
+ plon = lone5;
279
+
280
+ # I used to need this for some reason
281
+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlat)).to_s
282
+ #encoded << encodeSignedNumber(Fixnum.induced_from(dlon)).to_s
283
+ encoded << encodeSignedNumber(dlat).to_s
284
+ encoded << encodeSignedNumber(dlon).to_s
285
+ end
286
+ end
287
+
288
+ return encoded
289
+
290
+ end
291
+
292
+ def encode_levels( points, distances, max_dist )
293
+
294
+ encoded = "";
295
+
296
+ # Force startpoint
297
+ encoded << encodeNumber(@numLevels - 1)
298
+
299
+ if( points.size() > 2 )
300
+ for i in 1..(points.size() - 2)
301
+ distance = distances[i]
302
+ if( ! @reduce || distance != nil)
303
+ computed_level = 0
304
+
305
+ while (distance < @zoom_level_breaks[computed_level]) do
306
+ computed_level += 1
307
+ end
308
+
309
+ encoded << encodeNumber( @numLevels - computed_level - 1 )
310
+ end
311
+ end
312
+ end
313
+
314
+ # Force endpoint
315
+ encoded << encodeNumber(@numLevels - 1)
316
+
317
+ return encoded;
318
+
319
+ end
320
+
321
+ def compute_distance( point, lineStart, lineEnd )
322
+
323
+ #
324
+ # Note: This has been refactored to encode() inline for performance and
325
+ # computation caching
326
+ #
327
+
328
+ px = point[0]
329
+ py = point[1]
330
+ x1 = lineStart[0]
331
+ y1 = lineStart[1]
332
+ x2 = lineEnd[0]
333
+ y2 = lineEnd[1]
334
+
335
+ distance = nil
336
+
337
+ magnitude = Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
338
+
339
+ if( magnitude == 0 )
340
+ return Math.sqrt((x2-px)**2 + (y2-py)**2)
341
+ end
342
+
343
+ u = (((px - x1) * (x2 - x1)) + ((py - y1) * (y2 - y1))) / (magnitude**2)
344
+
345
+ if( u <= 0 || u > 1 )
346
+ # The point is closest to an endpoint. Find out which
347
+ ix = Math.sqrt((x1 - px)**2 + (y1 - py)**2)
348
+ iy = Math.sqrt((x2 - px)**2 + (y2 - py)**2)
349
+ if( ix > iy )
350
+ distance = iy
351
+ else
352
+ distance = ix
353
+ end
354
+ else
355
+ # The perpendicular point intersects the line
356
+ ix = x1 + u * (x2 - x1)
357
+ iy = y1 + u * (y2 - y1)
358
+ distance = Math.sqrt((ix - px)**2 + (iy - py)**2)
359
+ end
360
+
361
+ return distance
362
+ end
363
+
364
+ def encodeSignedNumber(num)
365
+ # Based on the official google example
366
+
367
+ sgn_num = num << 1
368
+
369
+ if( num < 0 )
370
+ sgn_num = ~(sgn_num)
371
+ end
372
+
373
+ return encodeNumber(sgn_num)
374
+ end
375
+
376
+ def encodeNumber(num)
377
+ # Based on the official google example
378
+
379
+ encoded = "";
380
+
381
+ while (num >= 0x20) do
382
+ encoded << ((0x20 | (num & 0x1f)) + 63).chr;
383
+ num = num >> 5;
384
+ end
385
+
386
+ encoded << (num + 63).chr;
387
+ return encoded;
388
+ end
389
+ end
390
+ end