;+ 
;NAME:
;
;  fancompress
;
;PURPOSE:
;  Decimates polylines in an aesthetically pleasing fashion.
;
;CALLING SEQUENCE:
;  outidx = fancompress(inpts,err)
;
;INPUT:
;  inpts: N x 2 dimension array, where inpts[*,0] are the x components of the polyline and inpts[*,1] are the y components of the polyline
;  err: The amount of error allowed before including a point 
; 
;Keywords:
;  vector:  Will enable the vectorized fan compression algorithm.  When completed.
;
;OUTPUT:
;  An array of indexes into inpts.  Indices will range from 0 to N-1.  First and Last points are always included.
;
;NOTES:
;  1. Based almost entirely on the paper: 
;  Fowell, Richard A. and McNeil, David D. , “Faster Plots by Fan Data-Compression,” 
;  IEEE Computer Graphics & Applications, Vol. 9, No. 2,Mar. 1989, pp. 58-66.
;  
;  2. One modification from published algorithm, handles NaNs by always including the point
;  before a group of NaNs, 1 NaN and the point after the NaNs.  This ensures that gaps will
;  be drawn accurately.
;  
;  3. Algorithm is fairly slow, because it requires 1 pass over all data points.
;  Optimizing this algorithm by divide and conquer, vectorization, or dlm may be
;  a worthwhile use of time in the future. 
;
;$LastChangedBy: pcruce $
;$LastChangedDate: 2009-06-17 13:27:52 -0700 (Wed, 17 Jun 2009) $
;$LastChangedRevision: 6239 $
;$URL: svn+ssh://thmsvn@ambrosia.ssl.berkeley.edu/repos/ssl_general/trunk/misc/fancompress.pro $
;-----------------------------------------------------------------------------------


pro fn_save_pt,n_out,outpts,o,k,i,n_in

  compile_opt idl2,hidden

  if n_out eq 0 then begin
    outpts = i
  endif else begin
    outpts = [outpts,i]
  endelse 

  n_out++
  o = i
  k = n_in

end

function fn_dot_p,u,v

  compile_opt idl2,hidden
  
  return,total(u*v)
  
end

function fn_norm,v

  compile_opt idl2,hidden

  return,sqrt(fn_dot_p(v,v))

end

function fancompress_iter,inpts,err

  compile_opt idl2,hidden
  
  n_in = (dimen(inpts))[0]
  n_out = 0
  keep = 1
  
  i = 1
  
  fn_save_pt,n_out,outpts,o,k,i,n_in
  
  if n_in eq 1 then begin
    return,lindgen(n_in)
  endif
  
  error = max([0,err])

  while i lt n_in - 1 do begin
    i++
    ;properly mark NaNs in data, this addition is currently the only significant
    ;modification from the published algorithm
    if ~finite(inpts[i-1,0]) || ~finite(inpts[i-1,1]) then begin
      if keep eq 0 then begin
        outpts = [outpts,i-1]
        n_out++
      endif
      outpts = [outpts,i]
      n_out++
      for k = i,n_in-1 do begin
        if finite(inpts[k-1,0]) && finite(inpts[k-1,1]) then begin
          break
        endif
      endfor
      i = k
      if k eq n_in-1 then begin
        fn_save_pt,n_out,outpts,o,k,i,n_in
        break
      endif else if k eq n_in then begin
        break
      endif
      fn_save_pt,n_out,outpts,o,k,i,n_in
    endif
    
    if i lt k then begin
      distance = fn_norm(inpts[i-1,*] - inpts[o-1,*])
      if distance gt error then begin
        k = i
        p_i_u = distance
        u_hat = (inpts[i-1,*] - inpts[o-1,*]) / p_i_u
        v_hat = [-u_hat[1],u_hat[0]]
        f = [p_i_u,error/p_i_u,-error/p_i_u]
      endif
    endif
    if i ge k then begin
      keep = 1
      p_i1_u = fn_dot_p((inpts[(i-1)+1,*] - inpts[(o-1),*]),u_hat)
      p_i1_v = fn_dot_p((inpts[(i-1)+1,*] - inpts[(o-1),*]),v_hat)
      if p_i1_u ge f[0] then begin
        m_i = p_i1_v/p_i1_u
        if m_i le f[1] && m_i ge f[2] then begin
          delta_m_i = error / p_i1_u
          keep = 0
          f[0] = p_i1_u
          f[1] = min([f[1],m_i+delta_m_i])
          f[2] = max([f[2],m_i-delta_m_i])
        endif
      endif
      if keep then begin
        fn_save_pt,n_out,outpts,o,k,i,n_in
      endif
    endif
  endwhile
  
  outpts = [outpts,n_in]
  
  return,outpts-1

end

function fancompress_vector,inpts,err 

  compile_opt idl2,hidden

  n_in = (dimen(inpts))[0]
  n_out = 0
  
  outpts = lindgen(n_in)
  
  if n_in eq 1 then begin
    return,outpts
  endif
  
  error = max([0,err])
  
  repeat begin
   
    current_pts = n_elements(outpts)
  
  endrep until current_pts eq n_elements(outpts)

  return, outpts

end

function fancompress,inpts,err,vector=vector

  compile_opt idl2
  
  if ~keyword_set(vector) then begin
    return,fancompress_iter(inpts,err)
  endif else begin
    return,fancompress_vector(inpts,err)
  endelse

end