#Metview Macro

# **************************** LICENSE START ***********************************
#
# Copyright 2019 ECMWF. This software is distributed under the terms
# of the Apache License version 2.0. In applying this license, ECMWF does not
# waive the privileges and immunities granted to it by virtue of its status as
# an Intergovernmental Organization or submit itself to any jurisdiction.
#
# ***************************** LICENSE END ************************************

# **************************************************************************
# Function      : mvl_geocircle
#
# Syntax        : definition mvl_geocircle (lat : number, lon : number,
#                                          radius, resolution : number)
#
# Category      : VISUAL
#
# OneLineDesc   : Returns a curve marking the circle with the given radius around the given centre
#
# Description   : Returns a plot definintion for drawing a circle with the given radius
#                 specified in km around the given centre. This circle is projection-independent.
#
# Parameters    : lat,lon - coordinates of the centre of the circle
#                 radius  - the radius of the circle in km.
#                           If a list is given here the circle is split into segments
#                           and each segment is rendered with the given radius. The firts segment 
#                           always starts at North and other segments follow in East-South-West direction.                          
#                 resolution - the number of points used to make up the circle 
#                           
# Return Value  : an Input Visualiser that may be used in a plot() command
#
# Example Usage : 
#
#                   lat  = 51
#                   lon  = -1                  
#                   radius = 100
#                   resolution = 100
#
#                   my_circle = mvl_geocircle(lat,lon,radius,resolution)
#
#                   plot ( display_window, data, my_circle, mgraph())
#
# **************************************************************************

function mvl_geocircle (_lat : number, _lon : number,
                      _radius , _resolution : number)
                 
    _resolution = int(_resolution)
    
    if _radius =  nil then
        fail("radius is nil!")
    end if
    
    _radiusLst = _radius
    if type(_radiusLst) = "number" then
       _radiusLst = nil
    end if    
            
    if count(_radiusLst) = 1 then   
       _radius = _radiusLst[1]
       _radiusLst = nil
    end if
        
    #Some checks
    
    if _lon > 180 then
        _lon = _lon - 360
    end if   
    
    if _lon < -180. or _lon > 180. then
        fail("lon=",_lon," is out of range!")
    end if
    
    if _lat < -90. or _lat > 90. then
        fail("lat=",_lat," is out of range!")
    end if        
    
    if count(_radiusLst) > 0 then
        for i=1 to count(_radiusLst) do
           if _radiusLst[i] <=0 or _radiusLst[i] > 45000 then
            fail("radius=",_radiusLst[i]," is out of range!")
           end if 
        end for     
    else if _radius <=0 or _radius > 45000 then
        fail("radius=",_radius," is out of range!")
    end if   
    
    if _resolution <=6 then
        fail("resolution=",_resolution," must be > 6 !")
    end if   
    
    #Define contants  
    PI=acos(-1)
     
    _latOut = nil
    _lonOut = nil 
      
    #-------------------------------------
    # Segments
    #-------------------------------------
    if count(_radiusLst) > 0 then
        
        if int(_resolution / count(_radiusLst)) < 10 then
            _resolution = count(_radiusLst)*11
        end if
           
        _res = _sample_geocircle_segment(_lat, _lon, _radiusLst, _resolution)
        
        _latOut = _res[1]
        _lonOut = _res[2]
        
        # we need to close the polygon
        _latOut = _latOut & [_latOut[1]]
        _lonOut = _lonOut & [_lonOut[1]]
        
    #-------------------------------------
    # Full circle
    #-------------------------------------  
    else if count(_radiusLst) = 0 then
       
        if _resolution > 500 then
            _resolution=500
        end if    
        
        if _resolution < 10 then
            _resolution=10
        end if     
            
        _res = _sample_geocircle(_lat, _lon, _radius, _resolution)   
        _latOut = _latOut & _res[1]
        _lonOut = _lonOut & _res[2]           
    end if
    
    # set up a curve with a suitable vis def
    _geocircle_curve = input_visualiser
    (
        input_plot_type        :    "geo_points",
        input_longitude_values :    _lonOut,
        input_latitude_values  :    _latOut
     )

	return _geocircle_curve


end  mvl_geocircle

function _sample_geocircle(_lat, _lon, _radius, _resolution)

     #First we compute the circle coordinates as if the north pole
     #was the centre then rotate the pole to the real centre

    _REarth=6371 #km, Radius of Earth
    PI=acos(-1)
      
    #Distance from the pole in radians
    _radDist=_radius/_REarth
    
    #Define some rotation parameters
    _theta=(PI/2-PI*_lat/180.)
    _cosTheta=cos(_theta)
    _sinTheta=sin(_theta)
    
    #The resulting lists
    _latOut=nil
    _lonOut=nil

    #Loop for the circle points
    for i=0 to _resolution do
        
        #Angle east from Greenwich in radians            
        _lonVal=i*2*PI/_resolution
        
        #Angle from the north pole in radians
        _latVal=PI/2-_radDist
           
        #print("------ " & i & " latVal=",_latVal*180/PI," lonVal=",_lonVal*180/PI)
           
        #transform to xyz
        x = cos(_lonVal)*cos(_latVal)
        y = sin(_lonVal)*cos(_latVal)
        z = sin(_latVal)  
         
        #print("x=",x," y=",y," z=",z)   
         
        #Rotate around the y axis by theta angle  
        xr = _cosTheta*x + _sinTheta*z
        yr = y
        zr = -_sinTheta*x + _cosTheta*z  
        
        #print("xr=",xr," yr=",yr," zr=",zr)
        
        #Back to lat-lon (in degrees)
        _latVal = asin(zr)*180./PI       
        _lonVal = mvl_atan2(xr,yr)*180./PI
      
        #print("latVal=",latVal," lonVal=",lonVal)
      
        #Rotate the lon  
        _lonVal=_lonVal+_lon
         
        #print("latVal=",latVal," lonVal=",lonVal)      
        _lonOut=_lonOut & [_lonVal]
        _latOut=_latOut & [_latVal]
        
    end for
    
    return [_latOut, _lonOut]
    
end  _sample_geocircle   

function _sample_geocircle_segment(_lat, _lon, _radiusLst, _resolution)

    #First we compute the circle coordinates as if the north pole
    #was the centre then rotate the pole to the real centre

    _REarth=6371 #km, Radius of Earth
    PI=acos(-1)
    
    #Define some rotation parameters
    _theta=(PI/2-PI*_lat/180.)
    _cosTheta=cos(_theta)
    _sinTheta=sin(_theta)
    
    #The resulting lists
    _latOut=nil
    _lonOut=nil

    # This is in radians
    _startAngle = PI # +180 E

    #mark segments ends at East and West
    _segNum = count(_radiusLst)  
    _eastId = -1
    _westId = -1 
    if mod(_segNum,4) = 0 then
        _eastId = _segNum/4
        _westId = 3*(_segNum/4)
    end if
    
    _segSize = int(_resolution/_segNum) + 1
    _resolution =  _segSize * _segNum
    _dAngle = 2*PI/_resolution
    _resolution = _segSize # number of points in a segment
   
    #print("_resolution: ", _resolution)
    #print("_dAngle: ", _dAngle*180/PI, " degrees")

    for _iSeg=1 to _segNum do

        _startAngle = PI - (_iSeg-1)*_dAngle*_resolution
        _radDist = _radiusLst[_iSeg]/_REarth
      
        #print("__startAngle: ", _startAngle*180/PI, " degrees")
      
        _extraBefore = 0
        _extraAfter = 0
        _eastPos = nil
        _westPos = nil
        _radiusEWInDeg = 360 * _radiusLst[_iSeg] / (2*PI*_REarth * cos(_lat*PI/180))
        _eastPos = [_lat, _lon + _radiusEWInDeg]
        _westPos = [_lat, _lon - _radiusEWInDeg]
        _reachedEast = 0
        _reachedWest = 0
        
        if _iSeg = _eastId then
            _extraBefore = 0
            _extraAfter = 5               
        else if _iSeg = _eastId+1 then
            _extraBefore = 5
            _extraAfter = 0          
        else if _iSeg = _westId then
            _extraBefore = 0
            _extraAfter = 5
        else if _iSeg = _westId+1 then
            _extraBefore = 5
            _extraAfter = 0
        end if
        
        #Loop for the circle points
        for _i=-_extraBefore to _resolution+_extraAfter do
             
            _addPoint = 1 
             
            #Angle east from Greenwich in radians            
            _lonVal=_startAngle - _i*_dAngle
        
            #Angle from the north pole in radians
            _latVal=PI/2-_radDist
           
            #print("------ " & _i & " latVal=",_latVal*180/PI," lonVal=",_lonVal*180/PI)
           
            #transform to xyz
            x = cos(_lonVal)*cos(_latVal)
            y = sin(_lonVal)*cos(_latVal)
            z = sin(_latVal)  
         
            #print("x=",x," y=",y," z=",z)   
         
            #Rotate around the y axis by theta angle  
            xr = _cosTheta*x + _sinTheta*z
            yr = y
            zr = -_sinTheta*x + _cosTheta*z  
        
            #print("xr=",xr," yr=",yr," zr=",zr)
        
            #Back to lat-lon (in degrees)
            _latVal = asin(zr)*180./PI       
            _lonVal = mvl_atan2(xr,yr)*180./PI
      
            #print("latVal=",latVal," lonVal=",lonVal)
      
            #Rotate the lon  
            _lonVal=_lonVal+_lon
         
            # segment ending at East
            if _iSeg = _eastId then
                if not _reachedEast and _latVal <= _lat then                
                    _latVal = _eastPos[1]
                    _lonVal = _eastPos[2]
                    _lonOut=_lonOut & [_lonVal]
                    _latOut=_latOut & [_latVal]
                    #print("    latVal=",_latVal," lonVal=",_lonVal) 
                    _reachedEast = 1
                end if
                
                #print("   _reachedEast:", _reachedEast)                
                if _reachedEast then
                    _addPoint = 0
                end if
                
            # segment starting at East        
            else if _iSeg = _eastId+1 then
                if not _reachedEast and _latVal <= _lat then                                   
                    _lonOut=_lonOut & [_eastPos[2]]
                    _latOut=_latOut & [_eastPos[1]]                 
                    #print("    latVal=",_eastPos[1]," lonVal=",_eastPos[2]) 
                    _reachedEast = 1  
                end if                 
                
                #print("   _reachedEast:", _reachedEast)                
                if not _reachedEast then
                    _addPoint = 0
                end if
              
            end if
           
            # segment ending at West
            if _iSeg = _westId then
                if not _reachedWest and _latVal >= _lat then                
                    _latVal = _westPos[1]
                    _lonVal = _westPos[2]
                    _lonOut=_lonOut & [_lonVal]
                    _latOut=_latOut & [_latVal]
                    #print("    latVal=",_latVal," lonVal=",_lonVal) 
                    _reachedWest = 1
                end if
                
                #print("   _reachedWest:", _reachedWest)  
                if _reachedWest then
                    _addPoint = 0
                end if           
            
            # segment ending at West        
            else if _iSeg = _westId+1 then
                if not _reachedWest and _latVal >= _lat then                                    
                    _lonOut=_lonOut & [_westPos[2]]
                    _latOut=_latOut & [_westPos[1]]
                    #print("    latVal=",_westPos[1]," lonVal=",_westPos[2]) 
                    _reachedWest = 1  
                end if 
                
                #print("   _reachedWest:", _reachedWest) 
                if not _reachedWest then
                    _addPoint = 0
                end if   
            
            end if
                   
            if _addPoint = 1 then                         
                _lonOut=_lonOut & [_lonVal]
                _latOut=_latOut & [_latVal]
                #print("    latVal=",_latVal," lonVal=",_lonVal) 
            end if    
        
        end for
    end for
    
    return [_latOut, _lonOut]
    
end  _sample_geocircle_segment   


function mvl_atan2(x,y)

    PI=acos(-1)
   
    if y = 0 then
        if x <= 0 then
            return -PI
        else
            return 0    
        end if
    end if

    if x = 0 then
        if y >= 0 then
            return PI/2.
        else
            return -PI/2.
        end if
    end if   
      
    v=atan(y/x)                  
    if x < 0 and y > 0 then
        return v+PI
    else if x < 0 and y < 0 then
        return v-PI
    else 
        return v      
    end if
     
    if x = 0 then
        if y >=0 then
            return PI/2.
        else
            return -PI/2.
        end if
    end if    
                    
    return 0             
            
end mvl_atan2