moduleGeocodermoduleCalculationsextendself### Compass point names, listed clockwise starting at North.## If you want bearings named using more, fewer, or different points# override Geocoder::Calculations.COMPASS_POINTS with your own array.#COMPASS_POINTS=%w[N NE E SE S SW W NW]### Radius of the Earth, in kilometers.# Value taken from: http://en.wikipedia.org/wiki/Earth_radius#EARTH_RADIUS=6371.0### Conversion factor: multiply by kilometers to get miles.#KM_IN_MI=0.621371192# Not a number constantNAN=defined?(::Float::NAN)?::Float::NAN:0/0.0### Distance spanned by one degree of latitude in the given units.#deflatitude_degree_distance(units=nil)units||=Geocoder::Configuration.units2*Math::PI*earth_radius(units)/360end### Distance spanned by one degree of longitude at the given latitude.# This ranges from around 69 miles at the equator to zero at the poles.#deflongitude_degree_distance(latitude,units=nil)units||=Geocoder::Configuration.unitslatitude_degree_distance(units)*Math.cos(to_radians(latitude))end### Distance between two points on Earth (Haversine formula).# Takes two points and an options hash.# The points are given in the same way that points are given to all# Geocoder methods that accept points as arguments. They can be:## * an array of coordinates ([lat,lon])# * a geocodable address (string)# * a geocoded object (one which implements a +to_coordinates+ method# which returns a [lat,lon] array## The options hash supports:## * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt># See Geocoder::Configuration to know how configure default units.#defdistance_between(point1,point2,options={})# set default optionsoptions[:units]||=Geocoder::Configuration.units# convert to coordinate arrayspoint1=extract_coordinates(point1)point2=extract_coordinates(point2)# convert degrees to radianspoint1=to_radians(point1)point2=to_radians(point2)# compute deltasdlat=point2[0]-point1[0]dlon=point2[1]-point1[1]a=(Math.sin(dlat/2))**2+Math.cos(point1[0])*(Math.sin(dlon/2))**2*Math.cos(point2[0])c=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a))c*earth_radius(options[:units])end### Bearing between two points on Earth.# Returns a number of degrees from due north (clockwise).## See Geocoder::Calculations.distance_between for# ways of specifying the points. Also accepts an options hash:## * <tt>:method</tt> - <tt>:linear</tt> or <tt>:spherical</tt>;# the spherical method is "correct" in that it returns the shortest path# (one along a great circle) but the linear method is less confusing# (returns due east or west when given two points with the same latitude).# See Geocoder::Configuration to know how configure default method.## Based on: http://www.movable-type.co.uk/scripts/latlong.html#defbearing_between(point1,point2,options={})# set default optionsoptions[:method]||=Geocoder::Configuration.distancesoptions[:method]=:linearunlessoptions[:method]==:spherical# convert to coordinate arrayspoint1=extract_coordinates(point1)point2=extract_coordinates(point2)# convert degrees to radianspoint1=to_radians(point1)point2=to_radians(point2)# compute deltasdlat=point2[0]-point1[0]dlon=point2[1]-point1[1]caseoptions[:method]when:lineary=dlonx=dlatwhen:sphericaly=Math.sin(dlon)*Math.cos(point2[0])x=Math.cos(point1[0])*Math.sin(point2[0])-Math.sin(point1[0])*Math.cos(point2[0])*Math.cos(dlon)endbearing=Math.atan2(x,y)# Answer is in radians counterclockwise from due east.# Convert to degrees clockwise from due north:(90-to_degrees(bearing)+360)%360end### Translate a bearing (float) into a compass direction (string, eg "North").#defcompass_point(bearing,points=COMPASS_POINTS)seg_size=360/points.sizepoints[((bearing+(seg_size/2))%360)/seg_size]end### Compute the geographic center (aka geographic midpoint, center of# gravity) for an array of geocoded objects and/or [lat,lon] arrays# (can be mixed). Any objects missing coordinates are ignored. Follows# the procedure documented at http://www.geomidpoint.com/calculation.html.#defgeographic_center(points)# convert objects to [lat,lon] arrays and convert degrees to radianscoords=points.map{|p|to_radians(extract_coordinates(p))}# convert to Cartesian coordinatesx=[];y=[];z=[]coords.eachdo|p|x<<Math.cos(p[0])*Math.cos(p[1])y<<Math.cos(p[0])*Math.sin(p[1])z<<Math.sin(p[0])end# compute average coordinate valuesxa,ya,za=[x,y,z].mapdo|c|c.inject(0){|tot,i|tot+=i}/c.size.to_fend# convert back to latitude/longitudelon=Math.atan2(ya,xa)hyp=Math.sqrt(xa**2+ya**2)lat=Math.atan2(za,hyp)# return answer in degreesto_degrees[lat,lon]end### Returns coordinates of the lower-left and upper-right corners of a box# with the given point at its center. The radius is the shortest distance# from the center point to any side of the box (the length of each side# is twice the radius).## This is useful for finding corner points of a map viewport, or for# roughly limiting the possible solutions in a geo-spatial search# (ActiveRecord queries use it thusly).## See Geocoder::Calculations.distance_between for# ways of specifying the point. Also accepts an options hash:## * <tt>:units</tt> - <tt>:mi</tt> or <tt>:km</tt>.# See Geocoder::Configuration to know how configure default units.#defbounding_box(point,radius,options={})lat,lon=extract_coordinates(point)radius=radius.to_funits=options[:units]||Geocoder::Configuration.units[lat-(radius/latitude_degree_distance(units)),lon-(radius/longitude_degree_distance(lat,units)),lat+(radius/latitude_degree_distance(units)),lon+(radius/longitude_degree_distance(lat,units))]end### Convert degrees to radians.# If an array (or multiple arguments) is passed,# converts each value and returns array.#defto_radians(*args)args=args.firstifargs.first.is_a?(Array)ifargs.size==1args.first*(Math::PI/180)elseargs.map{|i|to_radians(i)}endend### Convert radians to degrees.# If an array (or multiple arguments) is passed,# converts each value and returns array.#defto_degrees(*args)args=args.firstifargs.first.is_a?(Array)ifargs.size==1(args.first*180.0)/Math::PIelseargs.map{|i|to_degrees(i)}endenddefdistance_to_radians(distance,units=nil)units||=Geocoder::Configuration.unitsdistance.to_f/earth_radius(units)enddefradians_to_distance(radians,units=nil)units||=Geocoder::Configuration.unitsradians*earth_radius(units)end### Convert miles to kilometers.#defto_kilometers(mi)mi*mi_in_kmend### Convert kilometers to miles.#defto_miles(km)km*km_in_miend### Radius of the Earth in the given units (:mi or :km).# See Geocoder::Configuration to know how configure default units.#defearth_radius(units=nil)units||=Geocoder::Configuration.unitsunits==:km?EARTH_RADIUS:to_miles(EARTH_RADIUS)end### Conversion factor: km to mi.#defkm_in_miKM_IN_MIend### Conversion factor: mi to km.#defmi_in_km1.0/KM_IN_MIend### Takes an object which is a [lat,lon] array, a geocodable string,# or an object that implements +to_coordinates+ and returns a# [lat,lon] array. Note that if a string is passed this may be a slow-# running method and may return nil.#defextract_coordinates(point)casepointwhenArrayifpoint.size==2lat,lon=pointif!lat.nil?&&lat.respond_to?(:to_f)and!lon.nil?&&lon.respond_to?(:to_f)thenreturn[lat.to_f,lon.to_f]endendwhenStringpoint=Geocoder.coordinates(point)andreturnpointelseifpoint.respond_to?(:to_coordinates)ifArray===array=point.to_coordinatesreturnextract_coordinates(array)endendend[NAN,NAN]endendend