#! /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( \
'\n' \
'\n' \
'\n' \
'\n' \
'\n' \
'\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 '' + str + ''
else :
return ''
def make_roman_style(str) :
"Packages a string with normal style (non-italic) markup."
if (str != '') :
return '' + str + ''
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);