User:Jorge Stolfi/make-coord-system-figure

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
#! /usr/bin/python -t 
# _*_ coding: iso-8859-1 _*_
# Last edited on 2009-05-03 18:12:55 by stolfilocal

PROG_NAME = "make-coord-system-figure"
PROG_DESC = "Generates an SVG illustration for the Wikipedia articles on coord systems"
PROG_VERS = "1.0"

import sys
import re
import os
import copy
import math
from math import sqrt,sin,cos
sys.path[1:0] = [ sys.path[0] + '/../lib', os.path.expandvars('${STOLFIHOME}/lib'), '.' ] 
import argparser; from argparser import ArgParser
import rn
import rmxn
import hrn
import perspective

from decimal import *
from datetime import date

PROG_COPYRIGHT = "Copyright © 2009-05-02 by the State University of Campinas (UNICAMP)"

PROG_HELP = \
  PROG_NAME+ " \\\n" \
  "    -back {BOOL} \\\n" \
  "    -frame {BOOL} \\\n" \
  "    -rho {BOOL} \\\n" \
  "    -system { \"SE\" | \"SZ\" | \"CY\" | \"CA\" } \\\n" \
  +argparser.help_info_HELP+ " \\\n" \
  "    > {FIGURE}.svg"
  
PROG_INFO = \
  "NAME\n" \
  "  " +PROG_NAME+ " - " +PROG_DESC+  ".\n" \
  "\n" \
  "SYNOPSIS\n" \
  "  " +PROG_HELP+ "\n" \
  "\n" \
  "DESCRIPTION\n" \
  "  Writes an SVG illustration for the Wikipedia articles on coord systems.\n" \
  "\n" \
  "OPTIONS\n" \
  "  -back {BOOL} \n" \
  "    If {BOOL} is 1, paints the background with a nonwhite color. If {BOOL} is 0," \
  " leaves it transparent. This option is meant to debug the image size.\n" \
  "\n" \
  "  -frame {BOOL} \n" \
  "    If {BOOL} is 1, draws some framing lines. This option is meant" \
  " to debug the plot dimensions.\n" \
  "\n" \
  "  -rho {BOOL} \n" \
  "    If {BOOL} is 1, uses \"rho\" for radius, else uses \"r\".\n" \
  "\n" \
  "  -system { \"SE\" | \"SZ\" | \"CY\" | \"CA\" } \n" \
  "    Coordinate system to illustrate.\n" \
  "      \"SE\" = Spherical (elevation angle).\n" \
  "      \"SZ\" = Spherical (zenith angle).\n" \
  "      \"CY\" = Cylindrical.\n" \
  "      \"CA\" = Cartesian.\n" \
  "\n" \
  "DOCUMENTATION OPTIONS\n" \
  +argparser.help_info_INFO+ "\n" \
  "\n" \
  "SEE ALSO\n" \
  "  cat(1).\n" \
  "\n" \
  "AUTHOR\n" \
  "  Created 2009-04-04 by Jorge Stolfi, IC-UNICAMP.\n" \
  "\n" \
  "MODIFICATION HISTORY\n" \
  "  2009-04-04 by J. Stolfi, IC-UNICAMP: created.\n" \
  "\n" \
  "WARRANTY\n" \
  "  " +argparser.help_info_NO_WARRANTY+ "\n" \
  "\n" \
  "RIGHTS\n" \
  "  " +PROG_COPYRIGHT+ ".\n" \
  "\n" \
  "  " +argparser.help_info_STANDARD_RIGHTS

# COMMAND ARGUMENT PARSING
pp = ArgParser(sys.argv, sys.stderr, PROG_HELP, PROG_INFO)

class Options :
  back = None;
  system = None;
  err = None;
 
def arg_error(msg):
  "Prints the error message {msg} about the command line arguments, and aborts."
  sys.stderr.write("%s\n" % msg);
  sys.stderr.write("usage: %s\n" % PROG_HELP);
  sys.exit(1)

def parse_args(pp) :
  "Parses command line arguments.\n" \
  "\n" \
  "  Expects an {ArgParser} instance {pp} containing the arguments," \
  " still unparsed.  Returns an {Options} instance {op}, where" \
  " {op.err} is an error message, if any (a string) or {None}."
  
  op = Options();
 
  # Being optimistic:
  op.err = None

  pp.get_keyword("-back")
  op.back = pp.get_next_int(0, 1)
  
  pp.get_keyword("-frame")
  op.frame = pp.get_next_int(0, 1)
  
  pp.get_keyword("-rho")
  op.rho = pp.get_next_int(0, 1)
  
  pp.get_keyword("-system")
  op.system = pp.get_next()

  return op
  #----------------------------------------------------------------------

class Dimensions :
  "Plot dimensions and perspective matrix." 
  
  def __init__(dim, op) :

    dim.map = None;     # World to Image perspective map.

    # Coordinates of point:
    dim.xc_val = None; # X coordinate (for all systems).
    dim.yc_val = None; # Y coordinate (for all systems).
    dim.zc_val = None; # Z coordinate (for all systems).
    dim.rc_val = None; # Radial coordinate (for SE, SZ, CY).
    dim.ac_val = None; # Azimuth angle (for SE, SZ, CY).
    dim.ec_val = None; # Elevation angle (for SE, SZ).

    dim.hd_len = 0.08; # Length of arrow heads (W).
    dim.ax_len = 1.20; # Length of coord axes (W).

    dim.font_wy = 30;  # Font size in pixels.

    dim.pt_rad = 0.02; # Point size in World units.

    dim.ax_unit = 0.2; # Unit for axis ticks.

    dim.fig_wx = 620;  # Total figure width.
    dim.fig_wy = 600;  # Total figure height.

    dim.ct_color = '200,0,100';  # Color for significant coord traces.

    dim.map = None;    # World to Image perspective map.
    dim.scale = 1.0;   # Final scale factor (can be chaged without changing any other dim).

    # Coordinates of point:
    if (op.system == 'CA') :
      dim.xc_val = 0.4;
      dim.yc_val = 0.6;
      dim.zc_val = 0.8;
    elif (op.system == 'CY') :
      dim.rc_val = 0.8;
      dim.ac_val = math.radians(130);
      dim.zc_val = 0.8;
      # Compute X,Y from azimuth and radius:
      dim.xc_val = dim.rc_val*cos(dim.ac_val);
      dim.yc_val = dim.rc_val*sin(dim.ac_val);
    elif (op.system == 'SE'):
      dim.rc_val = 0.8;
      dim.ac_val = math.radians(130);
      dim.ec_val = math.radians(50);
      # Compute X,Y,Z from azimuth, elevation, and radius:
      dim.xc_val = dim.rc_val*cos(dim.ec_val)*cos(dim.ac_val);
      dim.yc_val = dim.rc_val*cos(dim.ec_val)*sin(dim.ac_val);
      dim.zc_val = dim.rc_val*sin(dim.ec_val);
    elif (op.system == 'SZ'):
      dim.rc_val = 0.8;
      dim.ac_val = math.radians(130);
      dim.ec_val = math.radians(70);
      # Compute X,Y,Z from azimuth, elevation, and radius:
      dim.xc_val = dim.rc_val*sin(dim.ec_val)*cos(dim.ac_val);
      dim.yc_val = dim.rc_val*sin(dim.ec_val)*sin(dim.ac_val);
      dim.zc_val = dim.rc_val*cos(dim.ec_val);
    else :
      assert False, "invalid coord system";

    # Perspective map:
    att = [0,0,0];
    obs = [5,3,3];
    upd = [0,0,1];

    mag = [+220,-220,+220];
    ctr = [+320,+300,0000];

    dim.map = perspective.camera_matrix(att,obs,upd,mag,ctr);
    sys.stderr.write("dim.map = %s\n" % dim.map);
    #----------------------------------------------------------------------

  #----------------------------------------------------------------------
  
def output_figure(op) :
  "Writes the figure to {stdout}."
  "\n" \
  "  Expects an {Options} instance {op}."

  # Computes the sizes of things and the perspective map:
  dim = Dimensions(op)

  output_figure_preamble(op,dim)
  sys.stdout.write('\n')
  output_figure_obj_defs(op,dim)
  sys.stdout.write('\n')
  output_figure_body(op,dim)
  sys.stdout.write('\n')
  output_figure_postamble(op,dim)
  sys.stderr.write("done.\n")
  #----------------------------------------------------------------------
  
def output_figure_preamble(op,dim) :
  "Writes the SVG preamble to {stdout}."
  
  sys.stdout.write( \
    '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \
    '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' \
    '<!-- Created on '+ date.isoformat(date.today()) +' by Jorge Stolfi with the script make-coord-system-figure -->\n' \
    '<!-- This file is declared PUBLIC DOMAIN by its creator -->\n' \
    '\n' \
    '<svg\n' \
    '  id="fig"\n' \
    '  xmlns="http://www.w3.org/2000/svg"\n' \
    '  xmlns:xlink="http://www.w3.org/1999/xlink"\n' \
    '\n' \
    '  fill="none"\n' \
    '  fill-opacity="1"\n' \
    '  fill-rule="evenodd"\n' \
    '\n' \
    '  stroke-linecap="round"\n' \
    '  stroke-linejoin="round"\n' \
    '  stroke-dasharray="none"\n' \
    '  stroke-opacity="1"\n' \
    '  stroke-width="1.5px"\n' \
    '\n' \
    '  font-style="normal"\n' \
    '  font-weight="normal"\n' \
    '  font-size="'+ dts(dim.font_wy) +'px"\n' \
    '  font-family="Bitstream Vera"\n' \
    '\n' \
    '  width="'+ dts(dim.scale*dim.fig_wx) +'"\n' \
    '  height="'+ dts(dim.scale*dim.fig_wy) +'"\n' \
    '>\n' \
  )
  sys.stdout.write('\n')
  if (op.back) :
    sys.stdout.write( \
      '    <!-- Rectangle to test the figure size -->\n' \
      '    <rect x="000" y="000" width="'+ dts(dim.scale*dim.fig_wx) +'" height="'+ dts(dim.scale*dim.fig_wy) +'"' \
             ' stroke="#000000" stroke-width="2px" fill="#ffcc55"' \
             ' />\n' \
    )
  sys.stdout.write('\n')

  # Scale everything and position the diagram:
  sys.stdout.write( \
    '  <g\n' \
    '    transform="scale('+ dts(dim.scale)+ ')"\n' \
    '  >\n' \
  )
  #----------------------------------------------------------------------

def output_figure_obj_defs(op,dim) :
  "Writes the main object definitions to {stdout}."

  sys.stdout.write( \
    '  <defs>\n' \
    '  </defs>\n' \
  )
  #----------------------------------------------------------------------

def output_figure_body(op,dim) :
  "Writes the body of the figure to {stdout}."

  # Plot the reference plane(s):
  if (op.system == 'CA') :
    output_system_planes_CA(op,dim);
  else:
    output_system_planes_CY_SE_SZ(op,dim);

  pos_W = [dim.xc_val,dim.yc_val,dim.zc_val];
  
  if (op.system == 'SE') :
    output_system_SE(op,dim,pos_W);
  elif (op.system == 'CA') :  
    output_system_CA(op,dim,pos_W);
  elif (op.system == 'CY') :  
    output_system_CY(op,dim,pos_W);
  elif (op.system == 'SZ') :  
    output_system_SZ(op,dim,pos_W);
  else :
    assert False, "invalid {op.system}";

def output_system_planes_CA(op,dim):

  # Key World points:
  ooo_W = [00,00,00]; # Origin.

  poo_W = [+1,00,00]; # +X axis dir.
  moo_W = [-1,00,00]; # -X axis dir.
  
  opo_W = [00,+1,00]; # +Y axis dir.
  omo_W = [00,-1,00]; # -Y axis dir.
  
  oop_W = [00,00,+1]; # +Z axis dir.
  oom_W = [00,00,-1]; # -Z axis dir.

  # Key Image points:
  ooo = img_point(ooo_W,dim.map); # Origin.

  moo = img_point(moo_W,dim.map); # Point -1 on X axis
  poo = img_point(poo_W,dim.map); # Point +1 on X axis

  omo = img_point(omo_W,dim.map); # Point -1 on U axis
  opo = img_point(opo_W,dim.map); # Point +1 on U axis

  oom = img_point(oom_W,dim.map); # Point -1 on Z axis
  oop = img_point(oop_W,dim.map); # Point +1 on Z axis

  pmo = img_point(rn.add(poo_W,omo_W),dim.map); # Corner A of XY square.
  ppo = img_point(rn.add(poo_W,opo_W),dim.map); # Corner B of XY square.
  mpo = img_point(rn.add(moo_W,opo_W),dim.map); # Corner C of XY square.
  mmo = img_point(rn.add(moo_W,omo_W),dim.map); # Corner D of XY square.

  opm = img_point(rn.add(opo_W,oom_W),dim.map); # Corner A of UZ square.
  opp = img_point(rn.add(opo_W,oop_W),dim.map); # Corner B of UZ square.
  omp = img_point(rn.add(omo_W,oop_W),dim.map); # Corner C of UZ square.
  omm = img_point(rn.add(omo_W,oom_W),dim.map); # Corner D of UZ square.

  mop = img_point(rn.add(moo_W,oop_W),dim.map); # Corner A of ZX square.
  pop = img_point(rn.add(poo_W,oop_W),dim.map); # Corner B of ZX square.
  pom = img_point(rn.add(poo_W,oom_W),dim.map); # Corner C of ZX square.
  mom = img_point(rn.add(moo_W,oom_W),dim.map); # Corner D of ZX square.

  sys.stdout.write( \
    '    <!-- The reference planes: -->\n' \
    '    <g stroke="rgb(185, 205, 170)" fill="rgb(215, 235, 200)" fill-opacity="0.5">\n' \
    '      <polygon points="'+ pfm(moo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(mom) +'"/>\n' \
    '      <polygon points="'+ pfm(omo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(omm) +'"/>\n' \
    '      <polygon points="'+ pfm(poo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(pom) +'"/>\n' \
    '      <polygon points="'+ pfm(opo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(opm) +'"/>\n' \
    '      \n' \
    '      <polygon points="'+ pfm(pmo) +' '+ pfm(ppo) +' '+ pfm(mpo) +' '+ pfm(mmo) +'"/>\n' \
    '      \n' \
    '      <polygon points="'+ pfm(moo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(mop) +'"/>\n' \
    '      <polygon points="'+ pfm(omo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(omp) +'"/>\n' \
    '      <polygon points="'+ pfm(poo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(pop) +'"/>\n' \
    '      <polygon points="'+ pfm(opo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(opp) +'"/>\n' \
    '    </g>\n' \
  );
  if (op.frame) :
    # Auxiliary lines for debugging:
    sys.stdout.write( \
      '    <polygon points="'+ pfm(pmo) +' '+ pfm(ppo) +' '+ pfm(mpo) +' '+ pfm(mmo) +'" stroke="rgb(177,0,0)" fill="none" />\n' \
      '    <path d="M '+ pfm(pmo) +' L '+ pfm(mpo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(ppo) +' L '+ pfm(mmo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(moo) +' L '+ pfm(poo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(omo) +' L '+ pfm(opo) +'" stroke="rgb(137, 137, 137)"/>\n' \
    );
    sys.stdout.write('\n');
  #----------------------------------------------------------------------
  
def output_system_planes_CY_SE_SZ(op,dim):

  # Key World points:
  ooo_W = [00,00,00]; # Origin.

  poo_W = [+1,00,00]; # +X axis dir.
  moo_W = [-1,00,00]; # -X axis dir.
  
  opo_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,1.0); # +U axis dir.
  omo_W = vec_from_ang_rad([-1,00,00],[00,-1,00],dim.ac_val,1.0); # -U axis dir.
  
  oop_W = [00,00,+1]; # +Z axis dir.
  oom_W = [00,00,-1]; # -Z axis dir.

  # Key Image points:
  ooo = img_point(ooo_W,dim.map); # Origin.

  moo = img_point(moo_W,dim.map); # Point -1 on X axis
  poo = img_point(poo_W,dim.map); # Point +1 on X axis

  omo = img_point(omo_W,dim.map); # Point -1 on U axis
  opo = img_point(opo_W,dim.map); # Point +1 on U axis

  oom = img_point(oom_W,dim.map); # Point -1 on Z axis
  oop = img_point(oop_W,dim.map); # Point +1 on Z axis

  opm = img_point(rn.add(opo_W,oom_W),dim.map); # Corner A of UZ square.
  opp = img_point(rn.add(opo_W,oop_W),dim.map); # Corner B of UZ square.
  omp = img_point(rn.add(omo_W,oop_W),dim.map); # Corner C of UZ square.
  omm = img_point(rn.add(omo_W,oom_W),dim.map); # Corner D of UZ square.

  mop = img_point(rn.add(moo_W,oop_W),dim.map); # Corner A of ZX square.
  pop = img_point(rn.add(poo_W,oop_W),dim.map); # Corner B of ZX square.
  pom = img_point(rn.add(poo_W,oom_W),dim.map); # Corner C of ZX square.
  mom = img_point(rn.add(moo_W,oom_W),dim.map); # Corner D of ZX square.

  # !!! BUG !!! should compute the ellipse from the map !!!
  sys.stdout.write( \
    '    <!-- The reference planes: -->\n' \
    '    <g stroke="rgb(185, 205, 170)" fill="rgb(215, 235, 200)" fill-opacity="0.5">\n' \
    '      <polygon points="'+ pfm(moo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(mom) +'"/>\n' \
    '      <polygon points="'+ pfm(omo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(omm) +'"/>\n' \
    '      <polygon points="'+ pfm(poo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(pom) +'"/>\n' \
    '      <polygon points="'+ pfm(opo) +' '+ pfm(ooo) +' '+ pfm(oom) +' '+ pfm(opm) +'"/>\n' \
    '      \n' \
    '      <ellipse cx="0" cy="14" rx="222" ry="102" transform="translate('+ pfm(ooo) +')rotate(0)"/>\n' \
    '      \n' \
    '      <polygon points="'+ pfm(moo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(mop) +'"/>\n' \
    '      <polygon points="'+ pfm(omo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(omp) +'"/>\n' \
    '      <polygon points="'+ pfm(poo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(pop) +'"/>\n' \
    '      <polygon points="'+ pfm(opo) +' '+ pfm(ooo) +' '+ pfm(oop) +' '+ pfm(opp) +'"/>\n' \
    '    </g>\n' \
  );

  sys.stdout.write('\n');
  if (op.frame) :
    # Auxiliary lines for debugging:
    pmo = img_point([+1,-1,00],dim.map); # Corner A of XY square.
    ppo = img_point([+1,+1,00],dim.map); # Corner B of XY square.
    mpo = img_point([-1,+1,00],dim.map); # Corner C of XY square.
    mmo = img_point([-1,-1,00],dim.map); # Corner D of XY square.
    sys.stdout.write( \
      '    <polygon points="'+ pfm(pmo) +' '+ pfm(ppo) +' '+ pfm(mpo) +' '+ pfm(mmo) +'" stroke="rgb(177,0,0)" fill="none" />\n' \
      '    <path d="M '+ pfm(pmo) +' L '+ pfm(mpo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(ppo) +' L '+ pfm(mmo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(moo) +' L '+ pfm(poo) +'" stroke="rgb(137, 137, 137)"/>\n' \
      '    <path d="M '+ pfm(omo) +' L '+ pfm(opo) +'" stroke="rgb(137, 137, 137)"/>\n' \
    );
    sys.stdout.write('\n');
  #----------------------------------------------------------------------

def output_system_CA(op,dim,pos_W) :

  # Relevant points:
  ooo_W = [00,00,00];
  px_W = [dim.xc_val,00,00];
  py_W = [00,dim.yc_val,00];
  pz_W = [00,00,dim.zc_val];
  pxy_W = [dim.xc_val,dim.yc_val,00];
  pyz_W = [00,dim.yc_val,dim.zc_val];
  pxz_W = [dim.xc_val,00,dim.zc_val];

  output_coord_axis(op,dim,[+1,00,00],[-10,+5],'X');
  output_coord_axis(op,dim,[00,+1,00],[+5,-5],'Y');
  output_coord_axis(op,dim,[00,00,+1],[-7,+5],'Z');
  sys.stdout.write('\n');

  ooo = img_point(ooo_W,dim.map)
  output_label(op,dim,ooo,[-5,-5],make_italic_style('O'));

  # Coordinate traces:
  output_straight_trace(op,dim,ooo_W,px_W,True,'x');
  # output_straight_trace(op,dim,ooo_W,py_W,False,'');
  # output_straight_trace(op,dim,ooo_W,pz_W,False,'');

  output_straight_trace(op,dim,px_W,pxy_W,True,'y');
  output_straight_trace(op,dim,px_W,pxz_W,True,'');
  output_straight_trace(op,dim,py_W,pxy_W,True,'');
  output_straight_trace(op,dim,py_W,pyz_W,True,'');
  output_straight_trace(op,dim,pz_W,pxz_W,True,'');
  output_straight_trace(op,dim,pz_W,pyz_W,True,'');

  output_straight_trace(op,dim,pyz_W,pos_W,True,'');
  output_straight_trace(op,dim,pxz_W,pos_W,True,'');
  output_straight_trace(op,dim,pxy_W,pos_W,True,'z');

  # The point:
  output_point(op,dim,pos_W,dim.pt_rad,'x','y','z');
  #----------------------------------------------------------------------  

def output_system_CY(op,dim,pos_W) :
  output_coord_axis(op,dim,[+1,00,00],[-5,+5],'A');
  output_coord_axis(op,dim,[00,00,+1],[-5,-5],'L');
  sys.stdout.write('\n');

  if (op.rho) : 
    r_str = 'ρ'; 
  else : 
    r_str = 'r';  

  # Relevant points:
  ooo_W = [00,00,00];
  Pr_W = [dim.rc_val,00,00];
  Pz_W = [00,00,dim.zc_val];
  Pra_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,dim.rc_val);
  Prz_W = [dim.rc_val,00,dim.zc_val];

  ooo = img_point(ooo_W,dim.map)
  output_label(op,dim,ooo,[-5,-5],make_italic_style('O'));

  # Radial traces:
  output_straight_trace(op,dim,ooo_W,Pr_W,True,r_str);
  output_straight_trace(op,dim,ooo_W,Pra_W,True,'');
  output_straight_trace(op,dim,Pz_W,Prz_W,True,'');
  output_straight_trace(op,dim,Pz_W,pos_W,True,'');

  # Azimuth traces:
  output_circular_trace(op,dim,ooo_W,[+1,00,00],[00,+1,00],0,dim.ac_val,dim.rc_val,'φ');
  output_circular_trace(op,dim,Pz_W,[+1,00,00],[00,+1,00],0,dim.ac_val,dim.rc_val,'');

  # Longitudinal traces:
  # output_straight_trace(op,dim,ooo_W,Pz_W,False,'');
  output_straight_trace(op,dim,Pr_W,Prz_W,True,'');
  output_straight_trace(op,dim,Pra_W,pos_W,True,'z');

  # The point:
  output_point(op,dim,pos_W,dim.pt_rad,r_str,'φ','z');
  #----------------------------------------------------------------------

def output_system_SE(op,dim,pos_W) :
  output_coord_axis(op,dim,[+1,00,00],[-5,+5],'A');
  sys.stdout.write('\n');

  if (op.rho) : 
    r_str = 'ρ'; 
  else : 
    r_str = 'r';  

  # Relevant points:
  ooo_W = [00,00,00];
  Pr_W = [dim.rc_val,00,00];
  Pz_W = [00,00,dim.zc_val];
  Pra_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,dim.rc_val);
  Pre_W = vec_from_ang_rad([+1,00,00],[00,00,+1],dim.ec_val,dim.rc_val);

  # Radial traces:
  output_straight_trace(op,dim,ooo_W,Pr_W,True,r_str);
  output_straight_trace(op,dim,ooo_W,Pra_W,True,'');
  output_straight_trace(op,dim,ooo_W,Pre_W,True,'');
  output_straight_trace(op,dim,ooo_W,pos_W,True,'');
  output_straight_trace(op,dim,Pz_W,Pre_W,True,'');
  output_straight_trace(op,dim,Pz_W,pos_W,True,'');
  output_straight_trace(op,dim,ooo_W,Pz_W,True,'');
  
  # Elevation traces:
  xy_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,1.0);
  output_circular_trace(op,dim,ooo_W,[+1,00,00],[00,00,+1],0,dim.ec_val,dim.rc_val,'θ');
  output_circular_trace(op,dim,ooo_W,xy_W,[00,00,+1],0,dim.ec_val,dim.rc_val,'');

  # Azimuth traces:
  tp_rad = dim.rc_val*cos(dim.ec_val);
  output_circular_trace(op,dim,ooo_W,[+1,00,00],[00,+1,00],0,dim.ac_val,dim.rc_val,'');
  output_circular_trace(op,dim,Pz_W,[+1,00,00],[00,+1,00],0,dim.ac_val,tp_rad,'φ');

  # The point:
  output_point(op,dim,pos_W,dim.pt_rad,r_str,'θ','φ');
  #----------------------------------------------------------------------
    
def output_system_SZ(op,dim,pos_W) :
  output_coord_axis(op,dim,[+1,00,00],[-5,+5],'A');
  output_coord_axis(op,dim,[00,00,+1],[-5,-5],'Z');

  if (op.rho) : 
    r_str = 'ρ'; 
  else : 
    r_str = 'r';  

  # Relevant points:
  ooo_W = [00,00,00];
  Pr_W = [00,00,dim.rc_val];
  Pz_W = [00,00,dim.zc_val];
  Pre_W = vec_from_ang_rad([00,00,+1],[+1,00,00],dim.ec_val,dim.rc_val);

  Qr_W = [dim.rc_val*sin(dim.ec_val),00,00];
  Qra_W = [pos_W[0],pos_W[1],00];

  Sr_W = [dim.rc_val,00,00];
  Sra_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,dim.rc_val);

  # Radial traces:
  output_straight_trace(op,dim,ooo_W,Pr_W,True,r_str);
  output_straight_trace(op,dim,ooo_W,Pre_W,True,'');
  output_straight_trace(op,dim,ooo_W,pos_W,True,'');
  output_straight_trace(op,dim,Pz_W,Pre_W,True,'');
  output_straight_trace(op,dim,Pz_W,pos_W,True,'');
  # output_straight_trace(op,dim,ooo_W,Qr_W,True,'');
  # output_straight_trace(op,dim,ooo_W,Qra_W,True,'');
  output_straight_trace(op,dim,ooo_W,Sr_W,True,'');
  output_straight_trace(op,dim,ooo_W,Sra_W,True,'');

  # Vertical traces:
  # output_straight_trace(op,dim,Qr_W,Pre_W,True,'');
  # output_straight_trace(op,dim,Qra_W,pos_W,True,'');
   
  # Elevation traces:
  xy_W = vec_from_ang_rad([+1,00,00],[00,+1,00],dim.ac_val,1.0);
  output_circular_trace(op,dim,ooo_W,[00,00,+1],[+1,00,00],0,dim.ec_val,dim.rc_val,'θ');
  output_circular_trace(op,dim,ooo_W,[00,00,+1],[+1,00,00],dim.ec_val,math.pi/2,dim.rc_val,'');
  output_circular_trace(op,dim,ooo_W,[00,00,+1],xy_W,0,dim.ec_val,dim.rc_val,'');
  output_circular_trace(op,dim,ooo_W,[00,00,+1],xy_W,dim.ec_val,math.pi/2,dim.rc_val,'');
 
  # Azimuth traces:
  tp_rad = dim.rc_val*sin(dim.ec_val);
  output_circular_trace(op,dim,Pz_W,[+1,00,00],[00,+1,00],0,dim.ac_val,tp_rad,'φ');
  # output_circular_trace(op,dim,ooo_W,[+1,00,00],[00,+1,00],0,dim.ac_val,tp_rad,'');
  output_circular_trace(op,dim,ooo_W,[+1,00,00],[00,+1,00],0,dim.ac_val,dim.rc_val,'');

  # The point:
  output_point(op,dim,pos_W,dim.pt_rad,r_str,'θ','φ');
  #----------------------------------------------------------------------

def output_coord_axis(op,dim,ax_dir,lab_dp,lab_str) :
  "Draws a coordinate axis." \
  "\n" \
  "  The axis goes from the origin to {dim.ax_len*ax_dir}.  The arrow tip has" \
  " size {dim.hd_len}.  The label is {lab_str} offset by {lab_dp} from the tip."
  
  # World points:
  ooo_W = [00,00,00]; # Origin.
  tip_W = rn.add(ooo_W,rn.scale(dim.ax_len,ax_dir)); # Tip of axis.
 
  # Image points:
  ooo = img_point(ooo_W,dim.map); # Origin.
  tip = img_point(tip_W,dim.map); # Axis tip.
  
  # Axis line:
  sys.stdout.write( \
    '    <!-- The '+ lab_str +' axis: -->\n' \
    '    <path d="M '+ pfm(ooo) +' L '+ pfm(tip) +'" stroke="rgb(0,0,0)"/>\n' \
  );

  # Tics:
  # Axis line:
  sys.stdout.write( \
    '    <g stroke="rgb(0,0,0)" fill="rgb(0,0,0)">\n' \
  );
  ct = dim.ax_unit;
  while (ct < dim.ax_len - dim.hd_len) :
    tic_W = rn.add(ooo_W,rn.scale(ct,ax_dir)); # Axis tic (World).
    tic = img_point(tic_W,dim.map); # Axis tic (Image).
    sys.stdout.write( \
      '      <circle '+ xyfm(tic,'c') +' r="1px"/>\n' \
    );
    ct += dim.ax_unit;
  sys.stdout.write( \
    '    </g>\n' \
  );

  # Auxiliary directions perpendicular to the axis:
  bx_dir = [ax_dir[2], ax_dir[0], ax_dir[1]]; # Sideways direction B for arrow tip. 
  cx_dir = [ax_dir[1], ax_dir[2], ax_dir[0]]; # Sideways direction C for arrow tip. 
  output_arrow_head(op,dim,tip_W,ax_dir,bx_dir,cx_dir);

  lab_pos = rn.add(lab_dp, tip);
  output_label(op,dim,tip,lab_dp,make_italic_style(lab_str));
  sys.stdout.write('\n');
  #----------------------------------------------------------------------

def output_arrow_head(op,dim,tip_W,ax_dir,bx_dir,cx_dir) :
  "Draws an arrow head with tip at point {tip}, direction {ax_dir}, length {hd_len}." \
  "\n" \
  "  The directions {bx,dir,cx_dir} must be orthogonal to {ax_dir} and to each other."

  # World points:
  eip_W = rn.add(tip_W,rn.scale(-dim.hd_len,ax_dir));      # Base of arrow head.
  tbm_W = rn.add(eip_W,rn.scale(-0.25*dim.hd_len,bx_dir)); # Corner B+ of arrow head. 
  tbp_W = rn.add(eip_W,rn.scale(+0.25*dim.hd_len,bx_dir)); # Corner B- of arrow head. 
  tcm_W = rn.add(eip_W,rn.scale(-0.25*dim.hd_len,cx_dir)); # Corner C+ of arrow head. 
  tcp_W = rn.add(eip_W,rn.scale(+0.25*dim.hd_len,cx_dir)); # Corner C- of arrow head. 

  # Image points:
  tip = img_point(tip_W,dim.map); # Arrow tip.
  tbm = img_point(tbm_W,dim.map); # Corner B+ of arrow head. 
  tbp = img_point(tbp_W,dim.map); # Corner B- of arrow head. 
  tcm = img_point(tcm_W,dim.map); # Corner C+ of arrow head. 
  tcp = img_point(tcp_W,dim.map); # Corner C- of arrow head. 
  
  sys.stdout.write( \
    '    <path d="M '+ pfm(tcm) +' L '+ pfm(tbm) +' L '+ pfm(tcp) +' L '+ pfm(tbp) +'" stroke="rgb(0,0,0)" fill="rgb(0,0,0)" />\n' \
    '    <path d="M '+ pfm(tip) +' L '+ pfm(tbm) +' L '+ pfm(tbp) +'" stroke="rgb(0,0,0)" fill="rgb(0,0,0)" />\n' \
    '    <path d="M '+ pfm(tip) +' L '+ pfm(tcm) +' L '+ pfm(tcp) +'" stroke="rgb(0,0,0)" fill="rgb(0,0,0)" />\n' \
  );
  #----------------------------------------------------------------------

def output_label(op,dim,pos,dp,str) :
  "Ouputs a label {str} at image position {pos} displaced by {dp}."

  if (str != '') :
    dpx = dp[0];
    dpy = dp[1];
    # Compute anchor {anch}:
    if (dpx < 0) :
      anch = 'end'
    elif (dpx > 0) :
      anch = 'start'
    else :
      anch = 'middle';
    if (dpy > 0) :
      # Adjust {dpy} for font height:
      dpy += 0.6*dim.font_wy; 
    elif (dpy == 0) :
      dpy += 0.3*dim.font_wy; 
    pos = rn.add(pos, [dpx,dpy]);
    sys.stdout.write( \
      '    <text '+ xyfm(pos,'') +' stroke="rgb(255,255,255)"' \
             ' stroke-width="3px" fill="none" text-anchor="'+ anch +'">'+ str +'</text>\n' \
      '    <text '+ xyfm(pos,'') +' stroke="none"' \
             ' fill="rgb(0,0,0)" text-anchor="'+ anch +'">'+ str +'</text>\n' \
    );
  #----------------------------------------------------------------------

def output_circular_trace(op,dim,ctr_W,u_W,v_W,ang_ini,ang_fin,rad,lab_str) :
  "Draws an angular coordinate trace.\n" \
  "\n" \
  "  The arc goes from {d_ini} to {d_fin} given the center {ctr_W}, the plane's ortho" \
  " axes {u_W,v_W}, the angle interval {ang_ini,ang_fin}, and the radius {rad}." \
  "\n" \
  "  If the label is not empty, uses solid colored line, else a dashed "

  # Parameters:
  dashed = (lab_str == '');
  ang = ang_ini; # Current angle.
  step = math.pi/100; # Initial guess for step.
  if (dashed) :
    down = False; # Is the pen down?
    dlen = 2;     # Length of current dash/gap (px).
    color = '0,0,0';
  else :
    down = True;  # Is the pen down?
    dlen = 4;     # Length of current dash/gap (px).
    color = dim.ct_color;

  # Compute the dash start and stop points {dini[k],dfin[k]}:
  dini = [];
  dfin = [];
  nd = 0;

  # Initial point:
  pos_W = rn.add(ctr_W, vec_from_ang_rad(u_W,v_W,ang_ini,rad)); # Current World point.
  pos = img_point(pos_W,dim.map); # Current Image point.
  ctr = img_point(ctr_W,dim.map); # Image arc center.

  # Loop on steps:
  while (ang < ang_fin) :
    pos_old = pos; # Save previous position.
    ang_old = ang; # Save previous angle.
    
    # Find {ang > ang_old} such that the step takes us about {dlen} avawy from {pre}
    while (True) :
      ang = ang_old + step;
      pos_W = rn.add(ctr_W, vec_from_ang_rad(u_W,v_W,ang,rad)); # Current World point.
      pos = img_point(pos_W,dim.map); # Current Image point.
      dst = rn.dist(pos_old,pos);
      rel = dst/dlen;
      # sys.stderr.write("ang = %6.2f  dst = %6.2f  dlen = %6.2f  rel = %8.4f\n" % (ang,dst,dlen,rel));
      if ((rel >= 0.9) and (rel <= 1.1)) :
        break;
      elif (rel < 0.5) :
        step *= 2;
      elif (rel > 2.0) :
        step /= 2;
      else :
        step /= rel;

    # Trim the angle to {ang_fin}:
    if (ang > ang_fin) :
      ang = ang_fin;
      pos_W = rn.add(ctr_W, vec_from_ang_rad(u_W,v_W,ang,rad)); # Current World point.
      pos = img_point(pos_W,dim.map); # Current Image point.

    # Process a gap/dash from {pre} to {pos}:
    if (down) :
      # Dash:
      dini[nd:nd] = [pos_old];
      dfin[nd:nd] = [pos];
      nd += 1;
      if (dashed) :
        down = False;
        dlen = 3;
    else :
      # Gap:
      down = True;
      dlen = 4;

  nd = len(dini);

  if (not dashed) :
    # Paint the sectors:
    sys.stdout.write( \
      '    <g stroke="none" fill="rgb(100,0,200)" fill-opacity="0.25">\n' \
    );
    for k in range(nd) :
      sys.stdout.write( \
        '      <path d="M '+ pfm(dini[k]) +' L '+ pfm(dfin[k]) +' L '+ pfm(ctr) +'"/>\n' \
      );
    sys.stdout.write( \
      '    </g>\n' \
    );

  # Draw the arc:
  sys.stdout.write( \
    '    <g stroke="rgb(' + color + ')" fill="none">\n' \
  );
  for k in range(nd) :
    sys.stdout.write( \
      '      <path d="M '+ pfm(dini[k]) +' L '+ pfm(dfin[k]) +'" />\n' \
    );
  sys.stdout.write( \
    '    </g>\n' \
  );

  # Compute the label position and displacement:
  ang_mid = (ang_ini + ang_fin)/2;
  mid_W = rn.add(ctr_W, vec_from_ang_rad(u_W,v_W,ang_mid,rad)); # Arc midpoint.
  mid = img_point(mid_W,dim.map); # Midpoint of arc.
  dmd,emd = rn.dir(rn.sub(mid,ctr)); # Direction from center to midpoint of arc.
  dpx = 5*dmd[0];
  dpy = 5*dmd[1];
  output_label(op,dim,mid,[dpx,dpy],make_italic_style(lab_str));
  #----------------------------------------------------------------------

def output_straight_trace(op,dim,ini_W,fin_W,draw,lab_str) :
  "Draws and/or labels a dashed straight line.\n" \
  "\n" \
  "  The line goes from {ini_W} to {fin_W} and is labeled {lab-str}.  If {draw}" \
  " is FALSE, does not plot it, just writes the label." \
  "\n" \
  "  "

  # Parameters:
  dashed = (lab_str == '');
  if (dashed) :
    style = 'stroke="rgb(0,0,0)" stroke-dasharray="3,4" stroke-dashoffset="9"';
  else :
    style = 'stroke="rgb(' + dim.ct_color + ')"';

  ini = img_point(ini_W,dim.map); # Start point.
  fin = img_point(fin_W,dim.map); # End point.
  if (draw) :
    sys.stdout.write( \
      '    <path d="M '+ pfm(ini) +' L '+ pfm(fin) +'" ' + style + '/> -->\n' \
    );
  # Compute the label position and displacement:
  mid = rn.scale(0.5,rn.add(ini,fin)); # Midpoint of trace.
  dtr,ntr = rn.dir(rn.sub(ini,fin)); # Direction and length of trace.
  dpx = +5*dtr[1];
  dpy = -5*dtr[0];
  ooo = img_point([00,00,00],dim.map); # Origin.
  if (abs(dpx) > abs(dpy)) :
    if ((mid[0] < ooo[0]) != (dpx < 0))  :
      dpx = -dpx; dpy = -dpy;
  else :
    if ((mid[1] < ooo[1]) != (dpy < 0))  :
      dpx = -dpx; dpy = -dpy;
  output_label(op,dim,mid,[dpx,dpy],make_italic_style(lab_str));
  #----------------------------------------------------------------------

def output_point(op,dim,pos_W,rad_W,x_str,y_str,z_str) :
  "Draws a dot with the given World position and radius, and its coordinate labels."

  # Image point and radius:
  pos = img_point(pos_W,dim.map);       # Image point.
  rad = img_scale(pos_W,dim.map)*rad_W; # Image radius. 
  if (rad > 0) :
    sys.stdout.write( \
      '    <circle '+ xyfm(pos,'c') +' r="'+ dts(rad) +'"' \
             ' stroke="rgb(0,0,0)" fill="rgb(0,0,0)"/> -->\n' \
    );
  
  # Compute the label position and displacement:
  ooo = img_point([00,00,00],dim.map); # Origin.
  if (pos[0] > ooo[0]) :
    dpx = +20
  else :
    dpx = -20;
  dmd,emd = rn.dir(rn.sub(pos,ooo)); # Direction from center to midpoint of arc.
  dpy = 0;
  lab_fmt = make_roman_style('(') \
    + make_italic_style(x_str) \
    + make_roman_style(',') \
    + make_italic_style(y_str) \
    + make_roman_style(',') \
    + make_italic_style(z_str) \
    + make_roman_style(')');
  output_label(op,dim,pos,[dpx,dpy],lab_fmt);
  #----------------------------------------------------------------------

def output_figure_postamble(op,dim) :
  "Writes the SVG postamble to {stdout}."

  sys.stdout.write( \
      '  </g>\n' \
      '</svg>\n' \
  )

def vec_from_ang_rad(u,v,ang,rad) :
  "Converts polar coords {ang,rad} to Cartesia, given two axis directions {u,v} in {R^3}."
  c = rad*cos(ang);
  s = rad*sin(ang);
  return rn.add(rn.scale(c,u),rn.scale(s,v))
  #----------------------------------------------------------------------

def dts(x) :
  "Converts the decimal number {x} to string."
  return ("%r" % x)
  #----------------------------------------------------------------------

def img_point(p,map):  
  "Computes a two-dimensional Cartesian Image point {p} from a World point."
  if (len(p) == 3) : p = copy.copy(p); p[0:0] = [1];
  assert (len(p) == 4), "invalid point";
  q = rmxn.map_row(p,map);
  return [q[1]/q[0], q[2]/q[0]];
  
def img_scale(p,map):  
  "Computes the World to Image magnificatin factor at the World point {p}."
  if (len(p) == 3) : p = copy.copy(p); p[0:0] = [1];
  assert (len(p) == 4), "invalid point";
  q = rmxn.map_row(p,map);
  s = sqrt(map[1][1]*map[1][1] + map[2][1]*map[2][1] + map[3][1]*map[3][1])
  return s*p[0]/q[0];
  
def make_italic_style(str) :
  "Packages a string with italic style markup."
  if (str != '') :
    return '<tspan font-style="italic">' + str + '</tspan>'
  else :
    return ''

def make_roman_style(str) :
  "Packages a string with normal style (non-italic) markup."
  if (str != '') :
    return '<tspan>' + str + '</tspan>'
  else :
    return ''

def pfm(p) :
  "Formats a two-dimensional Image point {p} as two numbers separated by a comma."
  return "%+06.1f,%+06.1f" % (p[0],p[1]);

def xyfm(p,tag) :
  "Formats a  two-dimensional Image point {p} as '{tag}x=\"{p[0]}\" {tag}y=\"{p[1]}\"'."
  return "%sx=\"%+06.1f\" %sy=\"%+06.1f\"" % (tag,p[0],tag,p[1]);
  
#----------------------------------------------------------------------

# Main program:
op = parse_args(pp);
output_figure(op);