Module: Wukong::Geolocated
- Defined in:
- lib/wu/geo/geolocated.rb
Overview
reference: Bing Maps Tile System
Defined Under Namespace
Modules: ByCoordinates
Constant Summary collapse
- EARTH_RADIUS =
meters
6371000
- MIN_LONGITUDE =
-180
- MAX_LONGITUDE =
180
- MIN_LATITUDE =
-85.05112878
- MAX_LATITUDE =
85.05112878
- ALLOWED_LONGITUDE =
(MIN_LONGITUDE..MAX_LONGITUDE)
- ALLOWED_LATITUDE =
(MIN_LATITUDE..MAX_LATITUDE)
- TILE_PIXEL_SIZE =
256
- BIT_TO_QUADKEY =
converts from even/odd state of tile x and tile y to quadkey. NOTE: bit order means y, x
{ [false, false] => "0", [false, true] => "1", [true, false] => "2", [true, true] => "3", }
- QUADKEY_TO_BIT =
converts from quadkey char to bits. NOTE: bit order means y, x
{ "0" => [0,0], "1" => [0,1], "2" => [1,0], "3" => [1,1]}
Class Method Summary collapse
-
.bbox_centroid(left_btm, right_top) ⇒ Array<Float, Float>
Returns the centroid of a bounding box.
-
.haversine_distance(left, btm, right, top) ⇒ Object
Return the haversine distance in meters between two points.
-
.haversine_midpoint(left, btm, right, top) ⇒ Object
Return the haversine midpoint in meters between two points.
-
.lat_zl_to_tile_yf(latitude, zl) ⇒ Object
Convert latitude in degrees to floating-point tile x,y coordinates at given zoom level.
-
.lng_lat_rad_to_bbox(longitude, latitude, radius) ⇒ Object
Returns a bounding box containing the circle created by the lat/lng and radius.
- .lng_lat_zl_to_pixel_xy(lng, lat, zl) ⇒ Object
-
.lng_lat_zl_to_quadkey(longitude, latitude, zl) ⇒ Object
Convert a lat/lng and zoom level into a quadkey.
-
.lng_lat_zl_to_tile_xy(longitude, latitude, zl) ⇒ Object
Convert latitude in degrees to integer tile x,y coordinates at given zoom level.
-
.lng_zl_to_tile_xf(longitude, zl) ⇒ Object
Convert longitude in degrees to floating-point tile x,y coordinates at given zoom level.
-
.map_pixel_size(zl) ⇒ Object
Width or height of grid bitmap in pixels at given zoom level.
-
.map_scale_for_dpi(latitude, zl, screen_dpi) ⇒ Object
Map scale at a specified latitude, zoom level, & screen resolution in dpi.
-
.map_tile_size(zl) ⇒ Object
Width or height in number of tiles.
-
.packed_qk_zl_to_tile_xy(packed_qk, zl = 16) ⇒ Object
Convert a packed quadkey (integer) into tile x,y coordinates and level.
-
.pixel_resolution(latitude, zl) ⇒ Object
Return pixel resolution in meters per pixel at a specified latitude and zoom level.
-
.pixel_xy_to_tile_xy(pixel_x, pixel_y) ⇒ Object
Convert from x,y pixel pair into tile x,y coordinates.
- .pixel_xy_zl_to_lng_lat(pixel_x, pixel_y, zl) ⇒ Object
-
.point_east(longitude, latitude, distance) ⇒ Object
From a given point, calculate the change in degrees directly east a given distance.
-
.point_north(longitude, latitude, distance) ⇒ Object
From a given point, calculate the point directly north a specified distance.
-
.quadkey_containing_bbox(left, btm, right, top) ⇒ Object
Retuns the smallest quadkey containing both of corners of the given bounding box.
-
.quadkey_to_bbox(quadkey) ⇒ Object
Convert a quadkey into a bounding box using adjacent tile.
-
.quadkey_to_tile_xy_zl(quadkey) ⇒ Object
Convert a quadkey into tile x,y coordinates and level.
-
.tile_xy_to_pixel_xy(tile_x, tile_y) ⇒ Object
Convert from x,y tile pair into pixel x,y coordinates (top left corner).
-
.tile_xy_zl_to_lng_lat(tile_x, tile_y, zl) ⇒ Object
Convert from tile_x, tile_y, zoom level to longitude and latitude in degrees (slight loss of precision).
-
.tile_xy_zl_to_packed_qk(tile_x, tile_y, zl) ⇒ Object
Convert from tile x,y into a packed quadkey at a specified zoom level.
-
.tile_xy_zl_to_quadkey(tile_x, tile_y, zl) ⇒ Object
Convert from tile x,y into a quadkey at a specified zoom level.
Class Method Details
.bbox_centroid(left_btm, right_top) ⇒ Array<Float, Float>
Returns the centroid of a bounding box
246 247 248 |
# File 'lib/wu/geo/geolocated.rb', line 246 def bbox_centroid(left_btm, right_top) haversine_midpoint(*left_btm, *right_top) end |
.haversine_distance(left, btm, right, top) ⇒ Object
Return the haversine distance in meters between two points
251 252 253 254 255 256 257 258 259 260 |
# File 'lib/wu/geo/geolocated.rb', line 251 def haversine_distance(left, btm, right, top) delta_lng = (right - left).abs.to_radians delta_lat = (top - btm ).abs.to_radians btm_rad = btm.to_radians top_rad = top.to_radians aa = (Math.sin(delta_lat / 2.0))**2 + Math.cos(top_rad) * Math.cos(btm_rad) * (Math.sin(delta_lng / 2.0))**2 cc = 2.0 * Math.atan2(Math.sqrt(aa), Math.sqrt(1.0 - aa)) cc * EARTH_RADIUS end |
.haversine_midpoint(left, btm, right, top) ⇒ Object
Return the haversine midpoint in meters between two points
263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/wu/geo/geolocated.rb', line 263 def haversine_midpoint(left, btm, right, top) cos_btm = Math.cos(btm.to_radians) cos_top = Math.cos(top.to_radians) bearing_x = cos_btm * Math.cos((right - left).to_radians) bearing_y = cos_btm * Math.sin((right - left).to_radians) mid_lat = Math.atan2( (Math.sin(top.to_radians) + Math.sin(btm.to_radians)), (Math.sqrt((cos_top + bearing_x)**2 + bearing_y**2))) mid_lng = left.to_radians + Math.atan2(bearing_y, (cos_top + bearing_x)) [mid_lng.to_degrees, mid_lat.to_degrees] end |
.lat_zl_to_tile_yf(latitude, zl) ⇒ Object
Convert latitude in degrees to floating-point tile x,y coordinates at given zoom level
122 123 124 125 126 127 |
# File 'lib/wu/geo/geolocated.rb', line 122 def lat_zl_to_tile_yf(latitude, zl) raise ArgumentError, "latitude must be within bounds ((#{latitude}) vs #{ALLOWED_LATITUDE})" unless (ALLOWED_LATITUDE.include?(latitude)) sin_lat = Math.sin(latitude.to_radians) yy = Math.log((1 + sin_lat) / (1 - sin_lat)) / (4 * Math::PI) (map_tile_size(zl) * (0.5 - yy)) end |
.lng_lat_rad_to_bbox(longitude, latitude, radius) ⇒ Object
Returns a bounding box containing the circle created by the lat/lng and radius
232 233 234 235 236 237 238 |
# File 'lib/wu/geo/geolocated.rb', line 232 def lng_lat_rad_to_bbox(longitude, latitude, radius) left, _ = point_east( longitude, latitude, -radius) _, btm = point_north(longitude, latitude, -radius) right, _ = point_east( longitude, latitude, radius) _, top = point_north(longitude, latitude, radius) [left, btm, right, top] end |
.lng_lat_zl_to_pixel_xy(lng, lat, zl) ⇒ Object
324 325 326 327 328 |
# File 'lib/wu/geo/geolocated.rb', line 324 def lng_lat_zl_to_pixel_xy(lng, lat, zl) pixel_x = lng_zl_to_tile_xf(lng, zl) pixel_y = lat_zl_to_tile_yf(lat, zl) [(pixel_x * TILE_PIXEL_SIZE + 0.5).floor, (pixel_y * TILE_PIXEL_SIZE + 0.5).floor] end |
.lng_lat_zl_to_quadkey(longitude, latitude, zl) ⇒ Object
Convert a lat/lng and zoom level into a quadkey
200 201 202 203 |
# File 'lib/wu/geo/geolocated.rb', line 200 def lng_lat_zl_to_quadkey(longitude, latitude, zl) tile_x, tile_y = lng_lat_zl_to_tile_xy(longitude, latitude, zl) tile_xy_zl_to_quadkey(tile_x, tile_y, zl) end |
.lng_lat_zl_to_tile_xy(longitude, latitude, zl) ⇒ Object
Convert latitude in degrees to integer tile x,y coordinates at given zoom level
130 131 132 |
# File 'lib/wu/geo/geolocated.rb', line 130 def lng_lat_zl_to_tile_xy(longitude, latitude, zl) [lng_zl_to_tile_xf(longitude, zl).floor, lat_zl_to_tile_yf(latitude, zl).floor] end |
.lng_zl_to_tile_xf(longitude, zl) ⇒ Object
Convert longitude in degrees to floating-point tile x,y coordinates at given zoom level
115 116 117 118 119 |
# File 'lib/wu/geo/geolocated.rb', line 115 def lng_zl_to_tile_xf(longitude, zl) raise ArgumentError, "longitude must be within bounds ((#{longitude}) vs #{ALLOWED_LONGITUDE})" unless (ALLOWED_LONGITUDE.include?(longitude)) xx = (longitude.to_f + 180.0) / 360.0 (map_tile_size(zl) * xx) end |
.map_pixel_size(zl) ⇒ Object
Width or height of grid bitmap in pixels at given zoom level
295 296 297 |
# File 'lib/wu/geo/geolocated.rb', line 295 def map_pixel_size(zl) TILE_PIXEL_SIZE * map_tile_size(zl) end |
.map_scale_for_dpi(latitude, zl, screen_dpi) ⇒ Object
Map scale at a specified latitude, zoom level, & screen resolution in dpi
306 307 308 |
# File 'lib/wu/geo/geolocated.rb', line 306 def map_scale_for_dpi(latitude, zl, screen_dpi) pixel_resolution(latitude, zl) * screen_dpi / 0.0254 end |
.map_tile_size(zl) ⇒ Object
Width or height in number of tiles
106 107 108 |
# File 'lib/wu/geo/geolocated.rb', line 106 def map_tile_size(zl) 1 << zl end |
.packed_qk_zl_to_tile_xy(packed_qk, zl = 16) ⇒ Object
Convert a packed quadkey (integer) into tile x,y coordinates and level
191 192 193 194 195 196 197 |
# File 'lib/wu/geo/geolocated.rb', line 191 def packed_qk_zl_to_tile_xy(packed_qk, zl=16) # don't "optimize" this without testing... string operations are faster than you think in ruby raise ArgumentError, "Quadkey must be an integer in range of the zoom level: #{packed_qk}, #{zl}" unless packed_qk.is_a?(Fixnum) && (packed_qk < 2 ** (zl*2)) quadkey_rhs = packed_qk.to_s(4) quadkey = ("0" * (zl - quadkey_rhs.length)) << quadkey_rhs quadkey_to_tile_xy_zl(quadkey) end |
.pixel_resolution(latitude, zl) ⇒ Object
Return pixel resolution in meters per pixel at a specified latitude and zoom level
300 301 302 303 |
# File 'lib/wu/geo/geolocated.rb', line 300 def pixel_resolution(latitude, zl) lat = latitude.clamp(MIN_LATITUDE, MAX_LATITUDE) Math.cos(lat.to_radians) * 2 * Math::PI * EARTH_RADIUS / map_pixel_size(zl).to_f end |
.pixel_xy_to_tile_xy(pixel_x, pixel_y) ⇒ Object
Convert from x,y pixel pair into tile x,y coordinates
311 312 313 |
# File 'lib/wu/geo/geolocated.rb', line 311 def pixel_xy_to_tile_xy(pixel_x, pixel_y) [pixel_x / TILE_PIXEL_SIZE, pixel_y / TILE_PIXEL_SIZE] end |
.pixel_xy_zl_to_lng_lat(pixel_x, pixel_y, zl) ⇒ Object
320 321 322 |
# File 'lib/wu/geo/geolocated.rb', line 320 def pixel_xy_zl_to_lng_lat(pixel_x, pixel_y, zl) tile_xy_zl_to_lng_lat(pixel_x.to_f / TILE_PIXEL_SIZE, pixel_y.to_f / TILE_PIXEL_SIZE, zl) end |
.point_east(longitude, latitude, distance) ⇒ Object
From a given point, calculate the change in degrees directly east a given distance
282 283 284 285 286 |
# File 'lib/wu/geo/geolocated.rb', line 282 def point_east(longitude, latitude, distance) radius = EARTH_RADIUS * Math.sin(((Math::PI / 2.0) - latitude.to_radians.abs)) east_lng = (longitude.to_radians + (distance.to_f / radius)).to_degrees [east_lng, latitude] end |
.point_north(longitude, latitude, distance) ⇒ Object
From a given point, calculate the point directly north a specified distance
276 277 278 279 |
# File 'lib/wu/geo/geolocated.rb', line 276 def point_north(longitude, latitude, distance) north_lat = (latitude.to_radians + (distance.to_f / EARTH_RADIUS)).to_degrees [longitude, north_lat] end |
.quadkey_containing_bbox(left, btm, right, top) ⇒ Object
Retuns the smallest quadkey containing both of corners of the given bounding box
219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/wu/geo/geolocated.rb', line 219 def quadkey_containing_bbox(left, btm, right, top) qk_tl = lng_lat_zl_to_quadkey(left, top, 23) qk_2 = lng_lat_zl_to_quadkey(right, btm, 23) # the containing qk is the longest one that both agree on containing_key = "" qk_tl.chars.zip(qk_2.chars).each do |char_tl, char_2| break if char_tl != char_2 containing_key << char_tl end containing_key end |
.quadkey_to_bbox(quadkey) ⇒ Object
Convert a quadkey into a bounding box using adjacent tile
210 211 212 213 214 215 216 |
# File 'lib/wu/geo/geolocated.rb', line 210 def quadkey_to_bbox(quadkey) tile_x, tile_y, zl = quadkey_to_tile_xy_zl(quadkey) # bottom right of me is top left of my southeast neighbor left, top = tile_xy_zl_to_lng_lat(tile_x, tile_y, zl) right, btm = tile_xy_zl_to_lng_lat(tile_x + 1, tile_y + 1, zl) [left, btm, right, top] end |
.quadkey_to_tile_xy_zl(quadkey) ⇒ Object
Convert a quadkey into tile x,y coordinates and level
170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/wu/geo/geolocated.rb', line 170 def quadkey_to_tile_xy_zl(quadkey) raise ArgumentError, "Quadkey must contain only the characters 0, 1, 2 or 3: #{quadkey}!" unless quadkey =~ /\A[0-3]*\z/ zl = quadkey.to_s.length tx = 0 ; ty = 0 quadkey.chars.each do |char| ybit, xbit = QUADKEY_TO_BIT[char] # bit order y, x tx = (tx << 1) + xbit ty = (ty << 1) + ybit end [tx, ty, zl] end |
.tile_xy_to_pixel_xy(tile_x, tile_y) ⇒ Object
Convert from x,y tile pair into pixel x,y coordinates (top left corner)
316 317 318 |
# File 'lib/wu/geo/geolocated.rb', line 316 def tile_xy_to_pixel_xy(tile_x, tile_y) [tile_x * TILE_PIXEL_SIZE, tile_y * TILE_PIXEL_SIZE] end |
.tile_xy_zl_to_lng_lat(tile_x, tile_y, zl) ⇒ Object
Convert from tile_x, tile_y, zoom level to longitude and latitude in degrees (slight loss of precision).
Tile coordinates may be floats or integer; they must lie within map range.
138 139 140 141 142 143 144 145 146 |
# File 'lib/wu/geo/geolocated.rb', line 138 def tile_xy_zl_to_lng_lat(tile_x, tile_y, zl) tile_size = map_tile_size(zl) raise ArgumentError, "tile index must be within bounds ((#{tile_x},#{tile_y}) vs #{tile_size})" unless ((0..(tile_size-1)).include?(tile_x)) && ((0..(tile_size-1)).include?(tile_x)) xx = (tile_x.to_f / tile_size) yy = 0.5 - (tile_y.to_f / tile_size) lng = 360.0 * xx - 180.0 lat = 90 - 360 * Math.atan(Math.exp(-yy * 2 * Math::PI)) / Math::PI [lng, lat] end |
.tile_xy_zl_to_packed_qk(tile_x, tile_y, zl) ⇒ Object
Convert from tile x,y into a packed quadkey at a specified zoom level
183 184 185 186 187 188 |
# File 'lib/wu/geo/geolocated.rb', line 183 def tile_xy_zl_to_packed_qk(tile_x, tile_y, zl) # don't optimize unless you're sure your way is faster; string ops are # faster than you think and loops are slower than you think quadkey_str = tile_xy_zl_to_quadkey(tile_x, tile_y, zl) quadkey_str.to_i(4) end |
.tile_xy_zl_to_quadkey(tile_x, tile_y, zl) ⇒ Object
Convert from tile x,y into a quadkey at a specified zoom level
158 159 160 161 162 163 164 165 166 167 |
# File 'lib/wu/geo/geolocated.rb', line 158 def tile_xy_zl_to_quadkey(tile_x, tile_y, zl) quadkey_chars = [] tx = tile_x.to_i ty = tile_y.to_i zl.times do quadkey_chars.push BIT_TO_QUADKEY[[ty.odd?, tx.odd?]] # bit order y,x tx >>= 1 ; ty >>= 1 end quadkey_chars.join.reverse end |