Class: Touchpass::Proximity::PRP

Inherits:
Object
  • Object
show all
Defined in:
lib/touchpass/prp.rb

Overview

PRP - Privacy Respecting Proximity Provides mechanisms that allows locations to be compared for proximity without revealing the actual location of either party.

Defined Under Namespace

Classes: BBoxResolution

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(params = {}) ⇒ PRP

Given a coordinate (latitude and longitude) and an Bounding Box resolution, return a PRP geohash



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/touchpass/prp.rb', line 36

def initialize(params={})
  resolution = params[:resolution]
  if resolution.nil? or resolution == ""
    resolution = 'LOCAL'
  end

  if params.has_key? :lat and params.has_key? :lng
    @lat, @lng = lat, lng
  elsif params[:address]
    addr = nil
    if params[:address].is_a? String
      addr = params[:address]
    elsif params[:address].is_a? Hash
      addr = build_address(params[:address])
    end

    coord = Geocoder.coordinates(addr) if addr
    #puts "addr = #{addr}"
    #puts "coord = #{coord.first}, #{coord.last}"
    return false if coord.nil? # Should we raise exception here?
    @lat, @lng = coord.first, coord.last
  end

  case resolution.upcase
  when 'STREET'
    @resolution = BBoxResolution::STREET
  when 'LOCAL'
    @resolution = BBoxResolution::LOCAL
  when 'METRO'
    @resolution = BBoxResolution::METRO
  when 'REGIONAL'
    @resolution = BBoxResolution::REGIONAL
  else
    raise Exception, "Unknown resolution '#{resolution}'."
  end

  # convert lat, lng to bigdecimal
  @lat, @lng = BigDecimal.new(@lat.to_s), BigDecimal.new(@lng.to_s)

  # calculate bounding coordinates
  calculate_bounding_coordinates!(@resolution)
end

Instance Attribute Details

#bounding_coordinatesObject (readonly)

Returns the value of attribute bounding_coordinates.



32
33
34
# File 'lib/touchpass/prp.rb', line 32

def bounding_coordinates
  @bounding_coordinates
end

#crypted_coordinatesObject (readonly)

Returns the value of attribute crypted_coordinates.



33
34
35
# File 'lib/touchpass/prp.rb', line 33

def crypted_coordinates
  @crypted_coordinates
end

#latObject (readonly)

Returns the value of attribute lat.



31
32
33
# File 'lib/touchpass/prp.rb', line 31

def lat
  @lat
end

#lngObject (readonly)

Returns the value of attribute lng.



31
32
33
# File 'lib/touchpass/prp.rb', line 31

def lng
  @lng
end

#resolutionObject (readonly)

Returns the value of attribute resolution.



31
32
33
# File 'lib/touchpass/prp.rb', line 31

def resolution
  @resolution
end

Class Method Details

.calibrate_lat(lat) ⇒ Object



199
200
201
202
203
204
205
206
207
208
# File 'lib/touchpass/prp.rb', line 199

def self.calibrate_lat(lat)
  if lat == 0.0
    # make sure -0.0 turned into 0.0
    lat = BigDecimal("0.0")
  elsif !(MIN_LAT..MAX_LAT).include?(lat)
    # latitude must fall within its range
    lat = (lat<MIN_LAT) ? MIN_LAT : MAX_LAT
  end
  return lat
end

.calibrate_lng(lng) ⇒ Object



210
211
212
213
214
215
216
217
218
219
# File 'lib/touchpass/prp.rb', line 210

def self.calibrate_lng(lng)
  if lng == 0.0
    # make sure -0.0 turned into 0.0
    lng = BigDecimal("0.0")
  elsif !(MIN_LNG..MAX_LNG).include?(lng)
    # longitude must fall within its range
    lng = (lng<MIN_LNG) ? MIN_LNG : MAX_LNG
  end
  return lng
end

.common_bounding_coordinates(prp, other_prp) ⇒ Object



225
226
227
228
229
230
# File 'lib/touchpass/prp.rb', line 225

def self.common_bounding_coordinates(prp, other_prp)
  return nil unless prp.bounding_coordinates
  bounding_coords_1 = prp.bounding_coordinates.collect{|k, v| v}
  bounding_coords_2 = other_prp.bounding_coordinates.collect{|k, v| v}
  return bounding_coords_1 & bounding_coords_2
end

.format_decimal(decimal, decimal_places) ⇒ Object

Truncate decimal (not round) E.g. format_decimal(-33.860987, 3) #=> -33.860 format_decimal(-33.860987, 2) #=> -33.86 format_decimal(-33.860987, 1) #=> -33.8 format_decimal(-33.860987, 0) #=> -33 format_decimal(151.200948, 3) #=> 151.200 format_decimal(151.200948, 2) #=> 151.20 format_decimal(151.200948, 1) #=> 151.2 format_decimal(151.200948, 0) #=> 151



194
195
196
197
# File 'lib/touchpass/prp.rb', line 194

def self.format_decimal(decimal, decimal_places)
  t = 100.0 ** decimal_places
  "%.#{decimal_places}f" % ((decimal * t).truncate / t)
end

.proximate?(prp, other_prp) ⇒ Boolean

Returns:

  • (Boolean)


221
222
223
# File 'lib/touchpass/prp.rb', line 221

def self.proximate?(prp, other_prp)
  return !Touchpass::Proximity::PRP.common_bounding_coordinates(prp, other_prp).empty?
end

.truncate_size(resolution) ⇒ Object



179
180
181
182
# File 'lib/touchpass/prp.rb', line 179

def self.truncate_size(resolution)
  split = resolution.to_s.split(".")
  return split.size==1 ? 0 : split.last.size
end

Instance Method Details

#bounding_boxObject

Returns the bounding coordinates



123
124
125
# File 'lib/touchpass/prp.rb', line 123

def bounding_box
  return @bounding_coordinates
end

#build_address(attributes) ⇒ Object

Converts addresss Hash to String “120 Sussex Street, Sydney 2000, NSW Australia”



245
246
247
248
249
250
251
252
# File 'lib/touchpass/prp.rb', line 245

def build_address(attributes)
  address = attributes[:address] + ","
  address << " #{attributes[:city]}" if attributes[:city].present?
  address << " #{attributes[:postcode]}" if attributes[:postcode].present?
  address << ", #{attributes[:state]}" if attributes[:state].present?
  address << " #{attributes[:country]}" if attributes[:country].present?
  address
end

#calculate_bounding_coordinates!(resolution) ⇒ Object

Calculates four coordinates that represent an anonymised version of the location. Anonymised means that regardless of the absolute value of lat and lng, the four coordinates will be a bounding box around the point. Returns an array of lat/lng coordinates that bound the coordinates. Notes: Size of the bounding box is determined by the granularity parameter



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/touchpass/prp.rb', line 85

def calculate_bounding_coordinates!(resolution)
  return @bounding_coordinates unless @bounding_coordinates.nil?

  trunc = Touchpass::Proximity::PRP.truncate_size(resolution)      
  granularity = BigDecimal.new(resolution.to_s)

  min_lat = @lat.truncate(trunc)
  min_lng = @lng.truncate(trunc)

  # Allow for different calcs depending on sign of lat and lng
  if @lat > 0.0
		max_lat = min_lat + granularity
	else
		max_lat = min_lat
		min_lat = max_lat - granularity
	end
	if @lng > 0.0
		max_lng = min_lng + granularity
	else
		max_lng = min_lng
		min_lng = max_lng - granularity
	end

  @bounding_coordinates = {
    :top_left     => [max_lat, min_lng],
    :top_right    => [max_lat, max_lng],
    :bottom_right => [min_lat, max_lng],
    :bottom_left  => [min_lat, min_lng]
  }

  # Calibrate lats and lngs
  @bounding_coordinates.collect{|k, coords| coords[0] = Touchpass::Proximity::PRP.calibrate_lat(coords[0])}
  @bounding_coordinates.collect{|k, coords| coords[1] = Touchpass::Proximity::PRP.calibrate_lng(coords[1])}

  return @bounding_coordinates
end

#calibrated?Boolean

Returns:

  • (Boolean)


127
128
129
130
131
132
133
# File 'lib/touchpass/prp.rb', line 127

def calibrated?
  return false if @bounding_coordinates.nil? or @bounding_coordinates.empty?
  for key in [:top_left, :top_right, :bottom_right, :bottom_left]
    return false if !@bounding_coordinates.has_key?(key)
  end
  return true
end

#common_bounding_coordinates(other_prp) ⇒ Object



141
142
143
# File 'lib/touchpass/prp.rb', line 141

def common_bounding_coordinates(other_prp)
  return Touchpass::Proximity::PRP.common_bounding_coordinates(self, other_prp)
end

#encrypt(salt = nil) ⇒ Object

Returns the hashed representation of the bounding coordinates



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/touchpass/prp.rb', line 146

def encrypt(salt=nil)
  if @bounding_coordinates
    crypted_1 = encrypt_point(@bounding_coordinates[:top_left], salt)
    crypted_2 = encrypt_point(@bounding_coordinates[:top_right], salt)
    crypted_3 = encrypt_point(@bounding_coordinates[:bottom_right], salt)
    crypted_4 = encrypt_point(@bounding_coordinates[:bottom_left], salt)
    @crypted_coordinates = crypted_1 + crypted_2 + crypted_3 + crypted_4
  end

  return @crypted_coordinates
end

#encrypt_point(point = [], salt = nil) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/touchpass/prp.rb', line 158

def encrypt_point(point=[], salt=nil)
  return if point.size!=2

  # Format lat, lng with correct decimal places
  # STREET  : 3 decimal places (e.g. -33.860, 151.200)
  # LOCAL   : 2 decimal places (e.g. -33.86, 151.20)
  # METRO   : 1 decimal places (e.g. -33.8, 151.2)
  # REGIONAL: 0 decimal places (e.g. -33, 151)
  lat = Touchpass::Proximity::PRP.format_decimal(point.first, Touchpass::Proximity::PRP.truncate_size(@resolution))
  lng = Touchpass::Proximity::PRP.format_decimal(point.last, Touchpass::Proximity::PRP.truncate_size(@resolution))

  # MFS: 20100722
  # Original approach hashed points in the corner independently. eg:
  # return crypto_provider.hexdigest(lat.to_s) + crypto_provider.hexdigest(lng.to_s)
  # Changed to string concat the points together *before* the hash so that we end up with hash values
  # that are coming from 3,240,000 values (ie 1,800*1,800) as opposed to 2 hashes derived from a set of 1,800
  # possible values.
  str_to_encrypt = (lat.to_s) + "," + (lng.to_s) + salt.to_s
  return Touchpass::Crypt.hash(str_to_encrypt)
end


232
233
234
235
236
237
238
239
240
241
# File 'lib/touchpass/prp.rb', line 232

def print_bbox
  for point in [:top_left, :top_right, :bottom_right, :bottom_left]
    lat = Touchpass::Proximity::PRP.format_decimal(self.bounding_coordinates[point].first, Touchpass::Proximity::PRP.truncate_size(@resolution))
    lng = Touchpass::Proximity::PRP.format_decimal(self.bounding_coordinates[point].last, Touchpass::Proximity::PRP.truncate_size(@resolution))
    # lat = "%.#{Touchpass::Proximity::PRP.truncate_size(@resolution)}f" % self.bounding_coordinates[point].first
    # lng = "%.#{Touchpass::Proximity::PRP.truncate_size(@resolution)}f" % self.bounding_coordinates[point].last
    puts "#{point.to_s}: [#{lat.to_s}, #{lng.to_s}]"
  end
  return true
end

#proximate?(other_prp) ⇒ Boolean

Test to see if the specified location is near this one, where ‘near’ means at least one of its bounding coordinates is shared between the two points

Returns:

  • (Boolean)


137
138
139
# File 'lib/touchpass/prp.rb', line 137

def proximate?(other_prp)
  return Touchpass::Proximity::PRP.proximate?(self, other_prp)
end