mapkit 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +19 -0
- data/README.markdown +55 -0
- data/lib/google_local.rb +59 -0
- data/lib/mapkit.rb +184 -0
- data/lib/tilekit.rb +57 -0
- metadata +101 -0
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.
|
data/README.markdown
ADDED
@@ -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.
|
data/lib/google_local.rb
ADDED
@@ -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
|
data/lib/mapkit.rb
ADDED
@@ -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
|
data/lib/tilekit.rb
ADDED
@@ -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
|
+
|