/* ---------------------------------------------------------------- */
/* Two classes to facilitate Google maps interaction:               */
/*                                                                  */
/*   GeoLoc - an immutable definition of a latitude/longitude pair  */
/*   GeoPath - a list of GeoLoc objects defining a route or path    */
/*                                                                  */
/* ---------------------------------------------------------------- */
/*                                                                  */
/* Originally by Ruurd J. Idenburg                                  */
/*                                                                  */
/* No copyright, no licence, no guarantees or warrantees, be it     */
/* explicit, implicit or whatever. Usage is totally and completely  */
/* at the users own risk, the author shall not be liable for any    */ 
/* damages whatsoever, for any reason whatsoever.                   */
/*                                                                  */
/* Please keep this comment block intact when modifying this code   */
/* and add a note with date and a description.                      */
/*                                                                  */
/* ---------------------------------------------------------------- */
/* 2013/12/01 - Initial version approximately                       */
/* 2020/02/15 - Corrected comments above                            */
/* ---------------------------------------------------------------- */

::class geoloc public
-- GeoLoc class, for now consisting of an immutable latitude
-- and longitude specification in decimal degrees.
--
-- The precision for calculations is a settable attribute, initially
-- set to the highest precision supported by the 'rxmath' library.
--

::attribute latitude get
::attribute longitude get
::attribute precision

-- (equatorial) radius of the Earth in meters as used by Google
::constant earthRadius 6378137

::method init
	expose latitude longitude precision
	use strict arg lat,lon
-- set precision to the maximum supported number of digits in the rxmath library
	precision = 16
-- create proper and valid latitude and longitude  
	latitude = .latitude~new(lat)
	longitude = .longitude~new(lon)

::method distanceFrom
-- Calculates the distance between the receiving and the argument (geo)location 
-- in thousands of Kilometers or Miles, as specified by the optional second argument. 
-- Default is the the metric system, specify 'M' for the imperial system.
--
	numeric digits (self~precision)
-- distance in Kilometers(K), is default, or Miles(M)
	use strict arg fromGeoLoc, unit='K'	
	if fromGeoLoc~class<>.geoloc then raise syntax 88.914 array("1-(geoloc)",self~class~id)
	unit = unit~subchar(1)~upper
-- distance will be calculated in thousands of the unit, be it metric or imperial
	if ('KM')~pos(unit)==0 then raise syntax 88.916 array("2-(unit)",'"K(m)","M(ile)"',unit)
	unit = '1000 1639'~word(('KM')~pos(unit)) 
-- I'm told that the best formula for short distances is: 
-- d=R*2*asin(sqrt((sin((lat1-lat2)/2))^2 + cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2)^2)))
	lat1 = self~latitude
	lat2 = fromGeoLoc~latitude
	lon1 = self~longitude
	lon2 = fromGeoLoc~longitude
	R = self~earthRadius
	dist = R*2*asin(sqrt((sin(lat1-lat2)/2)**2 + cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2)**2)),,'R')
--for a possibly future method implementation 
--R*2*(((lat1-lat2)/2)~sin**2 + lat1~cos*lat2~cos*(((lon1-lon2)/2)**2)~sqrt)~asin(,'R')
--
-- return distance in thousands of unit
	return (dist/unit)~format(,3) 

::method distanceTo
	forward message('distanceFrom') to(self)

::class geoPath public subclass list
-- GeoPath class, defined as a list of GeoLocs.
-- Class method 'of' in the .list class uses insert instance methods, no need to override.

::method insert
	use arg item
	if item~class<>.geoloc then do
		raise syntax 88.914 array(1,.geoloc~id)
	end
-- 2nd argument is index if specified
	if arg(2,'E') then do
		return self~insert:super(item,arg(2))
	end
-- no 2nd argument means append
	else do
		return self~insert:super(item)
	end
exit
	
::method append
	use strict arg item
	self~insert(item)
exit
	
::method put
-- same as insert with 2nd argument
	use strict arg item, index
	self~insert(item,index)
	
::method distance
	use strict arg unit='K'
	items = self~items
	distance = 0
	do l=0 to items-2
    distance += self[l]~distanceTo(self[l+1],unit)
	end
  return distance
	
::class number public subclass string
-- Number class, a subclass of the String class
-- Values can be any valid number
--
-- All arithmetic methods need to be defined here, because 
-- the result of the operation should be another object of 
-- the Number class, and the result of the String class operators
-- always is a String class object.
--
-- Note that subclasses of the Number class will yield results
-- that belong to that particular receiver subclass.

::method init
	self~init:super
	if (self)~datatype\='NUM' then do	--if self is not a number
		raise syntax 93.904 array(1,self) -- then raise an error and quit	
	end

::method '+' 
--use strict arg num
--return self~class~new(self~'+':super(num))
-- or:
	forward class(super) continue
	return self~class~new(result)
	
::method '-' 
	use strict arg num
	return self~class~new(self~'-':super(num))

::method '*' 
	use strict arg num
	return self~class~new(self~'*':super(num))

::method '/' 
	use strict arg num
	return self~class~new(self~'/':super(num))

::method '%' 
	use strict arg num
	return self~class~new(self~'%':super(num))

::method '//' 
	use strict arg num
	return self~class~new(self~'//':super(num))

::method '**' 
	use strict arg num
	return self~class~new(self~'**':super(num))

::class latitude public subclass number
-- Latitude class, a subclass of the Number class
-- Values range from -90 thru +90

::method init
-- the number class will check if I'm a valid number
	self~init:super	 
-- I check if I'm a valid latitude
	if self<-90 | self>90 then do 
		raise syntax 88.907 array("1-(latitude)","-90","90",self)
	end

::class longitude public subclass number
-- Longitude class, a subclass of the Number class
-- Values range from -180 thru +180

::method init
	self~init:super	
	if self<-180 | self>180 then do 
		raise syntax 88.907 array("1-(longitude)","-180","180",self)
	end

-- Simplify function names for the necessary 'RxMath' functions	
::routine Sin EXTERNAL "LIBRARY rxmath RxCalcSin"
::routine Cos EXTERNAL "LIBRARY rxmath RxCalcCos"
::routine Asin EXTERNAL "LIBRARY rxmath RxCalcArcSin"
::routine Sqrt EXTERNAL "LIBRARY rxmath RxCalcSqrt"

-- number.cls added above
--::requires "number.cls" 
