Skip to content

Commit

Permalink
Changing order to longitude, latitude to reflect geojson.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Apr 5, 2016
1 parent cf5ce7b commit b5e8937
Show file tree
Hide file tree
Showing 10 changed files with 250 additions and 18 deletions.
2 changes: 1 addition & 1 deletion geospatial.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"]

spec.add_development_dependency "rspec", "~> 3.1.0"
spec.add_development_dependency "rspec", "~> 3.4.0"

spec.add_development_dependency "bundler", "~> 1.6"
spec.add_development_dependency "rake"
Expand Down
30 changes: 30 additions & 0 deletions lib/geospatial/aligned_box.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,41 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'matrix'

module Geospatial
class AlignedBox
def initialize(origin, size)
@origin = origin
@size = size
end

def dimensions
@origin.size
end

def min
@origin
end

def max
@origin + @size
end

def contains_point(point)
point >= min and point < max
end

def overlaps?(other, includes_edges)
contains_point()

dimensions.times do |i|
if self.max[i] < other.min[i] or other.max[i] < self.min[i]
return false
end
end

return true
end
end
end
30 changes: 15 additions & 15 deletions lib/geospatial/location.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,32 @@ class Location
R2D = (180.0 / Math::PI)
D2R = (Math::PI / 180.0)

MIN_LATITUDE = -90.0 * D2R
MAX_LATITUDE = 90 * D2R
VALID_LATITUDE = MIN_LATITUDE...MAX_LATITUDE

MIN_LONGITUDE = -180 * D2R
MAX_LONGITUDE = 180 * D2R
VALID_LONGITUDE = MIN_LONGITUDE...MAX_LONGITUDE

def initialize(latitude, longitude, altitude = 0)

MIN_LATITUDE = -90.0 * D2R
MAX_LATITUDE = 90 * D2R
VALID_LATITUDE = MIN_LATITUDE...MAX_LATITUDE

def initialize(longitude, latitude, altitude = 0)
@latitude = latitude
@longitude = longitude
@altitude = altitude
end

def valid?
VALID_LATITUDE.include? latitude and VALID_LONGITUDE.include? longitude
VALID_LONGITUDE.include?(longitude) and VALID_LATITUDE.include?(latitude)
end

def to_s
"#<Location latitude=#{@latitude} longitude=#{@longitude.to_f} altitude=#{@altitude.to_f}>"
"#<Location longitude=#{@longitude.to_f} latitude=#{@latitude} altitude=#{@altitude.to_f}>"
end

alias inspect to_s

attr :latitude
attr :longitude
attr :longitude # -180 -> 180 (equivalent to x)
attr :latitude # -90 -> 90 (equivalent to y)
attr :altitude

# http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
Expand Down Expand Up @@ -98,11 +98,11 @@ def bounding_box(distance, radius = EARTH_RADIUS)

# Converts latitude, longitude to ECEF coordinate system
def to_ecef(alt)
clat = Math::cos(lat * D2R)
slat = Math::sin(lat * D2R)
clon = Math::cos(lon * D2R)
slon = Math::sin(lon * D2R)

clat = Math::cos(lat * D2R)
slat = Math::sin(lat * D2R)

n = WGS84_A / Math::sqrt(1.0 - WGS84_E * WGS84_E * slat * slat)

x = (n + alt) * clat * clon
Expand Down Expand Up @@ -135,10 +135,10 @@ def self.from_ecef(x, y, z)
# calculate distance in metres between us and something else
# ref: http://codingandweb.blogspot.co.nz/2012/04/calculating-distance-between-two-points.html
def distance_from(other_position)
rlat1 = self.latitude * D2R
rlong1 = self.longitude * D2R
rlat2 = other_position.latitude * D2R
rlat1 = self.latitude * D2R
rlong2 = other_position.longitude * D2R
rlat2 = other_position.latitude * D2R

dlon = rlong1 - rlong2
dlat = rlat1 - rlat2
Expand Down
68 changes: 68 additions & 0 deletions lib/geospatial/map.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative 'location'
require_relative 'aligned_box'

module Geospatial
class Map
# Will use 60 bits to store hash:
ORDER = 30

class Point
def initialize(location)
@location = location
end

attr :location

def hash
@hash ||= Hilbert.hash(@location.latitude, @location.longitude, ORDER).freeze
end
end

EARTH_BOUNDS = AlignedBox.new(Vector[-180, -90], Vector[180, 90]).freeze

def initialize(bounds = EARTH_BOUNDS)
@bounds = bounds

@points = []
end

def << point
@points << point
end

def sort!
@points.sort_by(&:hash)
end

def query(bounding_box)
ranges = []

Hilbert.traverse(ORDER, origin: @bounds.origin, size: @bounds.size) do |child_origin, child_size, prefix, order|
child = AlignedBox.new(child_origin, child_size)

if bounding_box.overlaps(child)
end
end
end
end
end
3 changes: 3 additions & 0 deletions lib/geospatial/sphere.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'matrix'

module Geospatial
class Sphere
# Center must be a vector, radius must be a numeric value.
def initialize(center, radius)
@center = center
@radius = radius
Expand Down
40 changes: 40 additions & 0 deletions lib/geospatial/tree.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright, 2016, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require_relative 'location'


module Geospatial
class Map
EARTH_BOUNDS = AlignedBox.new(Vector[-180, -90], Vector[180, 90]).freeze

def self.for_earth
self.new(EARTH_BOUNDS)
end

def initialize(bounds)
@bounds = bounds
end

def << object

end
end
end
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
module Geospatial::LocationSpec
describe Geospatial::Location do
it "compute the correct distance between two points" do
lake_tekapo = Geospatial::Location.new(-43.883, 170.516)
lake_alex = Geospatial::Location.new(-43.95, 170.45)
lake_tekapo = Geospatial::Location.new(170.516, -43.883)
lake_alex = Geospatial::Location.new(170.45, -43.95)

expect(lake_alex.distance_from(lake_tekapo)).to be_within(10).of(9_130)
end
Expand Down
91 changes: 91 additions & 0 deletions spec/geospatial/map_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

require 'geospatial/map'

module Geospatial::MapSpec
describe Geospatial::Map do
it "base case should be identity" do
# The base case for our coordinate system:
expect(Geospatial::Hilbert.rotate(0, 0)).to be == 0
expect(Geospatial::Hilbert.rotate(0, 1)).to be == 1
expect(Geospatial::Hilbert.rotate(0, 2)).to be == 2
expect(Geospatial::Hilbert.rotate(0, 3)).to be == 3
end

it "rotation is self-inverting" do
4.times do |rotation|
4.times do |quadrant|
# rotate(rotation, rotate(rotation, quadrant)) == quadrant
rotated = Geospatial::Hilbert.rotate(rotation, quadrant)
expect(Geospatial::Hilbert.rotate(rotation, rotated)).to be == quadrant
end
end
end

it "computes the correct hash of order=0" do
expect(Geospatial::Hilbert.hash(0, 0, 0)).to be == 0
expect(Geospatial::Hilbert.hash(1, 0, 0)).to be == 1
expect(Geospatial::Hilbert.hash(1, 1, 0)).to be == 2
expect(Geospatial::Hilbert.hash(0, 1, 0)).to be == 3
end

it "computes the correct hash of order=1" do
expect(Geospatial::Hilbert.hash(0, 0, 1)).to be == 0
expect(Geospatial::Hilbert.hash(1, 0, 1)).to be == 1
expect(Geospatial::Hilbert.hash(1, 1, 1)).to be == 2
expect(Geospatial::Hilbert.hash(0, 1, 1)).to be == 3

expect(Geospatial::Hilbert.hash(0, 2, 1)).to be == 4
expect(Geospatial::Hilbert.hash(0, 3, 1)).to be == 5
expect(Geospatial::Hilbert.hash(1, 3, 1)).to be == 6
expect(Geospatial::Hilbert.hash(1, 2, 1)).to be == 7

expect(Geospatial::Hilbert.hash(2, 2, 1)).to be == 8
expect(Geospatial::Hilbert.hash(2, 3, 1)).to be == 9
expect(Geospatial::Hilbert.hash(3, 3, 1)).to be == 10
expect(Geospatial::Hilbert.hash(3, 2, 1)).to be == 11

expect(Geospatial::Hilbert.hash(3, 1, 1)).to be == 12
expect(Geospatial::Hilbert.hash(2, 1, 1)).to be == 13
expect(Geospatial::Hilbert.hash(2, 0, 1)).to be == 14
expect(Geospatial::Hilbert.hash(3, 0, 1)).to be == 15
end

it "computes the correct unhash" do
expect(Geospatial::Hilbert.unhash(12)).to be == [3, 1]
end

it "traverses and generates all prefixes" do
items = Geospatial::Hilbert.traverse(1).to_a

# 4 items of order 1, 16 items of order 0.
expect(items.size).to be == (4 + 16)

expect(items[0]).to be == [[0, 0], [0.5, 0.5], 0b0000, 1]
expect(items[5]).to be == [[0.0, 0.5], [0.5, 0.5], 0b0100, 1]
expect(items[10]).to be == [[0.5, 0.5], [0.5, 0.5], 0b1000, 1]
expect(items[15]).to be == [[0.5, 0], [0.5, 0.5], 0b1100, 1]

prefixes = items.collect{|item| item[2]}
expect(prefixes).to be_sorted
end
end
end
File renamed without changes.

0 comments on commit b5e8937

Please sign in to comment.