mapkit 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 inovex GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,55 @@
1
+ ## MapKit & TileKit
2
+
3
+ MapKit and TileKit are tools to help you develop a tile rendering service in
4
+ ruby. If you need to draw many markers in google maps then the performance
5
+ limit is reached fast. Google has advices to use up to 20 markers at a time.
6
+ With some tricks you can inrease the number of markes but with the cost of
7
+ inprintability.
8
+
9
+ If you want to take the full power of google maps you might have to render
10
+ your own tile over the tiles of google. Like layers where your layer is on top
11
+ of googles.
12
+
13
+ The system is very simple. You have to add a new Layer to your Google Maps that
14
+ request a tile with X, Y and Z like this:
15
+
16
+ var layer = new GTileLayer(null, 0, 21, {
17
+ isPng: true,
18
+ opacity: 1
19
+ });
20
+ layer.getTileUrl = function(tile, zoom) {
21
+ return "" + zoom + "/" + tile.x + "/" + tile.y + ".png";
22
+ }
23
+ map.addOverlay(new GTileLayerOverlay(layer));
24
+
25
+ Once this is done, google starts to request tiles from your server. To
26
+ implement the server you need to decode X (tile x), Y (tile y) and
27
+ Z (zoom level) to a bounding box of latitude and longitude so that you can
28
+ check what to draw in the tile that was requested. After you have fetched some
29
+ points you have to draw them in the Tile. This is where TileKit comes into play.
30
+ TileKit relies on gd2 a well known image rendering library (http://libgd.org).
31
+
32
+ # this example assumes that a request with x, y and z was done
33
+ # by the browser and saved into x, y, z
34
+
35
+ POI = TileKit::Icon.new("images/poi.png", [20, 20], [3, 20], [0, 0, 20, 17])
36
+ bounding_box = MapKit.bounding_box(x, y, z)
37
+
38
+ # search for points_of_interest in a bigger bouning box (grow by 10%)
39
+ points = DB.points(bounding_box.grow(10))
40
+
41
+ unless points.empty?
42
+ # cerate tile
43
+ tile = TileKit::Image.new(bounding_box)
44
+
45
+ # draw icons at point positions
46
+ for point in points do
47
+ tile.draw_icon(point, POI)
48
+ end
49
+
50
+ # return tile
51
+ return tile.png
52
+ end
53
+
54
+ To get an overview on the whole story checkout the sample application in the
55
+ example directory. It requires json, sequel, sqlite3 and sinatra.
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + "/mapkit"
2
+ require 'httparty'
3
+
4
+ # Class for searching with the google local search
5
+ class GoogleLocal
6
+ include HTTParty
7
+ base_uri "www.google.com"
8
+ default_params :hl => :de, :v => "1.0", :rsz => :large
9
+ format :json
10
+
11
+ # searches a term near point with sspn (span in degrees)
12
+ def self.search(term, point, sspn)
13
+ resp = get("/uds/GlocalSearch", :query => { :q => term,
14
+ :sll => point.join(","), :sspn => sspn.join(",") })
15
+ if resp["responseStatus"] == 200
16
+ resp["responseData"]["results"]
17
+ else
18
+ raise Exception.new("Error in Google request")
19
+ end
20
+ end
21
+
22
+ # just searches a term in a bounding box and returns points
23
+ def self.search_in_bounding_box(term, bounding_box)
24
+ crawl(term, bounding_box.center, bounding_box.sspn).map do |i|
25
+ MapKit::Point.new(i["lat"].to_f, i["lng"].to_f)
26
+ end
27
+ end
28
+
29
+ # searches a term near point with sspn (span in degrees)
30
+ def self.crawl(term, point, sspn)
31
+ print " - crawl for '#{term}' at #{point.inspect} within #{sspn.inspect} ("
32
+ count = 0
33
+ 4.times do |i|
34
+ data = get("/uds/GlocalSearch", :query => { :q => term, :start => i * 8,
35
+ :sll => point.join(","), :sspn => sspn.join(",") }).to_hash
36
+ if data["responseStatus"] == 200
37
+ data["responseData"]["results"].each do |row|
38
+ yield(row)
39
+ count += 1
40
+ end
41
+ else
42
+ raise Exception.new("Error in Google request")
43
+ end
44
+ print "."
45
+ end
46
+ print ") results: #{count}\n"
47
+ end
48
+
49
+ # searches a term near point with sspn (span in degrees) n times n
50
+ def self.crawl_region(term, point, span, n = 10, &block) # :yields: data
51
+ half_span = span / 2
52
+ n.times do |x|
53
+ n.times do |y|
54
+ point = [point[0] + x * half_span, point[1] + y * half_span]
55
+ crawl(term, point, [half_span, half_span], &block)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,184 @@
1
+ # Module to create tile for the google maps tile overlay
2
+ module MapKit
3
+ # consant for radiants
4
+ RADIANT = Math::PI / 180.0
5
+
6
+ # the size of tiles in google maps
7
+ TILE_SIZE = 256
8
+
9
+ # the constant earth radius in meters
10
+ EARTH_RADIUS = 6_378_137
11
+
12
+ # the min latitude based on the mercator projection
13
+ MIN_LATITUDE = -85.05112877
14
+
15
+ # the max latitude based on the mercator projection
16
+ MAX_LATITUDE = 85.05112877
17
+
18
+ # the min longitude based on the mercator projection
19
+ MIN_LONGITUDE = -180
20
+
21
+ # the max longitude based on the mercator projection
22
+ MAX_LONGITUDE = 180
23
+
24
+ # the resolution in meters per pixel
25
+ RESOLUTION = 2 * Math::PI * EARTH_RADIUS / TILE_SIZE
26
+
27
+ # version of MapKit
28
+ VERSION = "0.0.1"
29
+
30
+ # The class represents an lat/lng point
31
+ class Point
32
+ attr_accessor :lat, :lng
33
+
34
+ # initializes a point object using latitude and longitude
35
+ def initialize(lat, lng)
36
+ @lat, @lng = lat, lng
37
+ end
38
+
39
+ # returns true if point is in bounding_box, false otherwise
40
+ def in?(bounding_box)
41
+ top, left, bottom, right = bounding_box.coords
42
+ (left..right) === @lng && (top..bottom) === @lat
43
+ end
44
+
45
+ # returns relative x and y for point in bounding_box
46
+ def pixel(bounding_box)
47
+ top, left, bottom, right = bounding_box.coords
48
+ ws = (right - left) / TILE_SIZE
49
+ hs = (bottom - top) / TILE_SIZE
50
+ [((@lng - left) / ws).to_i, ((@lat - top) / hs).to_i]
51
+ end
52
+ end
53
+
54
+ # The class represents a bounding box specified by a top/left point and a
55
+ # bottom/right point (the coordinates can be pixels or degrees)
56
+ class BoundingBox
57
+ attr_accessor :top, :left, :bottom, :right, :zoom
58
+
59
+ # initialize the bounding box using the positions of two points and a
60
+ # optional zoom level
61
+ #
62
+ # top
63
+ # left o------+
64
+ # | |
65
+ # | |
66
+ # +------o right
67
+ # bottom
68
+ #
69
+ def initialize(top, left, bottom, right, zoom = nil)
70
+ @top, @left, @bottom, @right, @zoom = top, left, bottom, right, zoom
71
+ end
72
+
73
+ # returns array of [top, left, bottom, right]
74
+ def coords
75
+ [@top, @left, @bottom, @right]
76
+ end
77
+
78
+ # returns array of [width, height] of sspn
79
+ def sspn
80
+ [(@right - @left) / 2, (@bottom - @top) / 2]
81
+ end
82
+
83
+ # returns [lat, lnt] of bounding box
84
+ def center
85
+ [@left + (@right - @left) / 2, @top + (@bottom - @top) / 2]
86
+ end
87
+
88
+ # grow bounding box by percentage
89
+ def grow!(percent)
90
+ lng = percent * ((@right - @left) / 100)
91
+ lat = percent * ((@top - @bottom) / 100)
92
+ @top += lat
93
+ @left -= lng
94
+ @bottom -= lat
95
+ @right += lng
96
+ end
97
+
98
+ # grow bounding box by percentage and return new bounding box object
99
+ def grow(percent)
100
+ copy = self.clone
101
+ copy.grow!(percent)
102
+ copy
103
+ end
104
+ end
105
+
106
+ # return bounding box for passed tile coordinates tiles
107
+ def self.bounding_box(tile_x, tile_y, zoom)
108
+ top, left, bottom, right = tile_bounds(tile_x, tile_y, zoom)
109
+ BoundingBox.new(top, left, bottom, right, zoom)
110
+ end
111
+
112
+ # returns bounds [top, left, bottom, right] of the given tile
113
+ # in WGS-94 coordinates
114
+ def self.tile_bounds(tile_x, tile_y, zoom)
115
+ pixel_x, pixel_y = tile2pixel(tile_x, tile_y)
116
+ top, left = pixel2latlng(pixel_x, pixel_y, zoom)
117
+
118
+ pixel_x, pixel_y = tile2pixel(tile_x + 1, tile_y + 1)
119
+ bottom, right = pixel2latlng(pixel_x, pixel_y, zoom)
120
+
121
+ [top, left, bottom, right]
122
+ end
123
+
124
+ # returns [lat, lng] shifted using the passed pixels and zoom
125
+ def self.shift_latlng(lat, lng, shift_x, shift_y, zoom)
126
+ pixel_x, pixel_y = latlng2pixel(lat.to_f, lng.to_f, zoom)
127
+ pixel_x, pixel_y = pixel_x + shift_x, pixel_y + shift_y
128
+ pixel2latlng(pixel_x, pixel_y, zoom)
129
+ end
130
+
131
+ # returns pixel coordinates [x, y] based on the passed lat/lng WGS-84
132
+ # coordinates using the specified zoom level
133
+ def self.latlng2pixel(lat, lng, zoom)
134
+ lat = clip(lat.to_f, MIN_LATITUDE, MAX_LATITUDE)
135
+ lng = clip(lng.to_f, MIN_LONGITUDE, MAX_LONGITUDE)
136
+
137
+ x = (lng + 180.0) / 360.0
138
+ sin_lat = Math.sin(lat * RADIANT)
139
+ y = 0.5 - Math.log((1.0 + sin_lat) / (1.0 - sin_lat)) / (4.0 * Math::PI)
140
+ sx, sy = map_size(zoom)
141
+
142
+ pixel_x = clip(x * sx + 0.5, 0.0, sx - 1.0)
143
+ pixel_y = clip(y * sy + 0.5, 0.0, sy - 1.0)
144
+ [pixel_x.to_i, pixel_y.to_i]
145
+ end
146
+
147
+ # returns lat/lng WGS-84 coordinates [lat, lng] basedon the passed pixel
148
+ # coordinates using the specified zoom level
149
+ def self.pixel2latlng(pixel_x, pixel_y, zoom)
150
+ sx, sy = map_size(zoom)
151
+ x = clip(pixel_x.to_f, 0.0, sx - 1.0) / sx - 0.5
152
+ y = 0.5 - clip(pixel_y.to_f, 0.0, sy - 1.0) / sy
153
+
154
+ lat = 90.0 - 360.0 * Math.atan(Math.exp(-y * 2.0 * Math::PI)) / Math::PI
155
+ lng = 360.0 * x
156
+ [lat, lng]
157
+ end
158
+
159
+ # returns the passed value in case it is in the passed range or the
160
+ # bounding min or max value
161
+ def self.clip(val, min, max)
162
+ (val < min) ? min : (val > max) ? max : val
163
+ end
164
+
165
+ # returns coordinates of tiles using passed pixel coordinates
166
+ def self.pixel2tile(pixel_x, pixel_y)
167
+ [pixel_x / TILE_SIZE, pixel_y / TILE_SIZE]
168
+ end
169
+
170
+ # returns coordinates of pixels using passed tile coordinates
171
+ def self.tile2pixel(tile_x, tile_y)
172
+ [tile_x * TILE_SIZE, tile_y * TILE_SIZE]
173
+ end
174
+
175
+ # returns the size [x, y] of the map using the passed zoom level
176
+ def self.map_size(zoom)
177
+ [TILE_SIZE << zoom, TILE_SIZE << zoom]
178
+ end
179
+
180
+ # returns resolution in meters per pixel for passed zoom level
181
+ def self.resolution(zoom)
182
+ RESOLUTION / (2 ** zoom)
183
+ end
184
+ end
@@ -0,0 +1,57 @@
1
+ require File.dirname(__FILE__) + "/mapkit"
2
+ require "gd2"
3
+
4
+ module TileKit
5
+ class Icon
6
+ attr_reader :image, :size
7
+
8
+ def initialize(path, size, peak_position, clickable_area)
9
+ @image = GD2::Image.import("images/gas.png")
10
+ @size_x, @size_y = size
11
+ @peak_x, @peak_y = peak_position
12
+ @shift_x, @shift_y, @width, @height = clickable_area
13
+ end
14
+
15
+ def draw(canvas, x, y)
16
+ # position icon at peak point
17
+ x, y = x - @peak_x, y - @peak_y
18
+
19
+ # copy image
20
+ canvas.copy_from(@image, x, y, 0, 0, @size_x, @size_y)
21
+ end
22
+
23
+ def bounding_box(lat, lng, zoom)
24
+ top, left = MapKit.shift_latlng(lat, lng, @shift_x - @peak_x, @shift_y - @peak_y, zoom)
25
+ bottom, right = MapKit.shift_latlng(top, left, @width, @height, zoom)
26
+ MapKit::BoundingBox.new(top, left, bottom, right, zoom)
27
+ end
28
+ end
29
+
30
+ class Image
31
+ attr_reader :canvas, :bounding_box
32
+
33
+ def initialize(bounding_box)
34
+ @bounding_box = bounding_box
35
+
36
+ # create image canvas
37
+ @canvas = GD2::Image.new(MapKit::TILE_SIZE, MapKit::TILE_SIZE)
38
+
39
+ # make image transparent
40
+ @canvas.save_alpha = true
41
+ @canvas.draw do |context|
42
+ context.color = GD2::Color::TRANSPARENT
43
+ context.fill
44
+ end
45
+ end
46
+
47
+ # draw icon at position
48
+ def draw_icon(point, icon)
49
+ x, y = point.pixel(@bounding_box)
50
+ icon.draw(@canvas, x, y)
51
+ end
52
+
53
+ def png
54
+ @canvas.png
55
+ end
56
+ end
57
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mapkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vincent Landgraf
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-10 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: httparty
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.5.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: gd2
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.1
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rake
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: rspec
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: |-
56
+ MapKit is an set of helpers to assist building tiles for
57
+ the google maps web client
58
+ email:
59
+ - vincent.landgraf@inovex.de
60
+ executables: []
61
+
62
+ extensions: []
63
+
64
+ extra_rdoc_files: []
65
+
66
+ files:
67
+ - lib/google_local.rb
68
+ - lib/mapkit.rb
69
+ - lib/tilekit.rb
70
+ - LICENSE
71
+ - README.markdown
72
+ has_rdoc: true
73
+ homepage: http://github.com/threez/mapkit
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ version:
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 1.3.5
92
+ version:
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.3.5
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: MapKit helps rendering tiles for google maps
100
+ test_files: []
101
+